diff --git a/.github/typos.toml b/.github/typos.toml index 82b443bca441..3b35e103ba72 100644 --- a/.github/typos.toml +++ b/.github/typos.toml @@ -3,6 +3,7 @@ mis = "mis" MIS = "MIS" inout = "inout" +esource = "esource" BARs = "BARs" [type.po] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7f95f807710..698833e2bea4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,7 +139,7 @@ jobs: # Upload the book now to retain it in case mdbook test fails. - name: Upload book - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: comprehensive-rust-${{ matrix.language }} path: book/ @@ -165,7 +165,7 @@ jobs: run: npm install working-directory: ./tests - name: Download english book - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: comprehensive-rust-en path: book/ diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 783733145646..af2b021af166 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -52,6 +52,6 @@ jobs: uses: actions/checkout@v6 - name: Check for typos - uses: crate-ci/typos@v1.40.0 + uses: crate-ci/typos@v1.41.0 with: config: ./.github/typos.toml diff --git a/Cargo.lock b/Cargo.lock index 0bafbcfb6d55..e09929ff999b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,8 +24,8 @@ version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" dependencies = [ - "cssparser", - "html5ever", + "cssparser 0.35.0", + "html5ever 0.35.0", "maplit", "tendril", "url", @@ -122,6 +122,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.4" @@ -236,12 +258,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.10.1" @@ -250,20 +266,34 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.35" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chat-async" version = "0.1.0" @@ -336,6 +366,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -353,6 +392,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "control-flow-basics" version = "0.1.0" @@ -367,6 +416,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -426,7 +485,20 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", "smallvec", ] @@ -442,9 +514,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7620f6cfc4dcca21f2b085b7a890e16c60fd66f560cd69ee60594908dc72ab1" +checksum = "bbda285ba6e5866529faf76352bdf73801d9b44a6308d7cd58ca2379f378e994" dependencies = [ "cc", "cxx-build", @@ -457,9 +529,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9bc1a22964ff6a355fbec24cf68266a0ed28f8b84c0864c386474ea3d0e479" +checksum = "af9efde466c5d532d57efd92f861da3bdb7f61e369128ce8b4c3fe0c9de4fa4d" dependencies = [ "cc", "codespan-reporting", @@ -472,9 +544,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f29a879d35f7906e3c9b77d7a1005a6a0787d330c09dfe4ffb5f617728cb44" +checksum = "3efb93799095bccd4f763ca07997dc39a69e5e61ab52d2c407d4988d21ce144d" dependencies = [ "clap", "codespan-reporting", @@ -486,15 +558,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d67109015f93f683e364085aa6489a5b2118b4a40058482101d699936a7836d6" +checksum = "3092010228026e143b32a4463ed9fa8f86dca266af4bf5f3b2a26e113dbe4e45" [[package]] name = "cxxbridge-macro" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d187e019e7b05a1f3e69a8396b70800ee867aa9fc2ab972761173ccee03742df" +checksum = "31d72ebfcd351ae404fb00ff378dfc9571827a00722c9e735c9181aec320ba0a" dependencies = [ "indexmap", "proc-macro2", @@ -644,6 +716,12 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ego-tree" version = "0.10.0" @@ -728,7 +806,7 @@ name = "error-handling" version = "0.1.0" dependencies = [ "anyhow", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -739,9 +817,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fnv" @@ -755,21 +833,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -785,6 +848,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -866,15 +935,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -905,8 +965,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -916,9 +978,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.3+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -989,7 +1053,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -1023,10 +1087,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" dependencies = [ "log", - "markup5ever", + "markup5ever 0.35.0", "match_token", ] +[[package]] +name = "html5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" +dependencies = [ + "log", + "markup5ever 0.36.1", +] + [[package]] name = "http" version = "1.4.0" @@ -1123,22 +1197,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.16" @@ -1415,6 +1473,38 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.78" @@ -1453,15 +1543,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "lifetimes" version = "0.1.0" dependencies = [ - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -1475,9 +1565,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1497,9 +1587,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "mac" @@ -1521,7 +1617,18 @@ checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" dependencies = [ "log", "tendril", - "web_atoms", + "web_atoms 0.1.3", +] + +[[package]] +name = "markup5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" +dependencies = [ + "log", + "tendril", + "web_atoms 0.2.0", ] [[package]] @@ -1689,23 +1796,6 @@ dependencies = [ name = "modules" version = "0.1.0" -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1804,49 +1894,11 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" [[package]] name = "parking_lot" @@ -1894,7 +1946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.17", "ucd-trie", ] @@ -1937,8 +1989,19 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -1947,8 +2010,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -1957,18 +2030,41 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", "syn", @@ -1983,6 +2079,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1995,12 +2100,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "portable-atomic" version = "1.11.1" @@ -2114,6 +2213,62 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.40" @@ -2217,9 +2372,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ "base64", "bytes", @@ -2233,21 +2388,19 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -2271,17 +2424,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.9.4", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2290,6 +2449,7 @@ version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -2297,21 +2457,62 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2355,14 +2556,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f3a24d916e78954af99281a455168d4a9515d65eca99a18da1b813689c4ad9" +checksum = "93cecd86d6259499c844440546d02f55f3e17bd286e529e48d1f9f67e92315cb" dependencies = [ - "cssparser", + "cssparser 0.36.0", "ego-tree", "getopts", - "html5ever", + "html5ever 0.36.1", "precomputed-hash", "selectors", "tendril", @@ -2376,12 +2577,12 @@ checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2389,9 +2590,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -2399,19 +2600,19 @@ dependencies = [ [[package]] name = "selectors" -version = "0.31.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6" +checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" dependencies = [ "bitflags 2.9.4", - "cssparser", + "cssparser 0.36.0", "derive_more", - "fxhash", "log", "new_debug_unreachable", - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "precomputed-hash", + "rustc-hash", "servo_arc", "smallvec", ] @@ -2448,15 +2649,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2496,9 +2697,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ "stable_deref_trait", ] @@ -2615,7 +2816,20 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", "precomputed-hash", "serde", ] @@ -2626,8 +2840,20 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -2662,7 +2888,7 @@ dependencies = [ "reqwest", "scraper", "tempfile", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -2692,7 +2918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2708,9 +2934,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -2759,13 +2985,33 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" name = "testing" version = "0.1.0" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2789,6 +3035,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.48.0" @@ -2817,16 +3078,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.2" @@ -2864,9 +3115,9 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d469594aed8da8afbb9dfd33a5548f057f74d6c8e3a50b142b04398f91029a" +checksum = "8b6aa6c8b5a31e06fd3760eb5c1b8d9072e30731f0467ee3795617fe768e7449" dependencies = [ "base64", "bytes", @@ -2914,9 +3165,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.9.4", "bytes", @@ -2991,7 +3242,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror", + "thiserror 2.0.17", "utf-8", ] @@ -3086,12 +3337,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -3214,16 +3459,47 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", +] + +[[package]] +name = "web_atoms" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +dependencies = [ + "rustls-pki-types", ] [[package]] @@ -3311,6 +3587,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3347,6 +3632,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3380,6 +3680,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3392,6 +3698,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3404,6 +3716,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3428,6 +3746,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3440,6 +3764,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3452,6 +3782,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3464,6 +3800,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3607,3 +3949,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f" diff --git a/mdbook-course/Cargo.toml b/mdbook-course/Cargo.toml index 044fdea8be7a..113e17f7c243 100644 --- a/mdbook-course/Cargo.toml +++ b/mdbook-course/Cargo.toml @@ -12,11 +12,11 @@ description = "An mdbook preprocessor for comprehensive-rust." anyhow = "1.0.100" clap = "4.5.53" lazy_static = "1.5" -log = "0.4.27" +log = "0.4.29" matter = "0.1.0-alpha4" mdbook = "0.4.52" pretty_env_logger = "0.5.0" regex = "1.12" serde = "1.0.228" -serde_json = "1.0.145" +serde_json = "1.0.148" serde_yaml = "0.9" diff --git a/mdbook-exerciser/Cargo.toml b/mdbook-exerciser/Cargo.toml index a504d66a355c..8ddf2c7d0598 100644 --- a/mdbook-exerciser/Cargo.toml +++ b/mdbook-exerciser/Cargo.toml @@ -9,7 +9,7 @@ description = "A tool for extracting starter code for exercises from Markdown fi [dependencies] anyhow = "1.0.100" -log = "0.4.27" +log = "0.4.29" mdbook = "0.4.52" pretty_env_logger = "0.5.0" pulldown-cmark = { version = "0.13.0", default-features = false } diff --git a/src/SUMMARY.md b/src/SUMMARY.md index dd6f25139ead..6519af281737 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -368,6 +368,7 @@ - [Using It](bare-metal/aps/logging/using.md) - [Exceptions](bare-metal/aps/exceptions.md) - [aarch64-rt](bare-metal/aps/aarch64-rt.md) + - [Exceptions](bare-metal/aps/aarch64-rt/exceptions.md) - [Other Projects](bare-metal/aps/other-projects.md) - [Useful Crates](bare-metal/useful-crates.md) - [`zerocopy`](bare-metal/useful-crates/zerocopy.md) @@ -486,16 +487,81 @@ - [Welcome](unsafe-deep-dive/welcome.md) - [Setup](unsafe-deep-dive/setup.md) -- [Motivations](unsafe-deep-dive/motivations.md) - - [Interoperability](unsafe-deep-dive/motivations/interop.md) - - [Data Structures](unsafe-deep-dive/motivations/data-structures.md) - - [Performance](unsafe-deep-dive/motivations/performance.md) -- [Foundations](unsafe-deep-dive/foundations.md) - - [What is unsafe?](unsafe-deep-dive/foundations/what-is-unsafe.md) - - [When is unsafe used?](unsafe-deep-dive/foundations/when-is-unsafe-used.md) - - [Data structures are safe](unsafe-deep-dive/foundations/data-structures-are-safe.md) - - [Actions might not be](unsafe-deep-dive/foundations/actions-might-not-be.md) - - [Less powerful than it seems](unsafe-deep-dive/foundations/less-powerful.md) +- [Introduction](unsafe-deep-dive/introduction.md) + - [Defining Unsafe Rust](unsafe-deep-dive/introduction/definition.md) + - [Purpose of the unsafe keyword](unsafe-deep-dive/introduction/purpose.md) + - [Two roles of the unsafe keyword](unsafe-deep-dive/introduction/two-roles.md) + - [Warm Up Examples](unsafe-deep-dive/introduction/warm-up.md) + - [Using an unsafe block](unsafe-deep-dive/introduction/warm-up/unsafe-block.md) + - [Defining an unsafe function](unsafe-deep-dive/introduction/warm-up/unsafe-fn.md) + - [Implementing an unsafe trait](unsafe-deep-dive/introduction/warm-up/unsafe-impl.md) + - [Defining an unsafe trait](unsafe-deep-dive/introduction/warm-up/unsafe-trait.md) + - [Characteristics of Unsafe Rust](unsafe-deep-dive/introduction/characteristics-of-unsafe-rust.md) + - [Dangerous](unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/dangerous.md) + - [Sometimes necessary](unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-necessary.md) + - [Sometimes useful](unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-useful.md) + - [Responsibility shift](unsafe-deep-dive/introduction/responsibility-shift.md) + - [Stronger development workflow required](unsafe-deep-dive/introduction/impact-on-workflow.md) + - [Example: may_overflow](unsafe-deep-dive/introduction/may_overflow.md) +- [Safety Preconditions](unsafe-deep-dive/safety-preconditions.md) + - [Common Preconditions](unsafe-deep-dive/safety-preconditions/common-preconditions.md) + - [Getter example](unsafe-deep-dive/safety-preconditions/getter.md) + - [Semantic preconditions](unsafe-deep-dive/safety-preconditions/semantic-preconditions.md) + - [Example: u8 to bool](unsafe-deep-dive/safety-preconditions/u8-to-bool.md) + - [Determining preconditions](unsafe-deep-dive/safety-preconditions/determining.md) + - [Example: references](unsafe-deep-dive/safety-preconditions/references.md) + - [Defining your own preconditions](unsafe-deep-dive/safety-preconditions/defining.md) + - [Example: ASCII Type](unsafe-deep-dive/safety-preconditions/ascii.md) +- [Rules of the game](unsafe-deep-dive/rules-of-the-game.md) + - [Rust is sound](unsafe-deep-dive/rules-of-the-game/rust-is-sound.md) + - [Copying memory](unsafe-deep-dive/rules-of-the-game/copying-memory.md) + - [Safe Rust](unsafe-deep-dive/rules-of-the-game/copying-memory/safe.md) + - [Encapsulated Unsafe Rust](unsafe-deep-dive/rules-of-the-game/copying-memory/encapsulated-unsafe.md) + - [Exposed Unsafe Rust](unsafe-deep-dive/rules-of-the-game/copying-memory/exposed-unsafe.md) + - [Documented safety preconditions](unsafe-deep-dive/rules-of-the-game/copying-memory/documented-safety-preconditions.md) + - [Crying Wolf](unsafe-deep-dive/rules-of-the-game/copying-memory/crying-wolf.md) + - [3 shapes of sound Rust](unsafe-deep-dive/rules-of-the-game/3-shapes-of-sound-rust.md) + - [Soundness Proof](unsafe-deep-dive/rules-of-the-game/soundness-proof.md) + - [Soundness](unsafe-deep-dive/rules-of-the-game/soundness-proof/soundness.md) + - [Corollary](unsafe-deep-dive/rules-of-the-game/soundness-proof/corollary.md) + - [Unsoundness](unsafe-deep-dive/rules-of-the-game/soundness-proof/unsoundness.md) +- [Memory Lifecycle](unsafe-deep-dive/memory-lifecycle.md) +- [Initialization](unsafe-deep-dive/initialization.md) + - [MaybeUninit](unsafe-deep-dive/initialization/maybeuninit.md) + - [Arrays of uninit](unsafe-deep-dive/initialization/maybeuninit/arrays.md) + - [MaybeUninit::zeroed()](unsafe-deep-dive/initialization/maybeuninit/zeroed-method.md) + - [ptr::write vs assignment](unsafe-deep-dive/initialization/maybeuninit/write-vs-assignment.md) + - [How to initialize memory](unsafe-deep-dive/initialization/how-to-initialize-memory.md) + - [Partial initialization](unsafe-deep-dive/initialization/partial-initialization.md) +- [Pinning](unsafe-deep-dive/pinning.md) + - [What pinning is](unsafe-deep-dive/pinning/what-pinning-is.md) + - [What a move is](unsafe-deep-dive/pinning/what-a-move-is.md) + - [Definition of Pin](unsafe-deep-dive/pinning/definition-of-pin.md) + - [Why it's difficult](unsafe-deep-dive/pinning/why-difficult.md) + - [`Unpin` trait](unsafe-deep-dive/pinning/unpin-trait.md) + - [`PhantomPinned`](unsafe-deep-dive/pinning/phantompinned.md) + - [Self-Referential Buffer Example](unsafe-deep-dive/pinning/self-referential-buffer.md) + - [C++ implementation](unsafe-deep-dive/pinning/self-referential-buffer/cpp.md) + - [Modeled in Rust](unsafe-deep-dive/pinning/self-referential-buffer/rust.md) + - [With a raw pointer](unsafe-deep-dive/pinning/self-referential-buffer/rust-raw-pointers.md) + - [With an integer offset](unsafe-deep-dive/pinning/self-referential-buffer/rust-offset.md) + - [With `Pin`](unsafe-deep-dive/pinning/self-referential-buffer/rust-pin.md) + - [`Pin` and `Drop`](unsafe-deep-dive/pinning/pin-and-drop.md) + - [Worked Example](unsafe-deep-dive/pinning/drop-and-not-unpin-worked-example.md) +- [FFI](unsafe-deep-dive/ffi.md) + - [Language Interop](unsafe-deep-dive/ffi/language-interop.md) + - [Strategies](unsafe-deep-dive/ffi/strategies.md) + - [Consideration: Type Safety](unsafe-deep-dive/ffi/type-safety.md) + - [Language differences](unsafe-deep-dive/ffi/language-differences.md) + - [Different representations](unsafe-deep-dive/ffi/language-differences/representations.md) + - [Different semantics](unsafe-deep-dive/ffi/language-differences/semantics.md) + - [Rust ↔ C](unsafe-deep-dive/ffi/language-differences/rust-and-c.md) + - [C++ ↔ C](unsafe-deep-dive/ffi/language-differences/cpp-and-c.md) + - [Rust ↔ C++](unsafe-deep-dive/ffi/language-differences/rust-and-cpp.md) + - [`abs(3)`](unsafe-deep-dive/ffi/abs.md) + - [`rand(3)`](unsafe-deep-dive/ffi/rand.md) + - [Exercise:C library](unsafe-deep-dive/ffi/c-library-example.md) + - [Exercise: C++ library](unsafe-deep-dive/ffi/cpp-library-example.md) --- diff --git a/src/bare-metal/aps/aarch64-rt/exceptions.md b/src/bare-metal/aps/aarch64-rt/exceptions.md new file mode 100644 index 000000000000..8af2eb752667 --- /dev/null +++ b/src/bare-metal/aps/aarch64-rt/exceptions.md @@ -0,0 +1,25 @@ +# Exceptions + +`aarch64-rt` provides a trait to define exception handlers, and a macro to +generate the assembly code for the exception vector to call them. + +The trait has default implementations for each method which simply panic, so we +can omit methods for exceptions we don't expect to happen. + + + +```rust,editable,compile_fail +{{#include ../examples/src/exceptions_rt.rs:exceptions}} +``` + +
+ +- The `exception_handlers` macro generates a `global_asm!` block with the + exception vector to call into the Rust code, similar to the `exceptions.S` we + had before. +- `RegisterStateRef` wraps a reference to the stack frame where the register + values were saved by the assembly code when the exception happed. This can be + used for example to extract the parameters for an SMC or HVC call from a lower + EL, and update the values to be restored when the exception handler returns. + +
diff --git a/src/bare-metal/aps/examples/Cargo.lock b/src/bare-metal/aps/examples/Cargo.lock index 2d3d5e98318d..c60977ff86cb 100644 --- a/src/bare-metal/aps/examples/Cargo.lock +++ b/src/bare-metal/aps/examples/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aarch64-paging" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f02b5bfa3a481fdade5948a994ce93aa6c76f67fc19f3a9b270565cf7dfc657" +checksum = "9fcde4fe4e43dff9326a54f5137de608f2b7f69e5a5f86fa8657f0db60a6ba2a" dependencies = [ "bitflags", "thiserror", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aarch64-rt" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dc662bf8045ff4a57989d927607b71654b902f0a1976ec3e7fbb30a1477aa" +checksum = "4969245b57b2aef286e6f507cd3fd1b9be7c6df1f0341141ab42226e3e6c52ae" dependencies = [ "smccc", ] @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "nb" @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "safe-mmio" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02a82ad13df46afeba34a4e54065fa912308b9101b060e4422898eac0e06f6" +checksum = "1e278214ff688cacb43a8e71611d1fd1b0788a8b55a054dbce9a9b70b9a6a6f2" dependencies = [ "zerocopy", ] @@ -170,18 +170,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", diff --git a/src/bare-metal/aps/examples/Cargo.toml b/src/bare-metal/aps/examples/Cargo.toml index 2cc60b92906f..34464650a24d 100644 --- a/src/bare-metal/aps/examples/Cargo.toml +++ b/src/bare-metal/aps/examples/Cargo.toml @@ -7,12 +7,12 @@ edition = "2024" publish = false [dependencies] -aarch64-paging = { version = "0.10.0", default-features = false } -aarch64-rt = "0.3.1" +aarch64-paging = { version = "0.11.0", default-features = false } +aarch64-rt = "0.4.2" arm-pl011-uart = "0.4.0" bitflags = "2.10.0" -log = "0.4.28" -safe-mmio = "0.2.5" +log = "0.4.29" +safe-mmio = "0.2.6" smccc = "0.2.0" spin = "0.10.0" zerocopy = "0.8.31" diff --git a/src/bare-metal/aps/examples/Makefile b/src/bare-metal/aps/examples/Makefile index 8f91d594836a..74390b616428 100644 --- a/src/bare-metal/aps/examples/Makefile +++ b/src/bare-metal/aps/examples/Makefile @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -.PHONY: build qemu qemu_logger qemu_minimal qemu_psci +.PHONY: build qemu qemu_logger qemu_minimal qemu_psci qemu_rt qemu_safemmio -all: minimal.bin improved.bin logger.bin +all: improved.bin logger.bin minimal.bin psci.bin rt.bin safemmio.bin build: cargo build diff --git a/src/bare-metal/aps/examples/src/exceptions.S b/src/bare-metal/aps/examples/src/exceptions.S index bd86eebbdfc7..59122a0fdb1e 100644 --- a/src/bare-metal/aps/examples/src/exceptions.S +++ b/src/bare-metal/aps/examples/src/exceptions.S @@ -14,6 +14,7 @@ * limitations under the License. */ +// ANCHOR: exceptions /** * Saves the volatile registers onto the stack. This currently takes * 14 instructions, so it can be used in exception handlers with 18 @@ -77,39 +78,18 @@ .endm /** - * This is a generic handler for exceptions taken at the current EL - * while using SP0. It behaves similarly to the SPx case by first - * switching to SPx, doing the work, then switching back to SP0 before - * returning. + * This is a generic handler for exceptions taken at the current EL. It saves + * volatile registers to the stack, calls the Rust handler, restores volatile + * registers, then returns. * - * Switching to SPx and calling the Rust handler takes 16 - * instructions. To restore and return we need an additional 16 - * instructions, so we can implement the whole handler within the - * allotted 32 instructions. + * This also works for exceptions taken from lower ELs, if we don't care about + * non-volatile registers. * + * Saving state and jumping to the Rust handler takes 15 instructions, and + * restoring and returning also takes 15 instructions, so we can fit the whole + * handler in 30 instructions, under the limit of 32. */ -.macro current_exception_sp0 handler:req - msr spsel, #1 - save_volatile_to_stack - bl \handler - restore_volatile_from_stack - msr spsel, #0 - eret -.endm - -/** - * This is a generic handler for exceptions taken at the current EL - * while using SPx. It saves volatile registers, calls the Rust - * handler, restores volatile registers, then returns. - * - * This also works for exceptions taken from EL0, if we don't care - * about non-volatile registers. - * - * Saving state and jumping to the Rust handler takes 15 instructions, - * and restoring and returning also takes 15 instructions, so we can - * fit the whole handler in 30 instructions, under the limit of 32. - */ -.macro current_exception_spx handler:req +.macro current_exception handler:req save_volatile_to_stack bl \handler restore_volatile_from_stack @@ -121,64 +101,64 @@ .balign 0x800 vector_table_el1: sync_cur_sp0: - current_exception_sp0 sync_exception_current + current_exception sync_current .balign 0x80 irq_cur_sp0: - current_exception_sp0 irq_current + current_exception irq_current .balign 0x80 fiq_cur_sp0: - current_exception_sp0 fiq_current + current_exception fiq_current .balign 0x80 serr_cur_sp0: - current_exception_sp0 serr_current + current_exception serror_current .balign 0x80 sync_cur_spx: - current_exception_spx sync_exception_current + current_exception sync_current .balign 0x80 irq_cur_spx: - current_exception_spx irq_current + current_exception irq_current .balign 0x80 fiq_cur_spx: - current_exception_spx fiq_current + current_exception fiq_current .balign 0x80 serr_cur_spx: - current_exception_spx serr_current + current_exception serror_current .balign 0x80 sync_lower_64: - current_exception_spx sync_lower + current_exception sync_lower .balign 0x80 irq_lower_64: - current_exception_spx irq_lower + current_exception irq_lower .balign 0x80 fiq_lower_64: - current_exception_spx fiq_lower + current_exception fiq_lower .balign 0x80 serr_lower_64: - current_exception_spx serr_lower + current_exception serror_lower .balign 0x80 sync_lower_32: - current_exception_spx sync_lower + current_exception sync_lower .balign 0x80 irq_lower_32: - current_exception_spx irq_lower + current_exception irq_lower .balign 0x80 fiq_lower_32: - current_exception_spx fiq_lower + current_exception fiq_lower .balign 0x80 serr_lower_32: - current_exception_spx serr_lower + current_exception serror_lower diff --git a/src/bare-metal/aps/examples/src/exceptions.rs b/src/bare-metal/aps/examples/src/exceptions.rs index cc52a1fe3379..07b2185b78de 100644 --- a/src/bare-metal/aps/examples/src/exceptions.rs +++ b/src/bare-metal/aps/examples/src/exceptions.rs @@ -19,8 +19,8 @@ use smccc::psci::system_off; // SAFETY: There is no other global function of this name. #[unsafe(no_mangle)] -extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { - error!("sync_exception_current"); +extern "C" fn sync_current(_elr: u64, _spsr: u64) { + error!("sync_current"); system_off::().unwrap(); } @@ -40,8 +40,8 @@ extern "C" fn fiq_current(_elr: u64, _spsr: u64) { // SAFETY: There is no other global function of this name. #[unsafe(no_mangle)] -extern "C" fn serr_current(_elr: u64, _spsr: u64) { - error!("serr_current"); +extern "C" fn serror_current(_elr: u64, _spsr: u64) { + error!("serror_current"); system_off::().unwrap(); } @@ -68,7 +68,7 @@ extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { // SAFETY: There is no other global function of this name. #[unsafe(no_mangle)] -extern "C" fn serr_lower(_elr: u64, _spsr: u64) { - error!("serr_lower"); +extern "C" fn serror_lower(_elr: u64, _spsr: u64) { + error!("serror_lower"); system_off::().unwrap(); } diff --git a/src/bare-metal/aps/examples/src/exceptions_rt.rs b/src/bare-metal/aps/examples/src/exceptions_rt.rs new file mode 100644 index 000000000000..aad0a8149f76 --- /dev/null +++ b/src/bare-metal/aps/examples/src/exceptions_rt.rs @@ -0,0 +1,45 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: exceptions +use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers}; +use log::error; +use smccc::Hvc; +use smccc::psci::system_off; + +struct Handlers; + +impl ExceptionHandlers for Handlers { + extern "C" fn sync_current(_state: RegisterStateRef) { + error!("sync_current"); + system_off::().unwrap(); + } + + extern "C" fn irq_current(_state: RegisterStateRef) { + error!("irq_current"); + system_off::().unwrap(); + } + + extern "C" fn fiq_current(_state: RegisterStateRef) { + error!("fiq_current"); + system_off::().unwrap(); + } + + extern "C" fn serror_current(_state: RegisterStateRef) { + error!("serror_current"); + system_off::().unwrap(); + } +} + +exception_handlers!(Handlers); diff --git a/src/bare-metal/aps/examples/src/main_rt.rs b/src/bare-metal/aps/examples/src/main_rt.rs index c9f29f328c6f..4d6a4375bef9 100644 --- a/src/bare-metal/aps/examples/src/main_rt.rs +++ b/src/bare-metal/aps/examples/src/main_rt.rs @@ -16,9 +16,9 @@ #![no_main] #![no_std] -mod exceptions; +mod exceptions_rt; -use aarch64_paging::paging::Attributes; +use aarch64_paging::descriptor::Attributes; use aarch64_rt::{InitialPagetable, entry, initial_pagetable}; use arm_pl011_uart::{PL011Registers, Uart, UniqueMmioPointer}; use core::fmt::Write; diff --git a/src/bare-metal/aps/exceptions.md b/src/bare-metal/aps/exceptions.md index 916d952fbc6b..719924c4c75f 100644 --- a/src/bare-metal/aps/exceptions.md +++ b/src/bare-metal/aps/exceptions.md @@ -26,6 +26,14 @@ calling into Rust code: but not `Sync`, then we'll need to wrap it in something like a `Mutex` and put it in a static. +The assembly code for the exception vector: + + + +```armasm +{{#include examples/src/exceptions.S:exceptions}} +``` + [1]: ../../concurrency/send-sync.md diff --git a/src/concurrency/async-exercises/chat-async/Cargo.toml b/src/concurrency/async-exercises/chat-async/Cargo.toml index 05dcfe9a5edd..ca504db26582 100644 --- a/src/concurrency/async-exercises/chat-async/Cargo.toml +++ b/src/concurrency/async-exercises/chat-async/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" futures-util = { version = "0.3.31", features = ["sink"] } http = "1.4.0" tokio = { version = "1.48.0", features = ["full"] } -tokio-websockets = { version = "0.13.0", features = ["client", "fastrand", "server", "sha1_smol"] } +tokio-websockets = { version = "0.13.1", features = ["client", "fastrand", "server", "sha1_smol"] } diff --git a/src/concurrency/sync-exercises/Cargo.toml b/src/concurrency/sync-exercises/Cargo.toml index 5e3124c86c60..742af7078bbb 100644 --- a/src/concurrency/sync-exercises/Cargo.toml +++ b/src/concurrency/sync-exercises/Cargo.toml @@ -13,9 +13,9 @@ name = "link-checker" path = "link-checker.rs" [dependencies] -reqwest = { version = "0.12.24", features = ["blocking"] } -scraper = "0.24.0" +reqwest = { version = "0.13.1", features = ["blocking"] } +scraper = "0.25.0" thiserror = "2.0.17" [dev-dependencies] -tempfile = "3.23.0" +tempfile = "3.24.0" diff --git a/src/exercises/bare-metal/rtc/Cargo.lock b/src/exercises/bare-metal/rtc/Cargo.lock index 43dd99b9317c..786a3b9991f7 100644 --- a/src/exercises/bare-metal/rtc/Cargo.lock +++ b/src/exercises/bare-metal/rtc/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aarch64-paging" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f02b5bfa3a481fdade5948a994ce93aa6c76f67fc19f3a9b270565cf7dfc657" +checksum = "9fcde4fe4e43dff9326a54f5137de608f2b7f69e5a5f86fa8657f0db60a6ba2a" dependencies = [ "bitflags", "thiserror", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aarch64-rt" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dc662bf8045ff4a57989d927607b71654b902f0a1976ec3e7fbb30a1477aa" +checksum = "4969245b57b2aef286e6f507cd3fd1b9be7c6df1f0341141ab42226e3e6c52ae" dependencies = [ "smccc", ] @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "nb" @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "safe-mmio" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02a82ad13df46afeba34a4e54065fa912308b9101b060e4422898eac0e06f6" +checksum = "1e278214ff688cacb43a8e71611d1fd1b0788a8b55a054dbce9a9b70b9a6a6f2" dependencies = [ "zerocopy", ] @@ -202,18 +202,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", diff --git a/src/exercises/bare-metal/rtc/Cargo.toml b/src/exercises/bare-metal/rtc/Cargo.toml index b6b71ac1ddbf..829a1e7773c7 100644 --- a/src/exercises/bare-metal/rtc/Cargo.toml +++ b/src/exercises/bare-metal/rtc/Cargo.toml @@ -7,14 +7,14 @@ edition = "2024" publish = false [dependencies] -aarch64-paging = { version = "0.10.0", default-features = false } -aarch64-rt = "0.3.1" +aarch64-paging = { version = "0.11.0", default-features = false } +aarch64-rt = "0.4.2" arm-gic = "0.7.1" arm-pl011-uart = "0.4.0" bitflags = "2.10.0" chrono = { version = "0.4.42", default-features = false } -log = "0.4.28" -safe-mmio = "0.2.5" +log = "0.4.29" +safe-mmio = "0.2.6" smccc = "0.2.2" spin = "0.10.0" zerocopy = "0.8.31" diff --git a/src/exercises/bare-metal/rtc/src/exceptions.rs b/src/exercises/bare-metal/rtc/src/exceptions.rs index bf3e0e732b73..ac5c99e07f49 100644 --- a/src/exercises/bare-metal/rtc/src/exceptions.rs +++ b/src/exercises/bare-metal/rtc/src/exceptions.rs @@ -12,66 +12,57 @@ // See the License for the specific language governing permissions and // limitations under the License. +use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers}; use arm_gic::gicv3::{GicCpuInterface, InterruptGroup}; use log::{error, info, trace}; use smccc::Hvc; use smccc::psci::system_off; -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { - error!("sync_exception_current"); - system_off::().unwrap(); -} +struct Handlers; -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn irq_current(_elr: u64, _spsr: u64) { - trace!("irq_current"); - let intid = - GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) - .expect("No pending interrupt"); - info!("IRQ {intid:?}"); -} +impl ExceptionHandlers for Handlers { + extern "C" fn sync_current(_state: RegisterStateRef) { + error!("sync_current"); + system_off::().unwrap(); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn fiq_current(_elr: u64, _spsr: u64) { - error!("fiq_current"); - system_off::().unwrap(); -} + extern "C" fn irq_current(_state: RegisterStateRef) { + trace!("irq_current"); + let intid = + GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) + .expect("No pending interrupt"); + info!("IRQ {intid:?}"); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn serr_current(_elr: u64, _spsr: u64) { - error!("serr_current"); - system_off::().unwrap(); -} + extern "C" fn fiq_current(_state: RegisterStateRef) { + error!("fiq_current"); + system_off::().unwrap(); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn sync_lower(_elr: u64, _spsr: u64) { - error!("sync_lower"); - system_off::().unwrap(); -} + extern "C" fn serror_current(_state: RegisterStateRef) { + error!("serror_current"); + system_off::().unwrap(); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn irq_lower(_elr: u64, _spsr: u64) { - error!("irq_lower"); - system_off::().unwrap(); -} + extern "C" fn sync_lower(_state: RegisterStateRef) { + error!("sync_lower"); + system_off::().unwrap(); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { - error!("fiq_lower"); - system_off::().unwrap(); -} + extern "C" fn irq_lower(_state: RegisterStateRef) { + error!("irq_lower"); + system_off::().unwrap(); + } -// SAFETY: There is no other global function of this name. -#[unsafe(no_mangle)] -extern "C" fn serr_lower(_elr: u64, _spsr: u64) { - error!("serr_lower"); - system_off::().unwrap(); + extern "C" fn fiq_lower(_state: RegisterStateRef) { + error!("fiq_lower"); + system_off::().unwrap(); + } + + extern "C" fn serror_lower(_state: RegisterStateRef) { + error!("serror_lower"); + system_off::().unwrap(); + } } + +exception_handlers!(Handlers); diff --git a/src/exercises/bare-metal/rtc/src/main.rs b/src/exercises/bare-metal/rtc/src/main.rs index 0eada2bf95a5..4d6ef4b9b4fa 100644 --- a/src/exercises/bare-metal/rtc/src/main.rs +++ b/src/exercises/bare-metal/rtc/src/main.rs @@ -27,7 +27,7 @@ use arm_gic::{IntId, Trigger, irq_enable, wfi}; use chrono::{TimeZone, Utc}; use core::hint::spin_loop; // ANCHOR: imports -use aarch64_paging::paging::Attributes; +use aarch64_paging::descriptor::Attributes; use aarch64_rt::{InitialPagetable, entry, initial_pagetable}; use arm_gic::gicv3::registers::{Gicd, GicrSgi}; use arm_gic::gicv3::{GicCpuInterface, GicV3}; diff --git a/src/unsafe-deep-dive/case-studies.md b/src/unsafe-deep-dive/case-studies.md new file mode 100644 index 000000000000..3be3183624b8 --- /dev/null +++ b/src/unsafe-deep-dive/case-studies.md @@ -0,0 +1,9 @@ +# Case Studies + +Uses of `unsafe` in production that may be useful for further study: + +| Case study | Topics | +| ------------------------------- | -------------------------------------- | +| tokio's [Intrusive Linked List] | UnsafeCell, PhantomData, PhantomPinned | + +[Intrusive Linked List]: case-studies/intrusive-linked-list.md diff --git a/src/unsafe-deep-dive/case-studies/intrusive-linked-list.md b/src/unsafe-deep-dive/case-studies/intrusive-linked-list.md new file mode 100644 index 000000000000..8bde9c2380b6 --- /dev/null +++ b/src/unsafe-deep-dive/case-studies/intrusive-linked-list.md @@ -0,0 +1,106 @@ +# Tokio's Intrusive Linked List + +> Current as of tokio v1.48.0 + +The Tokio project maintains an [intrusive linked list implementation][ill] that +demonstrates many use cases of `unsafe` and a number of types and traits from +Rust's unsafe ecosystem, including `cell::UnsafeCell`, `mem::ManuallyDrop`, +[pinning](../pinning/what-pinning-is.md), and unsafe traits. + +A linked list is a difficult data structure to implement in Rust, because it +relies heavily on stable memory addresses remaining stable. This isn't something +that happens naturally, as Rust types change their memory address every time +they move. + +## Introductory Walkthrough + +The public API is provided by the `LinkedList` type, which contains fields +for the start and the end of the list. `Option>` could be read as a +`Option<*mut T>`, with the assurance that null pointers will never be created. + +`NonNull` is "_covariant_ over `T`", which means that `NonNull` inherits +the [variance] relationships from `T`. + +```rust,ignore +use core::marker::PhantomData; + +// ... + +/// An intrusive linked list. +/// +/// Currently, the list is not emptied on drop. It is the caller's +/// responsibility to ensure the list is empty before dropping it. +pub(crate) struct LinkedList { + /// Linked list head + head: Option>, + + /// Linked list tail + tail: Option>, + + /// Node type marker. + _marker: PhantomData<*const L>, +} +``` + +`LinkedList` is neither `Send` nor `Sync`, unless its targets are. + +```rust,ignore +unsafe impl Send for LinkedList where L::Target: Send {} +unsafe impl Sync for LinkedList where L::Target: Sync {} +``` + +The `Link` trait used the those trait bounds is defined next. `Link` is an +unsafe trait that manages the relationships between nodes when the list needs to +pass references externally to callers. + +Here's trait's definition. The most significant method is `pointers()`, which +returns a `Pointers` struct. `Pointers` provides access to the two ends of the +link by marking itself as `!Unpin`. + +```rust,ignore +pub unsafe trait Link { + type Handle; + + type Target; + + fn as_raw(handle: &Self::Handle) -> NonNull; + + unsafe fn from_raw(ptr: NonNull) -> Self::Handle; + + /// # Safety + /// + /// The resulting pointer should have the same tag in the stacked-borrows + /// stack as the argument. In particular, the method may not create an + /// intermediate reference in the process of creating the resulting raw + /// pointer. + unsafe fn pointers( + target: NonNull, + ) -> NonNull>; +} +``` + +`Pointers` is where the magic happens: + +```rust,ignore +pub(crate) struct Pointers { + inner: UnsafeCell>, +} + +struct PointersInner { + prev: Option>, + next: Option>, + + /// This type is !Unpin due to the heuristic from: + /// + _pin: PhantomPinned, +} +``` + +## Remarks + +Understanding the whole implementation will take some time, but it's a rewarding +experience. The code demonstrates composing many parts of unsafe Rust's +ecosystem into a workable, high performance data structure. Enjoy exploring! + +[ill]: https://docs.rs/tokio/1.48.0/src/tokio/util/linked_list.rs.html +[variance]: https://doc.rust-lang.org/reference/subtyping.html diff --git a/src/unsafe-deep-dive/ffi.md b/src/unsafe-deep-dive/ffi.md new file mode 100644 index 000000000000..34117bf5751f --- /dev/null +++ b/src/unsafe-deep-dive/ffi.md @@ -0,0 +1 @@ +# FFI diff --git a/src/unsafe-deep-dive/ffi/README.md b/src/unsafe-deep-dive/ffi/README.md new file mode 100644 index 000000000000..3fe7435c3697 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/README.md @@ -0,0 +1,7 @@ +This segment of the class is about the foreign function interface with Rust. + +outline: + +Start by wrapping a simple C function + +progress into more complex cases which involve pointers and uninitialized memory diff --git a/src/unsafe-deep-dive/ffi/abs.md b/src/unsafe-deep-dive/ffi/abs.md new file mode 100644 index 000000000000..807e2adc2c26 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/abs.md @@ -0,0 +1,111 @@ +--- +minutes: 15 +--- + +# Wrapping `abs(3)` + +```rust,editable,ignore +fn abs(x: i32) -> i32; + +fn main() { + let x = -42; + let abs_x = abs(x); + println!("{x}, {abs_x}"); +} +``` + +
+ +In this slide, we’re establishing a pattern for writing wrappers. + +Find the external definition of a function’s signature Write a matching function +in Rust within an `extern` block Confirm which safety invariants need to be +upheld Decide whether it’s possible to mark the function as safe + +Note that this doesn’t work _yet_. + +Add the extern block: + +```rust +unsafe extern "C" { + fn abs(x: i32) -> i32; +} +``` + +Explain that many POSIX functions are available Rust because cargo links against +the C standard library (libc) by default, which makes its symbols into the +program’s scope. + +Show `man 3 abs` in the terminal or [a webpage][abs]. + +Explain that our function signature must match its definition: +`int abs(int j);`. + +Update the code block to use the C types. + +```rust +use std::ffi::c_int; + +unsafe extern "C" { + fn abs(x: c_int) -> c_int; +} +``` + +Discuss rationale: using `ffi::c_int` increases the portability of our code. +When the standard library is compiled for the target platform, the platform can +determine the widths. According to the C standard, an `c_int` may be defined as +an `i16` rather than the much more common `i32`. + +(Optional) Show the [documentation for c_int][c_int] to reveal that it is a type +alias for `i32`. + +Attempt to compile to trigger “error: extern blocks must be unsafe” error +message. + +Add the unsafe keyword to the block: + +```rust +use std::ffi::c_int; + +unsafe extern "C" { + fn abs(x: c_int) -> c_int; +} +``` + +Check that learners understand the significance of this change. We are required +to uphold type safety and other safety preconditions. + +Recompile. + +Add safe keyword to the abs function: + +```rust +use std::ffi::c_int; + +unsafe extern "C" { + safe fn abs(x: c_int) -> c_int; +} +``` + +Explain the `safe fn` marks `abs` as safe to call without an `unsafe` block. + +Completed program for reference: + +```rust +use std::ffi::c_int; + +unsafe extern "C" { + safe fn abs(x: c_int) -> c_int; +} + +fn main() { + let x = -42; + let abs_x = abs(x); + println!("{x}, {abs_x}"); +} +``` + +[abs]: https://www.man7.org/linux/man-pages/man3/abs.3.html +[c_int]: https://doc.rust-lang.org/std/ffi/type.c_int.html + +
diff --git a/src/unsafe-deep-dive/ffi/c-library-example.md b/src/unsafe-deep-dive/ffi/c-library-example.md new file mode 100644 index 000000000000..2a830bdc60c1 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/c-library-example.md @@ -0,0 +1,139 @@ +# C Library Example + +```c +#ifndef TEXT_ANALYSIS_H +#define TEXT_ANALYSIS_H + +#include +#include + +typedef struct TextAnalyst TextAnalyst; + +typedef struct { + const char* start; + size_t length; + size_t index; +} Token; + +typedef enum { + TA_OK = 0, + TA_ERR_NULL_POINTER, + TA_ERR_OUT_OF_MEMORY, + TA_ERR_OTHER, +} TAError; + +/* Return `false` to indicate that no token was found. */ +typedef bool (*Tokenizer)(Token* token, void* extra_context); + + +typedef bool (*TokenCallback)(void* user_context, Token* token, void* result); + +/* TextAnalyst constructor */ +TextAnalyst* ta_new(void); + +/* TextAnalyst destructor */ +void ta_free(TextAnalyst* ta); + +/* Resets state to clear the current document */ +void ta_reset(TextAnalyst* ta); + +/* Use custom tokenizer (defaults to whitespace) */ +void ta_set_tokenizer(TextAnalyst* ta, Tokenizer* func); + +TAError ta_set_text(TextAnalyst* ta, const char* text, size_t len, bool make_copy); + +/* Apply `callback` to each token */ +size_t ta_foreach_token(const TextAnalyst* ta, const TokenCallback* callback, void* user_context); + +/* Get human-readable error message */ +const char* ta_error_string(TAError error); + +#endif /* TEXT_ANALYSIS_H */ +``` + +
+ +C libraries will hide their implementation details with a `void*` argument. + +Consider this header file of a natural language processing library that hides +the `TextAnalyst` and `Analysis` types. + +This can be emulated in Rust with a type similar to this: + +```rust +#[repr(C)] +pub struct TextAnalyst { + _private: [u8; 0], +} +``` + +Exercise: Ask learners to wrap this library. + +_Suggested Solution_ + +```rust +// ffi.rs +use std::ffi::c_char; +use std::os::raw::c_void; + +#[repr(C)] +pub struct TextAnalyst { + _private: [u8; 0], +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Token { + pub start: *const c_char, + pub length: usize, + pub index: usize, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TAError { + Ok = 0, + NullPointer = 1, + OutOfMemory = 2, + Other = 3, +} + +pub type Tokenizer = Option< + unsafe extern "C" fn(token: *mut Token, extra_context: *mut c_void) -> bool, +>; + +pub type TokenCallback = Option< + unsafe extern "C" fn( + user_context: *mut c_void, + token: *mut Token, + result: *mut c_void, + ) -> bool, +>; + +unsafe extern "C" { + pub fn ta_new() -> *mut TextAnalyst; + + pub fn ta_free(ta: *mut TextAnalyst); + + pub fn ta_reset(ta: *mut TextAnalyst); + + pub fn ta_set_tokenizer(ta: *mut TextAnalyst, func: *const Tokenizer); + + pub fn ta_set_text( + ta: *mut TextAnalyst, + text: *const c_char, + len: usize, + make_copy: bool, + ) -> TAError; + + pub fn ta_foreach_token( + ta: *const TextAnalyst, + callback: *const TokenCallback, + user_context: *mut c_void, + ) -> usize; + + pub fn ta_error_string(error: TAError) -> *const c_char; +} +``` + +
diff --git a/src/unsafe-deep-dive/ffi/cpp-library-example.md b/src/unsafe-deep-dive/ffi/cpp-library-example.md new file mode 100644 index 000000000000..bc3872480637 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/cpp-library-example.md @@ -0,0 +1,121 @@ +--- +minutes: 30 +--- + +# Example: String interning library + +C++ Header: interner.hpp + +```cpp +#ifndef INTERNER_HPP +#define INTERNER_HPP + +#include +#include + +class StringInterner { + std::unordered_set strings; + +public: + // Returns pointer to interned string (valid for lifetime of interner) + const char* intern(const char* s) { + auto [it, _] = strings.emplace(s); + return it->c_str(); + } + + size_t count() const { + return strings.size(); + } +}; + +#endif +``` + +C header file: interner.h + +```c +// interner.h (C API for FFI) +#ifndef INTERNER_H +#define INTERNER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct StringInterner StringInterner; + +StringInterner* interner_new(void); +void interner_free(StringInterner* interner); +const char* interner_intern(StringInterner* interner, const char* s); +size_t interner_count(const StringInterner* interner); + +#ifdef __cplusplus +} +#endif +``` + +C++ implementation (interner.cpp) + +```cpp +#include "interner.hpp" +#include "interner.h" + +extern "C" { + +StringInterner* interner_new(void) { + return new StringInterner(); +} + +void interner_free(StringInterner* interner) { + delete interner; +} + +const char* interner_intern(StringInterner* interner, const char* s) { + return interner->intern(s); +} + +size_t interner_count(const StringInterner* interner) { + return interner->count(); +} + +} +``` + +
+ +This is a larger example. Write a wrapper for the string interner. You will need +to guide learners on how to create an opaque pointer, either directly by +explaining the code below or asking learners to do further research. + +_Suggested Solution_ + +```rust +use std::ffi::{CStr, CString}; +use std::marker::PhantomData; +use std::os::raw::c_char; + +#[repr(C)] +pub struct StringInternerRaw { + _opaque: [u8; 0], + _pin: PhantomData<(*mut u8, std::marker::PhantomPinned)>, +} + +unsafe extern "C" { + fn interner_new() -> *mut StringInternerRaw; + + fn interner_free(interner: *mut StringInternerRaw); + + fn interner_intern( + interner: *mut StringInternerRaw, + s: *const c_char, + ) -> *const c_char; + + fn interner_count(interner: *const StringInternerRaw) -> usize; +} +``` + +Once the raw wrapper is written, ask learners to create a safe wrapper. + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences.md b/src/unsafe-deep-dive/ffi/language-differences.md new file mode 100644 index 000000000000..7932cf3e2ebd --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences.md @@ -0,0 +1,24 @@ +--- +minutes: 5 +--- + +# Language differences + +```bob +╭────────────╮ ╭───╮ ╭───╮ ╭────────────╮ +│ │ │ │ │ │ │ │ +│ │ <-----> │ │ <~~~~~~~> │ │ <------> │ │ +│ │ │ │ │ │ │ │ +╰────────────╯ ╰───╯ ╰───╯ ╰────────────╯ + Rust C C "C++" +``` + +
+ +Using C as the lowest common denominator means that lots of the richness +available to Rust and C++ is lost. + +Each translation has the potential for semantic loss, runtime overhead, and +subtle bugs. + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences/cpp-and-c.md b/src/unsafe-deep-dive/ffi/language-differences/cpp-and-c.md new file mode 100644 index 000000000000..6366609d33aa --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences/cpp-and-c.md @@ -0,0 +1,33 @@ +--- +minutes: 3 +--- + +# C++ ↔ C + +| Concern | C | C++ | +| ----------------- | -------------- | ------------------------------------------------- | +| **Overloading** | Manual/ad-hoc | Automatic | +| **Exceptions** | - | Stack unwinding | +| **Destructors** | Manual cleanup | Automatic via destructors (RAII) | +| **Non-POD types** | - | Objects with constructors, vtables, virtual bases | +| **Templates** | - | Compile-time code generation | + +
+ +C++ includes a number of features that don’t exist in C with an FFI impact: + +Overloading: overloads become impossible to express because of name mangling + +Exceptions: must catch exceptions at the FFI boundary and convert as escaping +exceptions in `extern "C"` functions is undefined behavior + +Destructors: C callers won't run destructors; must expose explicit `*_destroy()` +functions + +Non-POD types: Must use opaque pointers across the FFI boundary as pass by value +does not make sense + +Templates: Cannot expose directly; must instantiate explicitly and wrap each +specialization + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences/representations.md b/src/unsafe-deep-dive/ffi/language-differences/representations.md new file mode 100644 index 000000000000..a36daeb537f7 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences/representations.md @@ -0,0 +1,46 @@ +# Different representations + +```rust,editable +fn main() { + let c_repr = b"Hello, C\0"; + let cc_repr = (b"Hello, C++\0", 10u32); + let rust_repr = (b"Hello, Rust", 11); +} +``` + +
+ +Each language has its own opinion about how to implement things, which can lead +to confusion and bugs. Consider three ways to represent text. + +Show how to convert the raw representations to a Rust string slice: + +```rust,ignore +// C representation to Rust +unsafe { + let ptr = c_repr.as_ptr() as *const i8; + let c: &str = std::ffi::CStr::from_ptr(ptr).to_str().unwrap(); + println!("{c}"); +}; + +// C++ representation to Rust +unsafe { + let ptr = cc_repr.0.as_ptr(); + let bytes = std::slice::from_raw_parts(ptr, cc_repr.1); + let cc: &str = std::str::from_utf8_unchecked(bytes); + println!("{cc}"); +}; + +// Rust representation (bytes) to string slice +unsafe { + let ptr = rust_repr.0.as_ptr(); + let bytes = std::slice::from_raw_parts(ptr, rust_repr.1); + let rust: &str = std::str::from_utf8_unchecked(bytes); + println!("{rust}"); +}; +``` + +Aside: Rust has a c-prefixed string literal. It appends a null byte at the end, +e.g. `c"Rust" == b"Rust\0"`. + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences/rust-and-c.md b/src/unsafe-deep-dive/ffi/language-differences/rust-and-c.md new file mode 100644 index 000000000000..6babeb829444 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences/rust-and-c.md @@ -0,0 +1,35 @@ +--- +minutes: 3 +--- + +# Rust ↔ C + +| Concern | Rust | C | +| --------------- | ------------------------------------- | --------------------------------------------------- | +| **Errors** | `Result`, `Option` | Magic return values, out-parameters, global `errno` | +| **Strings** | `&str`/`String` (UTF-8, length-known) | Null-terminated `char*`, encoding undefined | +| **Nullability** | Explicit via `Option` | Any pointer may be null | +| **Ownership** | Affine types, lifetimes | Conventions | +| **Callbacks** | `Fn`/`FnMut`/`FnOnce` closures | Function pointer + `void* userdata` | +| **Panics** | Stack unwinding (or abort) | Abort | + +
+ +Errors: Must convert `Result` to abide by C conventions; easy to forget to check +errors on C side. + +Strings: Conversion cost; null bytes in Rust strings cause truncation; UTF-8 +validation on ingress + +Nullability: Every pointer from C must be checked to create an +`Option>`, implying unsafe blocks or runtime cost + +Ownership: Must document and enforce object lifetimes manually + +Callbacks: Must decompose closures into fn pointer + context; lifetime of +context is manual + +Panics: Panic across FFI boundary is undefined behavior; must catch at boundary +with `catch_unwind` + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences/rust-and-cpp.md b/src/unsafe-deep-dive/ffi/language-differences/rust-and-cpp.md new file mode 100644 index 000000000000..8e93438133fa --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences/rust-and-cpp.md @@ -0,0 +1,44 @@ +--- +minutes: 3 +--- + +# Rust ↔ C++ + +| Concern | Rust | C++ | +| -------------------------- | ---------------------------------------- | --------------------------------------------------------------- | +| **Trivial relocatability** | All moves are `memcpy` | Self-referential types, move constructors can have side effects | +| **Destruction safety** | `Drop::drop()` on original location only | Destructor may run on moved-from objects | +| **Exception safety** | Panics (abort or unwind) | Exceptions (unwind) | +| **ABI stability** | Explicitly unstable | Vendor-specific | + +
+ +Even if it were possible to avoid interop via C, there are still some areas of +the languages that impact FFI: + +_Trivial relocatability_ + +Cannot safely move C++ objects on Rust side; must pin or keep in C++ heap. + +In Rust, object movement, which occurs during assignment or by being passed by +value, always copies values bit by bit. + +C++ allows users to define their own semantics by allowing them to overload the +assignment operator and create move and copy constructors. + +This impacts interop because self-referential types become natural in +high-performance C++. Custom constructors can uphold safety invariants even when +the object moves its position in memory. + +Objects with the same semantics are impossible to define in Rust. + +_Destruction safety_ + +Moved-from C++ object semantics don't map; must prevent Rust from "moving" C++ +types + +_Exception safety_ + +Neither can cross into the other safely; both must catch at boundary + +
diff --git a/src/unsafe-deep-dive/ffi/language-differences/semantics.md b/src/unsafe-deep-dive/ffi/language-differences/semantics.md new file mode 100644 index 000000000000..20cd8d73b688 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-differences/semantics.md @@ -0,0 +1,52 @@ +--- +minutes: 15 +--- + +# Different semantics + + + +```rust,editable,ignore +use std::ffi::{CStr, c_char}; +use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; + +unsafe extern "C" { + /// Create a formatted time based on timestamp `t`. + fn ctime(t: *const libc::time_t) -> *const c_char; +} + +fn now_formatted() -> Result { + let now = SystemTime::now().duration_since(UNIX_EPOCH)?; + let seconds = now.as_secs() as i64; + + // SAFETY: `seconds` is generated by the system clock and will not cause + // overflow + let ptr = unsafe { ctime(&seconds) }; + + // SAFETY: ctime returns a pointer to a preallocated (non-null) buffer + let ptr = unsafe { CStr::from_ptr(ptr) }; + + // SAFETY: ctime uses valid UTF-8 + let fmt = ptr.to_str().unwrap(); + + Ok(fmt.trim_end().to_string()) +} + +fn main() { + let t = now_formatted(); + println!("{t:?}"); +} +``` + +
+ +Some constructs that other languages allow cannot be expressed in the Rust +language. + +The `ctime` function modifies an internal buffer shared between calls. This +cannot be represented as Rust’s lifetimes. + +- `'static` does not apply, as the semantics are different +- `'a` does not apply, as the buffer outlives each call + +
diff --git a/src/unsafe-deep-dive/ffi/language-interop.md b/src/unsafe-deep-dive/ffi/language-interop.md new file mode 100644 index 000000000000..fd2f62d013c3 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/language-interop.md @@ -0,0 +1,32 @@ +# Language Interop + +Ideal scenario: + +```bob +╭────────────╮ ╭────────────╮ +│ │ │ │ +│ │ <--------------------------------------> │ │ +│ │ │ │ +╰────────────╯ ╰────────────╯ + Rust "C++" +``` + +
+ +This section of the course covers interacting with Rust and external languages +via its foreign-function interface (FFI), with a special focus on +interoperability with C++. + +Ideally, users of Rust and the external language (in this case C++) could call +each others’ methods directly. + +This ideal scenario is very difficult to achieve: + +Different languages have different semantics and mapping between them implies +trade-offs Neither Rust nor C++ offer ABI stability[^1], making it difficult to +build from a stable foundation + +[^1]: Some C++ compiler vendors provide support for ABI stability within their + toolchain. + +
diff --git a/src/unsafe-deep-dive/ffi/rand.md b/src/unsafe-deep-dive/ffi/rand.md new file mode 100644 index 000000000000..e2473e8d9146 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/rand.md @@ -0,0 +1,50 @@ +--- +minutes: 15 +--- + +# Wrapping `srand(3)` and `rand(3)` + + + +```rust,editable,ignore +use libc::{rand, srand}; + +// unsafe extern "C" { +// /// Seed the rng +// fn srand(seed: std::ffi::c_uint); + +// fn rand() -> std::ffi::c_int; +// } + +fn main() { + unsafe { srand(12345) }; + + let a = unsafe { rand() as i32 }; + println!("{a:?}"); +} +``` + +
+ +This slide attempts to demonstrate that it is very easy for wrappers to trigger +undefined behavior if they are written incorrectly. We’ll see how easy it is to +trigger type safety problems. + +Explain that `rand` and `srand` functions are provided by the C standard library +(libc). + +Explain that the functions are exported by the libc crate, but we can also write +an FFI wrapper for them manually. + +Show calling the functions from the exported. + +Code compiles because libc is linked to Rust programs by default. + +Explain that Rust will trust you if you use the wrong type(s). + +Modify `fn rand() -> std::ffi::c_int;` to return `char`. + +Avoiding type safety issues is a reason for using tools for generating wrappers, +rather than doing it by hand. + +
diff --git a/src/unsafe-deep-dive/ffi/strategies.md b/src/unsafe-deep-dive/ffi/strategies.md new file mode 100644 index 000000000000..5b62698c38f2 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/strategies.md @@ -0,0 +1,63 @@ +--- +minutes: 5 +--- + +# Strategies of interop + + + +Sharing data structures and symbols directly is very difficult: + +```bob +╭────────────╮ ╭────────────╮ +│ │ │ │ +│ │ <--------------------------------------> │ │ +│ │ │ │ +╰────────────╯ ╰────────────╯ + Rust "C++" +``` + +FFI through the C ABI is much more feasible: + +```bob +╭────────────╮ ╭───╮ ╭───╮ ╭────────────╮ +│ │ │ │ │ │ │ │ +│ │ <-----> │ │ <~~~~~~~> │ │ <------> │ │ +│ │ │ │ │ │ │ │ +╰────────────╯ ╰───╯ ╰───╯ ╰────────────╯ + Rust C C "C++" +``` + +Other strategies: + +- Distributed system (RPC) +- Custom ABI (i.e. WebAssembly Interface Types) + +
+ +_High-fidelity interop_ + +The ideal scenario is currently experimental. + +Two projects exploring this are [crubit](https://github.com/google/crubit) and +[Zngur](https://hkalbasi.github.io/zngur/). The first provides glue code on each +side for enabling compatible types to work seamlessly across domains. The second +relies on dynamic dispatch and imports C++ objects into Rust as trait objects. + +_Low-fidelity interop_ work through a C API + +The typical strategy for interop is to use the C language as the interface. C is +a lossy codec. This strategy typically results in complicated code on both +sides. + +_Other strategies_ are less viable in a zero cost environment. + +_Distributed systems_ impose runtime costs. + +They incur significant overhead as calling a method in a foreign library incurs +a round trip of serialization/transport/deserialization. Generally speaking, a +transparent RPC is not a good idea. There’s network in the middle. + +_Custom ABI_, such as wasm require a runtime or significant implementation cost. + +
diff --git a/src/unsafe-deep-dive/ffi/type-safety.md b/src/unsafe-deep-dive/ffi/type-safety.md new file mode 100644 index 000000000000..910f799a4244 --- /dev/null +++ b/src/unsafe-deep-dive/ffi/type-safety.md @@ -0,0 +1 @@ +# Consideration: Type Safety diff --git a/src/unsafe-deep-dive/foundations.md b/src/unsafe-deep-dive/foundations.md deleted file mode 100644 index b81d9de12c1e..000000000000 --- a/src/unsafe-deep-dive/foundations.md +++ /dev/null @@ -1,5 +0,0 @@ -# Foundations - -Some fundamental concepts and terms. - -{{%segment outline}} diff --git a/src/unsafe-deep-dive/foundations/actions-might-not-be.md b/src/unsafe-deep-dive/foundations/actions-might-not-be.md deleted file mode 100644 index fd9f60d790e6..000000000000 --- a/src/unsafe-deep-dive/foundations/actions-might-not-be.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -minutes: 2 ---- - -# ... but actions on them might not be - -```rust -fn main() { - let n: i64 = 12345; - let safe = &n as *const _; - println!("{safe:p}"); -} -``` - -
- -Modify the example to de-reference `safe` without an `unsafe` block. - -
diff --git a/src/unsafe-deep-dive/foundations/data-structures-are-safe.md b/src/unsafe-deep-dive/foundations/data-structures-are-safe.md deleted file mode 100644 index e298edaa09df..000000000000 --- a/src/unsafe-deep-dive/foundations/data-structures-are-safe.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -minutes: 2 ---- - -# Data structures are safe ... - -Data structures are inert. They cannot do any harm by themselves. - -Safe Rust code can create raw pointers: - -```rust -fn main() { - let n: i64 = 12345; - let safe = &raw const n; - println!("{safe:p}"); -} -``` - -
- -Consider a raw pointer to an integer, i.e., the value `safe` is the raw pointer -type `*const i64`. Raw pointers can be out-of-bounds, misaligned, or be null. -But the unsafe keyword is not required when creating them. - -
diff --git a/src/unsafe-deep-dive/foundations/less-powerful.md b/src/unsafe-deep-dive/foundations/less-powerful.md deleted file mode 100644 index cc2d795b8d09..000000000000 --- a/src/unsafe-deep-dive/foundations/less-powerful.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -minutes: 10 ---- - -# Less powerful than it seems - -The `unsafe` keyword does not allow you to break Rust. - -```rust,ignore -use std::mem::transmute; - -let orig = b"RUST"; -let n: i32 = unsafe { transmute(orig) }; - -println!("{n}") -``` - -
- -## Suggested outline - -- Request that someone explains what `std::mem::transmute` does -- Discuss why it doesn't compile -- Fix the code - -## Expected compiler output - -```ignore - Compiling playground v0.0.1 (/playground) -error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - --> src/main.rs:5:27 - | -5 | let n: i32 = unsafe { transmute(orig) }; - | ^^^^^^^^^ - | - = note: source type: `&[u8; 4]` (64 bits) - = note: target type: `i32` (32 bits) -``` - -## Suggested change - -```diff -- let n: i32 = unsafe { transmute(orig) }; -+ let n: i64 = unsafe { transmute(orig) }; -``` - -## Notes on less familiar Rust - -- the `b` prefix on a string literal marks it as byte slice (`&[u8]`) rather - than a string slice (`&str`) - -
diff --git a/src/unsafe-deep-dive/foundations/what-is-unsafe.md b/src/unsafe-deep-dive/foundations/what-is-unsafe.md deleted file mode 100644 index 8af083ac3fac..000000000000 --- a/src/unsafe-deep-dive/foundations/what-is-unsafe.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -minutes: 6 ---- - -# What is “unsafety”? - -Unsafe Rust is a superset of Safe Rust. - -Let's create a list of things that are enabled by the `unsafe` keyword. - -
- -## Definitions from authoritative docs: - -From the [unsafe keyword's documentation](): - -> Code or interfaces whose memory safety cannot be verified by the type system. -> -> ... -> -> Here are the abilities Unsafe Rust has in addition to Safe Rust: -> -> - Dereference raw pointers -> - Implement unsafe traits -> - Call unsafe functions -> - Mutate statics (including external ones) -> - Access fields of unions - -From the [reference](https://doc.rust-lang.org/reference/unsafety.html) - -> The following language level features cannot be used in the safe subset of -> Rust: -> -> - Dereferencing a raw pointer. -> - Reading or writing a mutable or external static variable. -> - Accessing a field of a union, other than to assign to it. -> - Calling an unsafe function (including an intrinsic or foreign function). -> - Calling a safe function marked with a target_feature from a function that -> does not have a target_feature attribute enabling the same features (see -> attributes.codegen.target_feature.safety-restrictions). -> - Implementing an unsafe trait. -> - Declaring an extern block. -> - Applying an unsafe attribute to an item. - -## Group exercise - -> You may have a group of learners who are not familiar with each other yet. -> This is a way for you to gather some data about their confidence levels and -> the psychological safety that they're feeling. - -### Part 1: Informal definition - -> Use this to gauge the confidence level of the group. If they are uncertain, -> then tailor the next section to be more directed. - -Ask the class: **By raising your hand, indicate if you would feel comfortable -defining unsafe?** - -If anyone's feeling confident, allow them to try to explain. - -### Part 2: Evidence gathering - -Ask the class to spend 3-5 minutes. - -- Find a use of the unsafe keyword. What contract/invariant/pre-condition is - being established or satisfied? -- Write down terms that need to be defined (unsafe, memory safety, soundness, - undefined behavior) - -### Part 3: Write a working definition - -### Part 4: Remarks - -Mention that we'll be reviewing our definition at the end of the day. - -## Note: Avoid detailed discussion about precise semantics of memory safety - -It's possible that the group will slide into a discussion about the precise -semantics of what memory safety actually is and how define pointer validity. -This isn't a productive line of discussion. It can undermine confidence in less -experienced learners. - -Perhaps refer people who wish to discuss this to the discussion within the -official [documentation for pointer types] (excerpt below) as a place for -further research. - -> Many functions in [this module] take raw pointers as arguments and read from -> or write to them. For this to be safe, these pointers must be _valid_ for the -> given access. -> -> ... -> -> The precise rules for validity are not determined yet. - -[this module]: https://doc.rust-lang.org/std/ptr/index.html -[documentation for pointer types]: https://doc.rust-lang.org/std/ptr/index.html#safety - -
diff --git a/src/unsafe-deep-dive/foundations/when-is-unsafe-used.md b/src/unsafe-deep-dive/foundations/when-is-unsafe-used.md deleted file mode 100644 index 955c17beb593..000000000000 --- a/src/unsafe-deep-dive/foundations/when-is-unsafe-used.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -minutes: 2 ---- - -# When is unsafe used? - -The unsafe keyword indicates that the programmer is responsible for upholding -Rust's safety guarantees. - -The keyword has two roles: - -- define pre-conditions that must be satisfied -- assert to the compiler (= promise) that those defined pre-conditions are - satisfied - -## Further references - -- [The unsafe keyword chapter of the Rust Reference](https://doc.rust-lang.org/reference/unsafe-keyword.html) - -
- -Places where pre-conditions can be defined (Role 1) - -- [unsafe functions] (`unsafe fn foo() { ... }`). Example: `get_unchecked` - method on slices, which requires callers to verify that the index is - in-bounds. -- unsafe traits (`unsafe trait`). Examples: [`Send`] and [`Sync`] marker traits - in the standard library. - -Places where pre-conditions must be satisfied (Role 2) - -- unsafe blocks (`unafe { ... }`) -- implementing unsafe traits (`unsafe impl`) -- access external items (`unsafe extern`) -- adding - [unsafe attributes](https://doc.rust-lang.org/reference/attributes.html) o an - item. Examples: [`export_name`], [`link_section`] and [`no_mangle`]. Usage: - `#[unsafe(no_mangle)]` - -[unsafe functions]: https://doc.rust-lang.org/reference/unsafe-keyword.html#unsafe-functions-unsafe-fn -[unsafe traits]: https://doc.rust-lang.org/reference/unsafe-keyword.html#unsafe-traits-unsafe-trait -[`export_name`]: https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute -[`link_section`]: https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute -[`no_mangle`]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute -[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html -[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html - -
diff --git a/src/unsafe-deep-dive/initialization.md b/src/unsafe-deep-dive/initialization.md new file mode 100644 index 000000000000..7597c9752195 --- /dev/null +++ b/src/unsafe-deep-dive/initialization.md @@ -0,0 +1 @@ +# Initialization diff --git a/src/unsafe-deep-dive/initialization/how-to-initialize-memory.md b/src/unsafe-deep-dive/initialization/how-to-initialize-memory.md new file mode 100644 index 000000000000..2250e616012c --- /dev/null +++ b/src/unsafe-deep-dive/initialization/how-to-initialize-memory.md @@ -0,0 +1,44 @@ +--- +minutes: 8 +--- + +# How to Initialize Memory + +Steps: + +1. Create `MaybeUninit` +2. Write a value to it +3. Notify Rust that the memory is initialized + +```rust,editable +use std::mem::MaybeUninit; + +fn main() { + // Step 1: create MaybeUninit + let mut uninit = MaybeUninit::uninit(); + + // Step 2: write a valid value to the memory + uninit.write(1); + + // Step 3: inform the type system that the memory location is valid + let init = unsafe { uninit.assume_init() }; + + println!("{init}"); +} +``` + +
+ +“To work with uninitialized memory, follow this general workflow: create, write, +confirm.” + +“1. Create `MaybeUninit`. The `::uninit()` constructor is the most general +purpose one, but there are others which perform a write as well.” + +“2. Write a value of T. Notice that this is available from safe Rust. Staying in +safe Rust is useful because you must ensure that the value you write is valid.” + +“3. Confirm to the type system that the memory is now initialized with the +`.assume_init()` method.” + +
diff --git a/src/unsafe-deep-dive/initialization/maybeuninit.md b/src/unsafe-deep-dive/initialization/maybeuninit.md new file mode 100644 index 000000000000..44ef0ef09a13 --- /dev/null +++ b/src/unsafe-deep-dive/initialization/maybeuninit.md @@ -0,0 +1,33 @@ +--- +minutes: 8 +--- + +# MaybeUninit + +`MaybeUninit` allows Rust to refer to uninitialized memory. + +```rust,editable +use std::mem::MaybeUninit; + +fn main() { + let uninit = MaybeUninit::<&i32>::uninit(); + println!("{uninit:?}"); +} +``` + +
+ +“Safe Rust is unable to refer to data that’s potentially uninitialized” + +“Yet, all data arrives at the program as uninitialized.” + +“Therefore, we need some bridge in the type system to allow memory to +transition. `MaybeUninit` is that type.” + +“`MaybeUninit` is very similar to the `Option` type, although its +semantics are very different. The equivalent of `Option::None` for +`MaybeUninit` is uninitialized memory, which is only safe to write to.” + +“Reading from memory that may be uninitialized is extremely dangerous.” + +
diff --git a/src/unsafe-deep-dive/initialization/maybeuninit/arrays.md b/src/unsafe-deep-dive/initialization/maybeuninit/arrays.md new file mode 100644 index 000000000000..f8b720849d01 --- /dev/null +++ b/src/unsafe-deep-dive/initialization/maybeuninit/arrays.md @@ -0,0 +1,73 @@ +--- +minutes: 8 +--- + +# MaybeUninit and arrays + +```rust +use std::mem::MaybeUninit; +use std::ptr; + +fn main() { + let input = b"RUST"; + + let mut buf = [const { MaybeUninit::::uninit() }; 2048]; + + // Initialize elements by writing values to the memory + for (i, input_byte) in input.iter().enumerate() { + unsafe { + let dst = buf.as_mut_ptr().add(i); + ptr::write((*dst).as_mut_ptr(), *input_byte); + } + } + + // When a portion of an array is initialized, one can + // use unsafe to isolate it + let ptr_to_init_subslice = buf.as_ptr() as *const u8; + let init = + unsafe { std::slice::from_raw_parts(ptr_to_init_subslice, input.len()) }; + let text = std::str::from_utf8(init).unwrap(); + println!("{text}"); + + // We must manually drop the initialized elements + for element in &mut buf[0..input.len()] { + unsafe { + element.assume_init_drop(); + } + } +} +``` + +
+ +To create an array of uninitialized memory, the `::uninit()` constructor can be +used within a `const` context. + +Use `ptr::write` to initialize values as per normal. + +`.assume_init()` does not work as easily for arrays. It requires every value to +be initialized, which may not occur when reusing a buffer. This example uses a +pointer to isolate the initialized bytes to create a string slice. + +When creating a sub-slice of a partially-initialized array, be careful with +ownership and correctly implementing drop. Reminder: `MaybeUninit` will not +call drop on its `T`. + +`MaybeUninit<[u8;2048]>` is distinct from `[MaybeUninit::; 2048]` the +difference between an array of uninitialized memory and an array that contains +uninitialized elements. + +- `MaybeUninit<[u8;2048]>` is "all or nothing". You either fully initialize the + whole array and then call `assume_init`, or you must keep it as + `MaybeUninit<[u8; 2048]>` and avoid touching it as `[u8; 2048]`. +- `[MaybeUninit; 2048]` lets you initialize elements one at a time, then + take a sub-slice of just the initialized prefix and treat it as `[u8]` via + `std::slice::from_raw_parts`. +- `slice_assume_init_ref` is safe only when every element in the slice is + initialized. For this example, we only pass `&buf[..input.len()]` after + writing exactly those bytes. +- When `T` needs drop, you must manually call `assume_init_drop()` for the + initialized elements. Skipping this leaks memory. However, calling it on an + uninitialized element is undefined behavior. + +
diff --git a/src/unsafe-deep-dive/initialization/maybeuninit/write-vs-assignment.md b/src/unsafe-deep-dive/initialization/maybeuninit/write-vs-assignment.md new file mode 100644 index 000000000000..6f39b8e0c2be --- /dev/null +++ b/src/unsafe-deep-dive/initialization/maybeuninit/write-vs-assignment.md @@ -0,0 +1,41 @@ +# MaybeUninit.write() vs assignment + +```rust +use std::mem::MaybeUninit; + +fn main() { + let mut buf = MaybeUninit::::uninit(); + + // Initialize + buf.write(String::from("Hello, Rust!")); + + // Overwrite + buf.write(String::from("Hi again")); + + // Assignment replaces the whole MaybeUninit value. + buf = MaybeUninit::new(String::from("Goodbye")); + + // Ensure inner value is dropped + let _ = unsafe { buf.assume_init() }; +} +``` + +
+ +Replacing inner values can cause memory leaks because the drop semantics differ +from most types. `MaybeUninit` does not call the destructor on its `T`. + +`MaybeUninit::write()` uses `ptr::write`: it initializes the memory in place +without reading or dropping the old contents. That is exactly what you want when +the memory might be uninitialized, but it also means you will leak if there was +already a live value there. + +Assignment, e.g. `buf = MaybeUninit::new(value)`, replaces the whole +`MaybeUninit`. The old `MaybeUninit` is moved and then dropped, but +`MaybeUninit` has no destructor for `T`, so the inner value is not dropped. If +the old slot held an initialized value, it is leaked just like with `write()`. + +If you need normal drop behavior, you need to tell Rust that the value is +initialized with `assume_init` or one of the related methods. + +
diff --git a/src/unsafe-deep-dive/initialization/maybeuninit/zeroed-method.md b/src/unsafe-deep-dive/initialization/maybeuninit/zeroed-method.md new file mode 100644 index 000000000000..bf75fa0829bb --- /dev/null +++ b/src/unsafe-deep-dive/initialization/maybeuninit/zeroed-method.md @@ -0,0 +1,30 @@ +# MaybeUninit::zeroed() + +```rust,editable +use std::mem::{MaybeUninit, transmute}; + +fn main() { + let mut x = [const { MaybeUninit::::zeroed() }; 10]; + + x[6].write(7); + + // SAFETY: All values of `x` have been written to + let x: [u32; 10] = unsafe { transmute(x) }; + println!("{x:?}") +} +``` + +
+ +“MaybeUninit::zeroed() is an alternative constructor to +MaybeUninit::uninit(). It instructs the compiler to fill the bits of T with +zeros.” + +Q: “Although the memory has been written to, the type remains `MaybeUninit`. +Can anyone think of why?” + +A: Some types require their values to be non-zero or non-null. The classic case +is references, but this applies to many other types as well. Consider +`NonZeroUsize` integer type and others in its family. + +
diff --git a/src/unsafe-deep-dive/initialization/partial-initialization.md b/src/unsafe-deep-dive/initialization/partial-initialization.md new file mode 100644 index 000000000000..f44d85288edd --- /dev/null +++ b/src/unsafe-deep-dive/initialization/partial-initialization.md @@ -0,0 +1,48 @@ +# Partial Initialization + +```rust +use std::mem::MaybeUninit; + +fn main() { + // let mut buf = [0u8; 2048]; + let mut buf = [const { MaybeUninit::::uninit() }; 2048]; + + let external_data = b"Hello, Rust!"; + let len = external_data.len(); + + for (dest, src) in buf.iter_mut().zip(external_data) { + dest.write(*src); + } + + // SAFETY: We initialized exactly 'len' bytes of `buf` with UTF-8 text + let text: &str = unsafe { + let ptr: *const u8 = buf.as_ptr().cast::(); + let init: &[u8] = std::slice::from_raw_parts(ptr, len); + std::str::from_utf8_unchecked(init) + }; + + println!("{text}"); +} +``` + +
+ +This code simulates receiving data from some external source. + +When reading bytes from an external source into a buffer, you typically don't +know how many bytes you'll receive. Using `MaybeUninit` lets you allocate the +buffer once without paying for a redundant initialization pass. + +If we were to create the array with the standard syntax (`buf = [0u8; 2048]`), +the whole buffer will be flushed with zeroes. `MaybeUninit` tells the +compiler to reserve space, but don't touch the memory yet. + +Q: Which part of the code snippet is performing a similar role to +`.assume_init()`? A: The pointer cast and the implicit read. + +We cannot call `assume_init()` on the whole array. That would be unsound because +most elements remain uninitialized. Instead, we cast the pointer from +`*const MaybeUninit` to `*const u8` and build a slice covering only the +initialised portion. + +
diff --git a/src/unsafe-deep-dive/introduction.md b/src/unsafe-deep-dive/introduction.md new file mode 100644 index 000000000000..0499fa01d783 --- /dev/null +++ b/src/unsafe-deep-dive/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +We'll start our course by creating a shared understanding of what Unsafe Rust is +and what the `unsafe` keyword does. + +## Outline + +{{%segment outline}} diff --git a/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust.md b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust.md new file mode 100644 index 000000000000..9c29271f0e86 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust.md @@ -0,0 +1,5 @@ +# Characteristics of unsafe + +- [Dangerous](characteristics-of-unsafe-rust/dangerous.md) +- [Sometimes necessary](characteristics-of-unsafe-rust/sometimes-necessary.md) +- [Sometimes useful](characteristics-of-unsafe-rust/sometimes-useful.md) diff --git a/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/dangerous.md b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/dangerous.md new file mode 100644 index 000000000000..f86f7381391c --- /dev/null +++ b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/dangerous.md @@ -0,0 +1,25 @@ +--- +minutes: 2 +--- + +# Unsafe is dangerous + +> “Use-after-free (UAF), integer overflows, and out of bounds (OOB) reads/writes +> comprise 90% of vulnerabilities with OOB being the most common.” +> +> --- **Jeff Vander Stoep and Chong Zang**, Google. +> "[Queue the Hardening Enhancements][blog]" + +[blog]: https://security.googleblog.com/2019/05/queue-hardening-enhancements.html + +
+ +“The software industry has gathered lots of evidence that unsafe code is +difficult to write correctly and creates very serious problems.” + +“The issues in this list are eliminated by Rust. The unsafe keyword lets them +back into your source code.” + +“Be careful.” + +
diff --git a/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-necessary.md b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-necessary.md new file mode 100644 index 000000000000..a39d2ec3e354 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-necessary.md @@ -0,0 +1,48 @@ +--- +minutes: 5 +--- + +# Unsafe is sometimes necessary + +The Rust compiler can only enforce its rules for code that it has compiled. + +```rust,editable,ignore +fn main() { + let pid = unsafe { libc::getpid() }; + println!("{pid}"); +} +``` + +
+ +“There are some activities that _require_ unsafe. + +“The Rust compiler cannot verify that external functions comply with Rust's +memory guarantees. Therefore, invoking external functions requires an unsafe +block.” + +Optional: + +“Working with the external environment often involves sharing memory. The +interface that computers provide is a memory address (a pointer).” + +“Here's an example that asks the Linux kernel to write to memory that we +control: + +```rust,ignore +fn main() { + let mut buf = [0u8; 8]; + let ptr = buf.as_mut_ptr() as *mut libc::c_void; + + let status = unsafe { libc::getrandom(ptr, buf.len(), 0) }; + if status > 0 { + println!("{buf:?}"); + } +} +``` + +“This FFI call reaches into the operating system to fill our buffer (`buf`). As +well as calling an external function, we must mark the boundary as `unsafe` +because the compiler cannot verify how the OS touches that memory.” + +
diff --git a/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-useful.md b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-useful.md new file mode 100644 index 000000000000..8a2c2c707929 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/characteristics-of-unsafe-rust/sometimes-useful.md @@ -0,0 +1,48 @@ +--- +minutes: 5 +--- + +# Unsafe is sometimes useful + +Your code can go faster! + +```rust,editable +fn iter_sum(xs: &[u64]) -> u64 { + xs.iter().sum() +} + +fn fast_sum(xs: &[u64]) -> u64 { + let mut acc = 0; + let mut i = 0; + unsafe { + while i < xs.len() { + acc += *xs.get_unchecked(i); + i += 1; + } + } + acc +} + +fn main() { + let data: Vec<_> = (0..1_000_000).collect(); + + let baseline = iter_sum(&data); + let unchecked = fast_sum(&data); + + assert_eq!(baseline, unchecked); +} +``` + +
+ +Code using `unsafe` _might_ be faster. + +`fast_sum()` skips skips bounds checks. However, benchmarking is necessary to +validate performance claims. For cases like this, Rust's iterators can usually +elide bounds checks anyway. + +Optional: [show identical generated assembly][godbolt] for the two functions. + +[godbolt]: https://rust.godbolt.org/z/d48v1Y5aj + +
diff --git a/src/unsafe-deep-dive/introduction/defining-unsafe-rust.md b/src/unsafe-deep-dive/introduction/defining-unsafe-rust.md new file mode 100644 index 000000000000..b4519d8f905c --- /dev/null +++ b/src/unsafe-deep-dive/introduction/defining-unsafe-rust.md @@ -0,0 +1,43 @@ +--- +minutes: 5 +--- + +# Defining Unsafe Rust + + + +```bob +╭───────────────────────────────────────────────────────────╮ +│╭─────────────────────────────────────────────────────────╮│ +││ ││ +││ Safe ││ +││ Rust ││ +││ ││ +││ ││ +│╰─────────╮ ││ +│ │ ││ +│ Unsafe │ ││ +│ Rust │ ││ +│ ╰───────────────────────────────────────────────╯│ +╰───────────────────────────────────────────────────────────╯ +``` + +
+ +“Unsafe Rust is a superset of Safe Rust.” + +“Unsafe Rust adds extra capabilities, such as allowing you to dereference raw +pointers and call functions that can break Rust’s safety guarantees if called +incorrectly.” + +“These extra capabilities are referred to as _unsafe operations_.” + +“Unsafe operations provide the foundation that the Rust standard library is +built on. For example, without the ability to dereference a raw pointer, it +would be impossible to implement `Vec` or `Box`.” + +“The compiler will still assist you while writing Unsafe Rust. Borrow checking +and type safety still apply. Unsafe operations have their own rules, which we’ll +learn about in this class.” + +
diff --git a/src/unsafe-deep-dive/introduction/definition.md b/src/unsafe-deep-dive/introduction/definition.md new file mode 100644 index 000000000000..308e2c8cc421 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/definition.md @@ -0,0 +1,60 @@ +--- +minutes: 5 +--- + +# Defining Unsafe Rust + + + +```bob +╭───────────────────────────────────────────────────────────╮ +│╭─────────────────────────────────────────────────────────╮│ +││ ││ +││ Safe ││ +││ Rust ││ +││ ││ +││ ││ +│╰─────────╮ ││ +│ │ ││ +│ Unsafe │ ││ +│ Rust │ ││ +│ ╰───────────────────────────────────────────────╯│ +╰───────────────────────────────────────────────────────────╯ +``` + +
+ +“Unsafe Rust is a superset of Safe Rust.” + +“Unsafe Rust adds extra capabilities, such as allowing you to dereference raw +pointers and call functions that can break Rust’s safety guarantees if called +incorrectly.” + +“These extra capabilities are referred to as _unsafe operations_.” + +“Unsafe operations provide the foundation that the Rust standard library is +built on. For example, without the ability to dereference a raw pointer, it +would be impossible to implement `Vec` or `Box`.” + +“The compiler will still assist you while writing Unsafe Rust. Borrow checking +and type safety still apply. Unsafe operations have their own rules, which we’ll +learn about in this class.” + +The unsafe operations from the [Rust Reference] (Avoid spending too much time): + +> The following language level features cannot be used in the safe subset of +> Rust: +> +> - Dereferencing a raw pointer. +> - Reading or writing a mutable or unsafe external static variable. +> - Accessing a field of a union, other than to assign to it. +> - Calling an `unsafe` function. +> - Calling a safe function marked with a `` from a function +> that does not have a `` attribute enabling the same features +> - Implementing an unsafe trait. +> - Declaring an extern block. +> - Applying an unsafe attribute to an item. + +[Rust Reference]: https://doc.rust-lang.org/reference/unsafety.html + +
diff --git a/src/unsafe-deep-dive/introduction/impact-on-workflow.md b/src/unsafe-deep-dive/introduction/impact-on-workflow.md new file mode 100644 index 000000000000..7f48138a126a --- /dev/null +++ b/src/unsafe-deep-dive/introduction/impact-on-workflow.md @@ -0,0 +1,33 @@ +--- +minutes: 5 +--- + +# Impact on workflow + +While writing code + +- Verify that you understand the preconditions of any `unsafe` functions/traits +- Check that the preconditions are satisfied +- Document your reasoning in safety comments + +Enhanced code review + +- Self-review → peer reviewer → unsafe Rust expert (when needed) +- Escalate to a person who is comfortable with your code and reasoning + +
+ +“The unsafe keyword places more responsibility on the programmer, therefore it +requires a stronger development workflow. + +“This class assumes a specific software development workflow where code review +is mandatory, and where the author and primary reviewer have access to an unsafe +Rust expert.” + +“The author and primary reviewer will verify simple unsafe Rust code themselves, +and punt to an unsafe expert when necessary.” + +“There are only a few unsafe Rust experts, and they are very busy, so we need to +optimally use their time.” + +
diff --git a/src/unsafe-deep-dive/introduction/may_overflow.md b/src/unsafe-deep-dive/introduction/may_overflow.md new file mode 100644 index 000000000000..1151bc68fd14 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/may_overflow.md @@ -0,0 +1,51 @@ +--- +minutes: 10 +--- + +# Example: may_overflow function + +```rust,should_panic,editable +/// Adds 2^31 - 1 to negative numbers. +unsafe fn may_overflow(a: i32) -> i32 { + a + i32::MAX +} + +fn main() { + let x = unsafe { may_overflow(123) }; + println!("{x}"); +} +``` + +
+ +“The `unsafe` keyword may have a subtly different meaning than what some people +assume.” + +“The code author believes that the code is correct. In principle, the code is +safe.” + +“In this toy example, the `may_overflow` function is only intended to be called +with negative numbers. + +Ask learners if they can explain why `may_overflow` requires the unsafe keyword. + +“In case you’re unsure what the problem is, let’s pause briefly to explain. An +`i32` only has 31 bits available for positive numbers. When an operation +produces a result that requires more than 31 bits, then the program is put into +an invalid state. And it’s not just a numerical problem. Compilers optimize code +on the basis that invalid states are impossible. This causes code paths to be +deleted, producing erratic runtime behavior while also introducing security +vulnerabilities. + +Compile and run the code, producing a panic. Then run the example in the +playground to run under `--release` mode to trigger UB. + +“This code can be used correctly, however, improper usage is highly dangerous.” + +“And it's impossible for the compiler to verify that the usage is correct.” + +This is what we mean when we say that the `unsafe` keyword marks the location +where responsibility for memory safety shifts from the compiler to the +programmer. + +
diff --git a/src/unsafe-deep-dive/introduction/purpose.md b/src/unsafe-deep-dive/introduction/purpose.md new file mode 100644 index 000000000000..6ca89529d790 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/purpose.md @@ -0,0 +1,26 @@ +--- +minutes: 5 +--- + +# Why the unsafe keyword exists + +- Rust ensures safety +- But there are limits to what the compiler can do +- The unsafe keyword allows programmers to assume responsibility for Rust’s + rules + +
+ +“A fundamental goal of Rust is to ensure memory safety.” + +“But, there are limits. Some safety considerations cannot be expressed in a +programming language. Even if they could be, there are limits to what the Rust +compiler can control.” + +“The `unsafe` keyword shifts the burden of upholding Rust’s rules from the +compiler to the programmer.” + +“When you see the `unsafe` keyword, you are seeing responsibility shift from the +compiler to the programmer. + +
diff --git a/src/unsafe-deep-dive/introduction/responsibility-shift.md b/src/unsafe-deep-dive/introduction/responsibility-shift.md new file mode 100644 index 000000000000..59da5ff973c0 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/responsibility-shift.md @@ -0,0 +1,32 @@ +--- +minutes: 3 +--- + +# Unsafe keyword shifts responsibility + +| | Is Memory Safe? | Responsibility for Memory Safety | +| :---------- | :-------------: | :------------------------------- | +| Safe Rust | Yes | Compiler | +| Unsafe Rust | Yes | Programmer | + +
+ +Who has responsibility for memory safety? + +- Safe Rust → compiler +- Unsafe Rust → programmer + +“While writing safe Rust, you cannot create memory safety problems. The compiler +will ensure that a program with mistakes will not build.” + +“The `unsafe` keyword shifts responsibility for maintaining memory safety from +the compiler to programmers. It signals that there are preconditions that must +be satisfied. + +“To uphold that responsibility, programmers must ensure that they've understood +what the preconditions are and that they code will always satisfy them. + +“Throughout this course, we'll use the term _safety preconditions_ to describe +this situation.” + +
diff --git a/src/unsafe-deep-dive/introduction/two-roles.md b/src/unsafe-deep-dive/introduction/two-roles.md new file mode 100644 index 000000000000..a7c61d9c6684 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/two-roles.md @@ -0,0 +1,58 @@ +--- +minutes: 5 +--- + +# The unsafe keyword has two roles + +1. _Creating_ APIs with safety considerations + + - unsafe functions: `unsafe fn get_unchecked(&self) { ... }` + - unsafe traits: `unsafe trait Send {}` + +2. _Using_ APIs with safety considerations + + - invoking built-in unsafe operators: `unsafe { *ptr }` + - calling unsafe functions: `unsafe { x.get_unchecked() }` + - implementing unsafe traits: `unsafe impl Send for Counter {}` + +
+ +Two roles: + +1. **Creating** APIs with safety considerations and defining what needs to be + considered +2. **Using** APIs with safety considerations and confirming that the + consideration has been made + +### Creating APIs with safety considerations + +“First, the unsafe keyword enables you to create APIs that can break Rust’s +safety guarantees. Specifically, you need to use the unsafe keyword when +defining unsafe functions and unsafe traits. + +“When used in this role, you’re informing users of your API that they need to be +careful.” + +“The creator of the API should communicate what care needs to be taken. Unsafe +APIs are not complete without documentation about safety requirements.. Callers +need to know that they have satisfied any requirements, and that’s impossible if +they’re not written down.” + +### Using APIs with safety considerations + +“The unsafe keyword adopts its other role, using APIs, when it is used nearby to +a curly brace. + +“When used in this role, the unsafe keyword means that the author has been +careful. They have verified that the code is safe and is providing an assurance +to others.” + +“Unsafe blocks are most common. They allow you to invoke unsafe functions that +have been defined using the first role. + +“Unsafe blocks also allow you to perform operations which the compiler knows are +unsafe, such as dereferencing a raw pointer.” + +“You might also see the unsafe keyword being used to implement unsafe traits. + +
diff --git a/src/unsafe-deep-dive/introduction/warm-up.md b/src/unsafe-deep-dive/introduction/warm-up.md new file mode 100644 index 000000000000..c36687bc5e9c --- /dev/null +++ b/src/unsafe-deep-dive/introduction/warm-up.md @@ -0,0 +1,13 @@ +# Warm up examples + +Examples to demonstrate: + +- using an [unsafe block] (`unsafe { ... }`) +- defining an [unsafe function] (`unsafe fn`) +- [implementing] an unsafe trait (`unsafe impl { ... }`) +- defining an [unsafe trait] (`unsafe trait`) + +[unsafe block]: warm-up/unsafe-block.md +[unsafe function]: warm-up/unsafe-fn.md +[implementing]: warm-up/unsafe-impl.md +[unsafe trait]: warm-up/unsafe-trait.md diff --git a/src/unsafe-deep-dive/introduction/warm-up/unsafe-block.md b/src/unsafe-deep-dive/introduction/warm-up/unsafe-block.md new file mode 100644 index 000000000000..4c18f5ed986e --- /dev/null +++ b/src/unsafe-deep-dive/introduction/warm-up/unsafe-block.md @@ -0,0 +1,57 @@ +--- +minutes: 8 +--- + +# Using an unsafe block + +```rust,editable,ignore +fn main() { + let numbers = vec![0, 1, 2, 3, 4]; + let i = numbers.len() / 2; + + let x = *numbers.get_unchecked(i); + assert_eq!(i, x); +} +``` + +
+ +Walk through the code. Confirm that the audience is familiar with the +dereference operator. + +Attempt to compile the code, trigger the compiler error. + +Add the unsafe block: + +```rust +# fn main() { +# let numbers = vec![0, 1, 2, 3, 4]; +# let i = numbers.len() / 2; +# + let x = unsafe { *numbers.get_unchecked(i) }; +# assert_eq!(i, x); +# } +``` + +Prompt audience for a code review. Guide learners towards adding a safety +comment. + +Add the safety comment: + +```rust +// SAFETY: `i` must be within 0..numbers.len() +``` + +_Suggested Solution_ + +```rust +fn main() { + let numbers = vec![0, 1, 2, 3, 4]; + let i = numbers.len() / 2; + + let x = unsafe { *numbers.get_unchecked(i) }; + assert_eq!(i, x); +} +``` + +
diff --git a/src/unsafe-deep-dive/introduction/warm-up/unsafe-fn.md b/src/unsafe-deep-dive/introduction/warm-up/unsafe-fn.md new file mode 100644 index 000000000000..e7b86f0aecc3 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/warm-up/unsafe-fn.md @@ -0,0 +1,62 @@ +--- +minutes: 8 +--- + +# Defining an unsafe function + +```rust,editable +/// Convert a nullable pointer to a reference. +/// +/// Returns `None` when `p` is null, otherwise wraps `val` in `Some`. +fn ptr_to_ref<'a, T>(ptr: *mut T) -> Option<&'a mut T> { + if ptr.is_null() { + None + } else { + // SAFETY: `ptr` is non-null + unsafe { Some(&mut *ptr) } + } +} +``` + +
+ +“This looks as though it’s safe code, however it actually requires an unsafe +block.” + +Highlight the dereference operation, i.e. `*p` within the unsafe block. + +“Callers must ensure that the `ptr` is null, or that it may be converted to a +reference. + +“It may be counter-intuitive, but many pointers cannot be converted to +references. + +“Among other issues, a pointer could be created that points to some arbitrary +bits rather than a valid value. That’s not something that Rust allows and +something that this function needs to protect itself against. + +“So we, as API designers, have two paths. We can either try to assume +responsibility for guarding against invalid inputs, or we can shift that +responsibility to the caller with the unsafe keyword.” + +“The first path is a difficult one. We’re accepting a generic type T, which is +all possible types that implement Sized. That’s a lot of types! + +“Therefore, the second path makes more sense. + +_Extra content (time permitting)_ + +“By the way, if you’re interested in the details of pointers and what the rules +of converting them to references are, the standard library has a lot of useful +documentation. You should also look into the source code of many of the methods +on std::pointer. + +“For example, the `ptr_to_ref` function on this slide actually exists in the +standard library as the `as_mut` method on pointers.” + +Open the documentation for [std::pointer.as_mut] and highlight the Safety +section. + +
+ +[std::pointer.as_mut]: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_mut diff --git a/src/unsafe-deep-dive/introduction/warm-up/unsafe-impl.md b/src/unsafe-deep-dive/introduction/warm-up/unsafe-impl.md new file mode 100644 index 000000000000..e200708e2dad --- /dev/null +++ b/src/unsafe-deep-dive/introduction/warm-up/unsafe-impl.md @@ -0,0 +1,43 @@ +--- +minutes: 4 +--- + +# Implementing an unsafe trait + +```rust,editable,ignore +pub struct LogicalClock { + inner: std::sync::Arc, +} + +// ... + +impl Send for LogicalClock {} +impl Sync for LogicalClock {} +``` + +
+ +“Before we take a look at the code, we should double check that everyone knows +what a trait is. Is anyone able to explain traits for the rest of the class? + +- “Traits are often described as a way to create shared behavior. Thinking about + traits as shared behavior focuses on the syntax of methods and their + signatures. +- “There’s also a deeper way to think of traits: as sets of requirements. This + emphasizes the shared semantics of the implementing types. + +“Can anyone explain what the `Send` and `Sync` traits are? + +- If no + - “Send and Sync relate to concurrency. There are many details, but broadly + speaking, Send types can be shared between threads by value. Sync types must + be shared by reference. + - There are many rules to follow to ensure that it’s safe to share data across + thread boundaries. Those rules cannot be checked by the compiler, and + therefore the code author must take responsibility for upholding them. + - Arc implements Send and Sync, therefore it’s safe for our clock to as well. + - It may be useful to point out that the word _atomic_ has the meaning of + “indivisible” or “whole” from Ancient Greek, rather than the contemporary + English sense of “tiny particle”. + +
diff --git a/src/unsafe-deep-dive/introduction/warm-up/unsafe-trait.md b/src/unsafe-deep-dive/introduction/warm-up/unsafe-trait.md new file mode 100644 index 000000000000..e823a1b3c485 --- /dev/null +++ b/src/unsafe-deep-dive/introduction/warm-up/unsafe-trait.md @@ -0,0 +1,26 @@ +--- +minutes: 5 +--- + +# Defining an unsafe trait + +```rust,editable +/// Indicates that the type uses 32 bits of memory. +pub trait Size32 {} +``` + +
+ +“Now let’s define our own unsafe trait.” + +Add the unsafe keyword and compile the code. + +“If the requirements of the trait are semantic, then your trait may not need any +methods at all. The documentation is essential, however.” + +“Traits without methods are called marker traits. When implementing them for +types, you are adding information to the type system. You have now given the +compiler the ability to talk about types that meet the requirements described in +the documentation.” + +
diff --git a/src/unsafe-deep-dive/memory-lifecycle.md b/src/unsafe-deep-dive/memory-lifecycle.md new file mode 100644 index 000000000000..3615628408b7 --- /dev/null +++ b/src/unsafe-deep-dive/memory-lifecycle.md @@ -0,0 +1,24 @@ +# Memory Lifecycle + +Memory moves through different phases as objects (values) are created and +destroyed + +| Memory State | Readable from Safe Rust? | +| ------------ | ------------------------ | +| Available | No | +| Allocated | No | +| Initialized | Yes | + +
+ +“This section discusses what it happens as memory from the operating system +becomes a valid variable in the program. + +When memory is available, the operating system has provided our program with it. + +When memory is allocated, it is reserved for values to be written to it. We call +this uninitialized memory. + +When memory is initialized, it is safe to read from. + +
diff --git a/src/unsafe-deep-dive/motivations.md b/src/unsafe-deep-dive/motivations.md index c4acb81994d1..30df07082d6a 100644 --- a/src/unsafe-deep-dive/motivations.md +++ b/src/unsafe-deep-dive/motivations.md @@ -2,7 +2,7 @@ minutes: 1 --- -# Motivations +# Why for learning unsafe Rust We know that writing code without the guarantees that Rust provides ... @@ -14,6 +14,8 @@ We know that writing code without the guarantees that Rust provides ... ... so why is `unsafe` part of the language? +## Outline + {{%segment outline}}
diff --git a/src/unsafe-deep-dive/motivations/data-structures.md b/src/unsafe-deep-dive/motivations/data-structures.md deleted file mode 100644 index 0e1b3a84697d..000000000000 --- a/src/unsafe-deep-dive/motivations/data-structures.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -minutes: 5 ---- - -# Data Structures - -Some families of data structures are impossible to create in safe Rust. - -- graphs -- bit twiddling -- self-referential types -- intrusive data structures - -
- -Graphs: General-purpose graphs cannot be created as they may need to represent -cycles. Cycles are impossible for the type system to reason about. - -Bit twiddling: Overloading bits with multiple meanings. Examples include using -the NaN bits in `f64` for some other purpose or the higher-order bits of -pointers on `x86_64` platforms. This is somewhat common when writing language -interpreters to keep representations within the word size the target platform. - -Self-referential types are too hard for the borrow checker to verify. - -Intrusive data structures: store structural metadata (like pointers to other -elements) inside the elements themselves, which requires careful handling of -aliasing. - -
diff --git a/src/unsafe-deep-dive/motivations/interop.md b/src/unsafe-deep-dive/motivations/interop.md deleted file mode 100644 index 85505b21fb35..000000000000 --- a/src/unsafe-deep-dive/motivations/interop.md +++ /dev/null @@ -1,243 +0,0 @@ ---- -minutes: 5 ---- - -> TODO: Refactor this content into multiple slides as this slide is intended as -> an introduction to the motivations only, rather than to be an elaborate -> discussion of the whole problem. - -# Interoperability - -Language interoperability allows you to: - -- Call functions written in other languages from Rust -- Write functions in Rust that are callable from other languages - -However, this requires unsafe. - -```rust,editable,ignore -unsafe extern "C" { - safe fn random() -> libc::c_long; -} - -fn main() { - let a = random() as i64; - println!("{a:?}"); -} -``` - -
- -The Rust compiler can't enforce any safety guarantees for programs that it -hasn't compiled, so it delegates that responsibility to you through the unsafe -keyword. - -The code example we're seeing shows how to call the random function provided by -libc within Rust. libc is available to scripts in the Rust Playground. - -This uses Rust's _foreign function interface_. - -This isn't the only style of interoperability, however it is the method that's -needed if you want to work between Rust and some other language in a zero cost -way. Another important strategy is message passing. - -Message passing avoids unsafe, but serialization, allocation, data transfer and -parsing all take energy and time. - -## Answers to questions - -- _Where does "random" come from?_\ - libc is dynamically linked to Rust programs by default, allowing our code to - rely on its symbols, including `random`, being available to our program. -- _What is the "safe" keyword?_\ - It allows callers to call the function without needing to wrap that call in - `unsafe`. The [`safe` function qualifier][safe] was introduced in the 2024 - edition of Rust and can only be used within `extern` blocks. It was introduced - because `unsafe` became a mandatory qualifier for `extern` blocks in that - edition. -- _What is the [`std::ffi::c_long`] type?_\ - According to the C standard, an integer that's at least 32 bits wide. On - today's systems, It's an `i32` on Windows and an `i64` on Linux. - -[`std::ffi::c_long`]: https://doc.rust-lang.org/std/ffi/type.c_long.html -[safe]: https://doc.rust-lang.org/stable/edition-guide/rust-2024/unsafe-extern.html - -## Consideration: type safety - -Modify the code example to remove the need for type casting later. Discuss the -potential UB - long's width is defined by the target. - -```rust -unsafe extern "C" { - safe fn random() -> i64; -} - -fn main() { - let a = random(); - println!("{a:?}"); -} -``` - -> Changes from the original: -> -> ```diff -> unsafe extern "C" { -> - safe fn random() -> libc::c_long; -> + safe fn random() -> i64; -> } -> -> fn main() { -> - let a = random() as i64; -> + let a = random(); -> println!("{a:?}"); -> } -> ``` - -It's also possible to completely ignore the intended type and create undefined -behavior in multiple ways. The code below produces output most of the time, but -generally results in a stack overflow. It may also produce illegal `char` -values. Although `char` is represented in 4 bytes (32 bits), -[not all bit patterns are permitted as a `char`][char]. - -Stress that the Rust compiler will trust that the wrapper is telling the truth. - -[char]: https://doc.rust-lang.org/std/primitive.char.html#validity-and-layout - - - -```rust,ignore -unsafe extern "C" { - safe fn random() -> [char; 2]; -} - -fn main() { - let a = random(); - println!("{a:?}"); -} -``` - -> Changes from the original: -> -> ```diff -> unsafe extern "C" { -> - safe fn random() -> libc::c_long; -> + safe fn random() -> [char; 2]; -> } -> -> fn main() { -> - let a = random() as i64; -> - println!("{a}"); -> + let a = random(); -> + println!("{a:?}"); -> } -> ``` - -> Attempting to print a `[char; 2]` from randomly generated input will often -> produce strange output, including: -> -> ```ignore -> thread 'main' panicked at library/std/src/io/stdio.rs:1165:9: -> failed printing to stdout: Bad address (os error 14) -> ``` -> -> ```ignore -> thread 'main' has overflowed its stack -> fatal runtime error: stack overflow, aborting -> ``` - -Mention that type safety is generally not a large concern in practice. Tools -that produce wrappers automatically, i.e. bindgen, are excellent at reading -header files and producing values of the correct type. - -## Consideration: Ownership and lifetime management - -While libc's `random` function doesn't use pointers, many do. This creates many -more possibilities for unsoundness. - -- both sides might attempt to free the memory (double free) -- both sides can attempt to write to the data - -For example, some C libraries expose functions that write to static buffers that -are re-used between calls. - - - - - -```rust,ignore -use std::ffi::{CStr, c_char}; -use std::time::{SystemTime, UNIX_EPOCH}; - -unsafe extern "C" { - /// Create a formatted time based on time `t`, including trailing newline. - /// Read `man 3 ctime` details. - fn ctime(t: *const libc::time_t) -> *const c_char; -} - -unsafe fn format_timestamp<'a>(t: u64) -> &'a str { - let t = t as libc::time_t; - - unsafe { - let fmt_ptr = ctime(&t); - CStr::from_ptr(fmt_ptr).to_str().unwrap() - } -} - -fn main() { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - let now = now.as_secs(); - let now_fmt = unsafe { format_timestamp(now) }; - print!("now (1): {}", now_fmt); - - let future = now + 60; - let future_fmt = unsafe { format_timestamp(future) }; - print!("future: {}", future_fmt); - - print!("now (2): {}", now_fmt); -} -``` - -> _Aside:_ Lifetimes in the `format_timestamp()` function -> -> Neither `'a`, nor `'static`, correctly describe the lifetime of the string -> that's returned. Rust treats it as an immutable reference, but subsequent -> calls to `ctime` will overwrite the static buffer that the string occupies. - -## Consideration: Representation mismatch - -Different programming languages have made different design decisions and this -can create impedance mismatches between different domains. - -Consider string handling. C++ defines `std::string`, which has an incompatible -memory layout with Rust's `String` type. `String` also requires text to be -encoded as UTF-8, whereas `std::string` does not. In C, text is represented by a -null-terminated sequence of bytes (`char*`). - -```rust -fn main() { - let c_repr = b"Hello, C\0"; - let rust_repr = (b"Hello, Rust", 11); - - let c: &str = unsafe { - let ptr = c_repr.as_ptr() as *const i8; - std::ffi::CStr::from_ptr(ptr).to_str().unwrap() - }; - println!("{c}"); - - let rust: &str = unsafe { - let ptr = rust_repr.0.as_ptr(); - let bytes = std::slice::from_raw_parts(ptr, rust_repr.1); - std::str::from_utf8_unchecked(bytes) - }; - println!("{rust}"); -} -``` - -
diff --git a/src/unsafe-deep-dive/motivations/performance.md b/src/unsafe-deep-dive/motivations/performance.md deleted file mode 100644 index 0b32e8600afa..000000000000 --- a/src/unsafe-deep-dive/motivations/performance.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -minutes: 5 ---- - -# Performance - -> TODO: Stub for now - -It's easy to think of performance as the main reason for unsafe, but high -performance code makes up the minority of unsafe blocks. diff --git a/src/unsafe-deep-dive/pinning.md b/src/unsafe-deep-dive/pinning.md new file mode 100644 index 000000000000..768bc818311b --- /dev/null +++ b/src/unsafe-deep-dive/pinning.md @@ -0,0 +1,34 @@ +# Pinning + +This segment of the course covers: + +- What "pinning" is +- Why it is necessary +- How Rust implements it +- How it interacts with unsafe and FFI + +## Outline + +{{%segment outline}} + +
+ +"Pinning, or holding a value's memory address in a fixed location,is one of the +more challenging concepts in Rust." + +"Normally only seen within async code, i.e. +[`poll(self: Pin<&mut Self>)`][poll], pinning has wider applicability." + +Some data structures that are difficult or impossible to write without the +unsafe keyword, including self-referential structs and intrusive data +structures. + +FFI with C++ is a prominent use case that's related to this. Rust must assume +that any C++ with a reference might be a self-referential data structure. + +"To understand this conflict in more detail, we'll first need to make sure that +we have a strong understanding of Rust's move semantics." + +
+ +[poll]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll diff --git a/src/unsafe-deep-dive/pinning/README.md b/src/unsafe-deep-dive/pinning/README.md new file mode 100644 index 000000000000..384124da357b --- /dev/null +++ b/src/unsafe-deep-dive/pinning/README.md @@ -0,0 +1,19 @@ +# pinning + +> **Important Note** +> +> To not add this section to the project's SUMMARY.md yet. Once CLs/PRs to +> accept all the new segments for the Unsafe Deep Dive have been included in the +> repository, an update to SUMMARY.md will be made. + +## About + +This segment explains pinning, Rust's `Pin` type and concepts that relate +to FFI rather than its async use case. Treatment of the `Unpin` trait and the +`PhantomPinned` type is provided. + +## Status + +Provisional/beta. + +## Outline diff --git a/src/unsafe-deep-dive/pinning/definition-of-pin.md b/src/unsafe-deep-dive/pinning/definition-of-pin.md new file mode 100644 index 000000000000..0a46acb76e05 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/definition-of-pin.md @@ -0,0 +1,37 @@ +--- +minutes: 5 +--- + +# Definition of Pin + +```rust,ignore +#[repr(transparent)] +pub struct Pin { + pointer: Ptr, +} + +impl> Pin { + pub fn new(pointer: Ptr) -> Pin { ... } +} + +impl Pin { + pub unsafe fn new_unchecked(pointer: Ptr) -> Pin { ... } +} +``` + +
+ +`Pin` is a minimal wrapper around a _pointer type_, which is defined as a type +that implements `Deref`. + +However, `Pin::new()` only accepts types that dereference into a target that +implements `Unpin` (`Deref`). This allows `Pin` to rely on the +the type system to enforce its guarantees. + +Types that do not implement `Unpin`, i.e. types that require pinning, must +create a `Pin` via the unsafe `Pin::new_unchecked()`. + +Aside: Unlike other `new()`/`new_unchecked()` method pairs, `new` does not do +any runtime checking. The check is a zero-cost compile-time check. + +
diff --git a/src/unsafe-deep-dive/pinning/drop-and-not-unpin-worked-example.md b/src/unsafe-deep-dive/pinning/drop-and-not-unpin-worked-example.md new file mode 100644 index 000000000000..d41a89868139 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/drop-and-not-unpin-worked-example.md @@ -0,0 +1,112 @@ +--- +minutes: 5 +--- + +# Worked example Implementing `Drop` for `!Unpin` types + +```rust,editable,ignore +use std::cell::RefCell; +use std::marker::PhantomPinned; +use std::mem; +use std::pin::Pin; + +thread_local! { + static BATCH_FOR_PROCESSING: RefCell> = RefCell::new(Vec::new()); +} + +#[derive(Debug)] +struct CustomString(String); + +#[derive(Debug)] +struct SelfRef { + data: CustomString, + ptr: *const CustomString, + _pin: PhantomPinned, +} + +impl SelfRef { + fn new(data: &str) -> Pin> { + let mut boxed = Box::pin(SelfRef { + data: CustomString(data.to_owned()), + ptr: std::ptr::null(), + _pin: PhantomPinned, + }); + + let ptr: *const CustomString = &boxed.data; + unsafe { + Pin::get_unchecked_mut(Pin::as_mut(&mut boxed)).ptr = ptr; + } + boxed + } +} + +impl Drop for SelfRef { + fn drop(&mut self) { + // SAFETY: Safe because we are reading bytes from a String + let payload = unsafe { std::ptr::read(&self.data) }; + BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload.0)); + } +} + +fn main() { + let pinned = SelfRef::new("Rust 🦀"); + drop(pinned); + + BATCH_FOR_PROCESSING.with(|batch| { + println!("Batch: {:?}", batch.borrow()); + }); +} +``` + +
+ +This example uses the `Drop` trait to add data for some post-processing, such as +telemetry or logging. + +**The Safety comment is incorrect.** `ptr::read` creates a bitwise copy, leaving +`self.data` in an invalid state. `self.data` will be dropped again at the end of +the method, which is a double free. + +Ask the class for fix the code. + +**Suggestion 0: Redesign** + +Redesign the post-processing system to work without `Drop`. + +**Suggestion 1: Clone** + +Using `.clone()` is an obvious first choice, but it allocates memory. + +```rust,ignore +impl Drop for SelfRef { + fn drop(&mut self) { + let payload = self.data.0.clone(); + BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload)); + } +} +``` + +**Suggestion 2: ManuallyDrop** + +Wrapping `CustomString` in `ManuallyDrop` prevents the (second) automatic drop +at the end of the `Drop` impl. + +```rust,ignore +struct SelfRef { + data: ManuallyDrop, + ptr: *const CustomString, + _pin: PhantomPinned, +} + +// ... + +impl Drop for SelfRef { + fn drop(&mut self) { + // SAFETY: self.data + let payload = unsafe { ManuallyDrop::take(&mut self.data) }; + BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload.0)); + } +} +``` + +
diff --git a/src/unsafe-deep-dive/pinning/phantompinned.md b/src/unsafe-deep-dive/pinning/phantompinned.md new file mode 100644 index 000000000000..f729bae81486 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/phantompinned.md @@ -0,0 +1,36 @@ +--- +minutes: 5 +--- + +# PhantomPinned + +## Definition + +```rust,ignore +pub struct PhantomPinned; + +impl !Unpin for PhantomPinned {} +``` + +## Usage + +```rust,editable +pub struct DynamicBuffer { + data: Vec, + cursor: std::ptr::NonNull, + _pin: std::marker::PhantomPinned, +} +``` + +
+ +`PhantomPinned` is a marker type. + +If a type contains a `PhantomPinned`, it will not implement `Unpin` by default. + +This has the effect of enforcing pinning when `DynamicBuffer` is wrapped by +`Pin`. + +
+ + diff --git a/src/unsafe-deep-dive/pinning/pin-and-drop.md b/src/unsafe-deep-dive/pinning/pin-and-drop.md new file mode 100644 index 000000000000..484871980428 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/pin-and-drop.md @@ -0,0 +1,98 @@ +--- +minutes: 10 +--- + +# Pin<Ptr> and Drop + +A key challenge with pinned, `!Unpin` types is implementing the `Drop` trait. +The `drop` method takes `&mut self`, which allows moving the value. However, +pinned values must not be moved. + +## An Incorrect `Drop` Implementation + +It's easy to accidentally move a value inside `drop`. Operations like +assignment, `ptr::read`, and `mem::replace` can silently break the pinning +guarantee. + +```rust,editable +struct SelfRef { + data: String, + ptr: *const String, +} + +impl Drop for SelfRef { + fn drop(&mut self) { + // BAD: `ptr::read` moves `self.data` out of `self`. + // When `_dupe` is dropped at the end of the function, it's a double free! + let _dupe = unsafe { std::ptr::read(&self.data) }; + } +} +``` + +
+`!Unpin` types can make it difficult to safely implement `Drop`. Implementations +must not move pinned values. + +Pinned types make guarantees about memory stability. Operations like `ptr::read` +and `mem::replace` can silently break these guarantees by moving or duplicating +data, invalidating internal pointers without the type system's knowledge. + +In this `drop()` method, `_dupe` is a bitwise copy of `self.data`. At the end of +the method, it will be dropped along with `self`. This double drop is undefined +behavior. + +
+ +## A Correct `Drop` Implementation + +To implement `Drop` correctly for a `!Unpin` type, you must ensure that the +value is not moved. A common pattern is to create a helper function that +operates on `Pin<&mut T>`. + +```rust,editable +use std::marker::PhantomPinned; +use std::pin::Pin; + +struct SelfRef { + data: String, + ptr: *const String, + _pin: PhantomPinned, +} + +impl SelfRef { + fn new(data: impl Into) -> Pin> { + let mut this = Box::pin(SelfRef { + data: data.into(), + ptr: std::ptr::null(), + _pin: PhantomPinned, + }); + let ptr: *const String = &this.data; + // SAFETY: `this` is pinned before we create the self-reference. + unsafe { + Pin::as_mut(&mut this).get_unchecked_mut().ptr = ptr; + } + this + } + + // This function can only be called on a pinned `SelfRef`. + unsafe fn drop_pinned(self: Pin<&mut SelfRef>) { + // `self` is pinned, so we must not move out of it. + println!("dropping {}", self.data); + } +} + +impl Drop for SelfRef { + fn drop(&mut self) { + // We can safely call `drop_pinned` because `drop` is the last time + // the value is used. We use `new_unchecked` because we know `self` + // will not be moved again. + unsafe { + SelfRef::drop_pinned(Pin::new_unchecked(self)); + } + } +} + +fn main() { + let _pinned = SelfRef::new("Hello, "); +} // `Drop` runs without moving the pinned value +``` diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer.md b/src/unsafe-deep-dive/pinning/self-referential-buffer.md new file mode 100644 index 000000000000..29a507502304 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer.md @@ -0,0 +1,26 @@ +--- +minutes: 5 +--- + +# Self-Referential Buffer Example + +A "self-referential buffer" is a type that has a reference to one of its own +fields: + +```rust,ignore +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: *mut u8, +} +``` + +This kind of structure is not typical in Rust, because there's no way to update +the cursor's address when instances of `SelfReferentialBuffer` move. + +However, this kind of setup is more natural in other languages that provide +garbage collection, and also C++ that allows users to define their own behavior +during moves and copies. + +## Outline + +{{%segment outline}} diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer/cpp.md b/src/unsafe-deep-dive/pinning/self-referential-buffer/cpp.md new file mode 100644 index 000000000000..bc2aa2e7fca7 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer/cpp.md @@ -0,0 +1,35 @@ +--- +minutes: 15 +--- + +# Modelled in C++ + +```cpp,editable,ignore +#include +#include + +class SelfReferentialBuffer { + std::byte data[1024]; + std::byte* cursor = data; + +public: + SelfReferentialBuffer(SelfReferentialBuffer&& other) + : cursor{data + (other.cursor - other.data)} + { + std::memcpy(data, other.data, 1024); + } +}; +``` + +Investigate on [Compiler Explorer](https://godbolt.org/z/ascME6aje) + +
+ +The `SelfReferentialBuffer` contains two members, `data` is a kilobyte of memory +and `cursor` is a pointer into the former. + +Its move constructor ensures that cursor is updated to the new memory address. + +This type can't be expressed easily in Rust. + +
diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-offset.md b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-offset.md new file mode 100644 index 000000000000..c9c2347991d8 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-offset.md @@ -0,0 +1,39 @@ +--- +minutes: 5 +--- + +# With Offset + +```rust,editable +#[derive(Debug)] +pub struct SelfReferentialBuffer { + data: [u8; 1024], + position: usize, +} + +impl SelfReferentialBuffer { + pub fn new() -> Self { + SelfReferentialBuffer { data: [0; 1024], position: 0 } + } + + pub fn read(&self, n_bytes: usize) -> &[u8] { + let available = self.data.len().saturating_sub(self.position); + let len = n_bytes.min(available); + &self.data[self.position..self.position + len] + } + + pub fn write(&mut self, bytes: &[u8]) { + let available = self.data.len().saturating_sub(self.position); + let len = bytes.len().min(available); + self.data[self.position..self.position + len].copy_from_slice(&bytes[..len]); + self.position += len; + } +} +``` + +
+ +In Rust, it's more idiomatic to use an offset variable and to create references +on-demand. + +
diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-pin.md b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-pin.md new file mode 100644 index 000000000000..cc7142f8423d --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-pin.md @@ -0,0 +1,85 @@ +--- +minutes: 10 +--- + +# With Pin<Ptr> + +Pinning allows Rust programmers to create a type which is much more similar to +the C++ class. + +```rust,editable +use std::marker::PhantomPinned; +use std::pin::Pin; + +/// A self-referential buffer that cannot be moved. +#[derive(Debug)] +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: *mut u8, + _pin: PhantomPinned, +} + +impl SelfReferentialBuffer { + pub fn new() -> Pin> { + let buffer = SelfReferentialBuffer { + data: [0; 1024], + cursor: std::ptr::null_mut(), + _pin: PhantomPinned, + }; + let mut pinned = Box::pin(buffer); + + unsafe { + let mut_ref = Pin::get_unchecked_mut(pinned.as_mut()); + mut_ref.cursor = mut_ref.data.as_mut_ptr(); + } + + pinned + } + + pub fn read(&self, n_bytes: usize) -> &[u8] { + unsafe { + let start = self.data.as_ptr(); + let end = start.add(self.data.len()); + let cursor = self.cursor as *const u8; + + assert!((start..=end).contains(&cursor), "cursor is out of bounds"); + + let offset = cursor.offset_from(start) as usize; + let available = self.data.len().saturating_sub(offset); + let len = n_bytes.min(available); + + &self.data[offset..offset + len] + } + } + + pub fn write(mut self: Pin<&mut Self>, bytes: &[u8]) { + let this = unsafe { self.as_mut().get_unchecked_mut() }; + unsafe { + let start = this.data.as_mut_ptr(); + let end = start.add(1024); + + assert!((start..=end).contains(&this.cursor), "cursor is out of bounds"); + let available = end.offset_from(this.cursor) as usize; + let len = bytes.len().min(available); + + std::ptr::copy_nonoverlapping(bytes.as_ptr(), this.cursor, len); + this.cursor = this.cursor.add(len); + } + } +} +``` + +
+ +Note that the function signatures have now changed. For example, `::new()` +returns `Pin>` rather than `Self`. This incurs a heap allocation +because `Pin` must work with a pointer type like `Box`. + +In `::new()`, we use `Pin::get_unchecked_mut()` to get a mutable reference to +the buffer _after_ it has been pinned. This is `unsafe` because we are breaking +the pinning guarantee for a moment to initialize the `cursor`. We must make sure +not to move the `SelfReferentialBuffer` after this point. The safety contract of +`Pin` is that once a value is pinned, its memory location is fixed until it is +dropped. + +
diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-raw-pointers.md b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-raw-pointers.md new file mode 100644 index 000000000000..5438b6964f57 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust-raw-pointers.md @@ -0,0 +1,75 @@ +--- +minutes: 5 +--- + +# With a raw pointer + +```rust,editable +#[derive(Debug)] +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: *mut u8, +} + +impl SelfReferentialBuffer { + pub fn new() -> Self { + let mut buffer = + SelfReferentialBuffer { data: [0; 1024], cursor: std::ptr::null_mut() }; + + buffer.update_cursor(); + buffer + } + + // Danger: must be called after every move + pub fn update_cursor(&mut self) { + self.cursor = self.data.as_mut_ptr(); + } + + pub fn read(&self, n_bytes: usize) -> &[u8] { + unsafe { + let start = self.data.as_ptr(); + let end = start.add(1024); + let cursor = self.cursor as *const u8; + + assert!((start..=end).contains(&cursor), "cursor is out of bounds"); + + let available = end.offset_from(cursor) as usize; + let len = n_bytes.min(available); + std::slice::from_raw_parts(cursor, len) + } + } + + pub fn write(&mut self, bytes: &[u8]) { + unsafe { + let start = self.data.as_mut_ptr(); + let end = start.add(1024); + + assert!((start..=end).contains(&self.cursor), "cursor is out of bounds"); + let available = end.offset_from(self.cursor) as usize; + let len = bytes.len().min(available); + + std::ptr::copy_nonoverlapping(bytes.as_ptr(), self.cursor, len); + self.cursor = self.cursor.add(len); + } + } +} +``` + +
+ +Avoid spending too much time here. + +Talking points: + +- Emphasize that `unsafe` appears frequently. This is a hint that another design + may be more appropriate. +- `unsafe` blocks lack of safety comments. Therefore, this code is unsound. +- `unsafe` blocks are too broad. Good practice uses smaller `unsafe` blocks with + specific behavior, specific preconditions and specific safety comments. + +Questions: + +Q: Should the `read()` and `write()` methods be marked as unsafe?\ +A: Yes, because `self.cursor` will be a null pointer unless written to. + +
diff --git a/src/unsafe-deep-dive/pinning/self-referential-buffer/rust.md b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust.md new file mode 100644 index 000000000000..18004032cebf --- /dev/null +++ b/src/unsafe-deep-dive/pinning/self-referential-buffer/rust.md @@ -0,0 +1,48 @@ +--- +minutes: 10 +--- + +# Modeled in Rust + +```rust,ignore +/// Raw pointers +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: *mut u8, +} + +/// Integer offsets +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: usize, +} + +/// Pinning +pub struct SelfReferentialBuffer { + data: [u8; 1024], + cursor: *mut u8, + _pin: std::marker::PhantomPinned, +} +``` + +## Original C++ class definition for reference + +```cpp,ignore +class SelfReferentialBuffer { + char data[1024]; + char* cursor; +} +``` + +
+ +The next few slides show three approaches to creating a Rust type with the same +semantics as the original C++. + +- Using raw pointers: matches C++ very closely, but using the resulting type is + extremely hazardous +- Storing integer offsets: more natural in Rust, but references need to be + created manually +- Pinning: allows raw pointers with fewer `unsafe` blocks + +
diff --git a/src/unsafe-deep-dive/pinning/unpin-trait.md b/src/unsafe-deep-dive/pinning/unpin-trait.md new file mode 100644 index 000000000000..18fd1896df16 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/unpin-trait.md @@ -0,0 +1,30 @@ +# Unpin trait + +- `Unpin` type allows types to move freely, even when they're wrapped by a `Pin` +- Most types implement `Unpin`, because it is an "`auto trait`" +- `auto trait` behavior can be changed: + - `!Unpin` types must never move + - Types containing a `PhantomPinned` field do not implement `Unpin` by default + +
+ +Explain that when a trait implements `Unpin`, the pinning behavior of `Pin` +does not get invoked. The value is free to move. + +Explain that almost all types implement `Unpin`; automatically implemented by +the compiler. + +Types implementing `Unpin` are saying: 'I promise I have no self-references, so +moving me is always safe.' + +Ask: What types might be `!Unpin`? + +- Compiler-generated futures +- Types containing a `PhantomPinned` field +- Some types wrapping C++ objects + +`!Unpin` types cannot be moved once pinned + +
+ +[`PhantomPinned`]: https://doc.rust-lang.org/std/marker/struct.PhantomPinned.html diff --git a/src/unsafe-deep-dive/pinning/what-a-move-is.md b/src/unsafe-deep-dive/pinning/what-a-move-is.md new file mode 100644 index 000000000000..0c5e7bb0efc2 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/what-a-move-is.md @@ -0,0 +1,67 @@ +# What a move is in Rust + +Always a bitwise copy, even for types that do not implement `Copy`: + +```rust +#[derive(Debug, Default)] +pub struct DynamicBuffer { + data: Vec, + position: usize, +}; + +pub fn move_and_inspect(x: DynamicBuffer) { println!("{x:?}"); } + +pub fn main() { + let a = DynamicBuffer::default(); + let mut b = a; + b.data.push(b'R'); + b.data.push(b'U'); + b.data.push(b'S'); + b.data.push(b'T'); + move_and_inspect(b); +} +``` + +Generated [LLVM IR] for calling `move_and_expect()`: + +```llvm +call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_12, ptr align 8 %b, i64 32, i1 false) +invoke void @move_and_inspect(ptr align 8 %_12) +``` + +- `memcpy` from variable `%b` to `%_12` +- Call to `move_and_inspect` with `%_12` (the copy) + +
+ +Note that `DynamicBuffer` does not implement `Copy`. + +Implication: a value's memory address is not stable. + +To show movement as a bitwise copy, either +[open the code in the playground][LLVM IR] and look at the or +[the Compiler Explorer]. + +Optional for those who prefer assembly output: + +The Compiler Explorer is useful for discussing the generated assembly and focus +the cursor assembly output in the `main` function on lines 128-136 (should be +highlighted in pink). + +Relevant code generated output `move_and_inspect`: + +```assembly +mov rax, qword ptr [rsp + 16] +mov qword ptr [rsp + 48], rax +mov rax, qword ptr [rsp + 24] +mov qword ptr [rsp + 56], rax +movups xmm0, xmmword ptr [rsp] +movaps xmmword ptr [rsp + 32], xmm0 +lea rdi, [rsp + 32] +call qword ptr [rip + move_and_inspect@GOTPCREL] +``` + +
+ +[LLVM IR]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=6f587283e8e0ec02f1ea8e871fc9ac72 +[The Compiler Explorer]: https://rust.godbolt.org/z/6o6nP7do4 diff --git a/src/unsafe-deep-dive/pinning/what-pinning-is.md b/src/unsafe-deep-dive/pinning/what-pinning-is.md new file mode 100644 index 000000000000..3dfe7df157d9 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/what-pinning-is.md @@ -0,0 +1,43 @@ +--- +minutes: 5 +--- + +# What pinning is + +- A pinned type cannot change its memory address (move) +- The pointed-to value cannot be moved by safe code + +`Pin` makes use of the ownership system to control how the pinned value is +accessed. Rather than changing the language, Rust's ownership system is used to +enforce pinning. `Pin` owns its contents and nothing in its safe API triggers a +move. + +This is explained in + +
+ +Conceptually, pinning prevents the default movement behavior. + +This appears to be a change in the language itself. + +However, the `Pin` wrapper doesn't actually change anything fundamental about +the language. + +`Pin` doesn't expose safe APIs that would allow a move. Thus, it can prevent +bitwise copy. + +Unsafe APIs allow library authors to wrap types that do not implement `Unpin`, +but they must uphold the same guarantees. + +The documentation of `Pin` uses the term "pointer types". + +The term "pointer type" is much more broad than the pointer primitive type in +the language. + +A "pointer type" wraps every type that implements `Deref` with a target that +implements `Unpin`. + +Rust style note: This trait bound is enforced through trait bounds on the +`::new()` constructor, rather than on the type itself. + +
diff --git a/src/unsafe-deep-dive/pinning/why-difficult.md b/src/unsafe-deep-dive/pinning/why-difficult.md new file mode 100644 index 000000000000..974c75cd5fb0 --- /dev/null +++ b/src/unsafe-deep-dive/pinning/why-difficult.md @@ -0,0 +1,24 @@ +# Why Pin is difficult to use + +- `Pin` is "just" a type defined in the standard library +- This satisfied the needs of its original audience, the creators of async + runtimes, without needing to extending the core language +- That audience could accept some of its ergonomic downsides, as users of + `async` would rarely interact with `Pin` directly + +
+ +"You might wonder why Pin is so awkward to use. The answer is largely +historical." + +"`Pin` offered a simpler implementation for the Rust project than +alternatives". + +"Pin was designed primarily for the ~100 people in the world who write async +runtimes. The Rust team chose a simpler (for the compiler) but less ergonomic +design." + +"More user-friendly proposals existed but were rejected as too complex for the +primary audience, who could handle the complexity." + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game.md b/src/unsafe-deep-dive/rules-of-the-game.md new file mode 100644 index 000000000000..18ae4d183786 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game.md @@ -0,0 +1,21 @@ +# Rules of the game + +
+ +“We've seen many examples of code that has problems in the class, but we lack +consistent terminology + +“The goal of the next section is to introduce some terms that describe many of +the concepts that we have been thinking about. + +- undefined behavior +- sound +- unsound + +“Given that many safety preconditions are semantic rather than syntactic, it's +important to use a shared vocabulary. That way we can agree on semantics. + +“The overarching goal is to develop a mental framework of what soundness is and +ensure that Rust code that contains unsafe remains sound.” + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/3-shapes-of-sound-rust.md b/src/unsafe-deep-dive/rules-of-the-game/3-shapes-of-sound-rust.md new file mode 100644 index 000000000000..4e8166414a0c --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/3-shapes-of-sound-rust.md @@ -0,0 +1,25 @@ +--- +minutes: 5 +--- + +# 3 shapes of sound Rust + +- Functions written only in Safe Rust +- Functions that contain `unsafe` blocks which are impossible to misuse +- Unsafe functions that have their safety preconditions documented + +
+ +- We want to write sound code. +- Sound code can only have the following shapes: + - safe functions that contain no unsafe blocks + - safe functions that completely encapsulate unsafe blocks, meaning that the + caller does not need to know about them + - unsafe functions that contain unsafe blocks but don't encapsulate them, and + pass the proof burden to their caller +- Burden of proof + - safe functions with only Safe Rust -> compiler + - safe functions with unsafe blocks -> function author + - unsafe functions -> function caller + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory.md new file mode 100644 index 000000000000..d5cd7774d146 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory.md @@ -0,0 +1,21 @@ +--- +minutes: 3 +--- + +# Copying memory - Introduction + +```rust,ignore +/// Reads bytes from `source` and writes them to `dest` +pub fn copy(dest: &mut [u8], source: &[u8]) { ... } +``` + +
+ +“Here is our initial function prototype.” + +“`copy` accepts two slices as arguments. `dest` (destination) is mutable, +whereas `source` is not.” + +“Let's see the shapes of sound Rust code.” + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory/crying-wolf.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/crying-wolf.md new file mode 100644 index 000000000000..eed69bff8ecb --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/crying-wolf.md @@ -0,0 +1,31 @@ +--- +minutes: 5 +--- + +# Crying Wolf + +```rust,editable +pub unsafe fn copy(dest: &mut [u8], source: &[u8]) { + for (dest, src) in dest.iter_mut().zip(source) { + *dest = *src; + } +} + +fn main() { + let a = &[114, 117, 115, 116]; + let b = &mut [82, 85, 83, 84]; + + println!("{}", String::from_utf8_lossy(b)); + unsafe { copy(b, a) }; + println!("{}", String::from_utf8_lossy(b)); +} +``` + +
+ +“It is also possible to create so-called crying wolf functions. + +“These are functions which are tagged as unsafe, but which have no safety +preconditions that programmers need to check. + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory/documented-safety-preconditions.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/documented-safety-preconditions.md new file mode 100644 index 000000000000..731d4f7585b1 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/documented-safety-preconditions.md @@ -0,0 +1,68 @@ +--- +minutes: 5 +--- + +# Documented safety preconditions + +```rust,editable +/// ... +/// +/// # Safety +/// +/// This function can easily trigger undefined behavior. Ensure that: +/// +/// - `source` pointer is non-null and non-dangling +/// - `source` data ends with a null byte within its memory allocation +/// - `source` data is not freed (its lifetime invariants are preserved) +/// - `source` data contains fewer than `isize::MAX` bytes +pub unsafe fn copy(dest: &mut [u8], source: *const u8) { + let source = { + let mut len = 0; + + let mut end = source; + // SAFETY: Caller has provided a non-null pointer + while unsafe { *end != 0 } { + len += 1; + // SAFETY: Caller has provided a data with length < isize:MAX + end = unsafe { end.add(1) }; + } + + // SAFETY: Caller maintains lifetime and aliasing requirements + unsafe { std::slice::from_raw_parts(source, len + 1) } + }; + + for (dest, src) in dest.iter_mut().zip(source) { + *dest = *src; + } +} + +fn main() { + let a = [114, 117, 115, 116].as_ptr(); + let b = &mut [82, 85, 83, 84, 0]; + + println!("{}", String::from_utf8_lossy(b)); + unsafe { + copy(b, a); + } + println!("{}", String::from_utf8_lossy(b)); +} +``` + +
+ +Changes to previous iterations: + +- `copy` marked as unsafe +- Safety preconditions are documented +- inline safety comments + +An unsafe function is sound when both its safety preconditions and its internal +unsafe blocks are documented. + +Fixes needed in `main`. + +- `a` does not satisfy one of the preconditions of `copy` (source` data ends + with a null byte within its memory allocation) +- SAFETY comment needed + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory/encapsulated-unsafe.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/encapsulated-unsafe.md new file mode 100644 index 000000000000..4948dc534489 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/encapsulated-unsafe.md @@ -0,0 +1,53 @@ +--- +minutes: 5 +--- + +# Encapsulated Unsafe Rust + +```rust,editable +pub fn copy(dest: &mut [u8], source: &[u8]) { + let len = dest.len().min(source.len()); + let mut i = 0; + while i < len { + // SAFETY: `i` must be in-bounds as it was produced by source.len() + let new = unsafe { source.get_unchecked(i) }; + + // SAFETY: `i` must be in-bounds as it was produced by dest.len() + let old = unsafe { dest.get_unchecked_mut(i) }; + + *old = *new; + i += 1; + } + + for (dest, src) in dest.iter_mut().zip(source) { + *dest = *src; + } +} + +fn main() { + let a = &[114, 117, 115, 116]; + let b = &mut [82, 85, 83, 84]; + + println!("{}", String::from_utf8_lossy(b)); + copy(b, a); + println!("{}", String::from_utf8_lossy(b)); +} +``` + +
+ +“Here we have a safe function that encapsulates unsafe blocks that are used +internally. + +“This implementation avoids iterators. Instead, the implementor is accessing +memory manually.” + +“Is this correct?” “Are there any problems?” + +“Who has responsibility for ensuring that correctness? The author of the +function. + +“A Safe Rust function that contains unsafe blocks remains sound if it’s +impossible for an input to cause memory safety issues. + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory/exposed-unsafe.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/exposed-unsafe.md new file mode 100644 index 000000000000..762283fdf302 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/exposed-unsafe.md @@ -0,0 +1,82 @@ +--- +minutes: 5 +--- + +# Exposed Unsafe Rust + +```rust,editable +pub fn copy(dest: &mut [u8], source: *const u8) { + let source = { + let mut len = 0; + + let mut end = source; + while unsafe { *end != 0 } { + len += 1; + end = unsafe { end.add(1) }; + } + + unsafe { std::slice::from_raw_parts(source, len + 1) } + }; + + for (dest, src) in dest.iter_mut().zip(source) { + *dest = *src; + } +} + +fn main() { + let a = [114, 117, 115, 116].as_ptr(); + let b = &mut [82, 85, 83, 84, 0]; + + println!("{}", String::from_utf8_lossy(b)); + copy(b, a); + println!("{}", String::from_utf8_lossy(b)); +} +``` + +
+ +onality of copying bytes from one place to the next remains the same. + +“However, we need to manually create a slice. To do that, we first need to find +the end of the data. + +“As we’re working with text, we’ll use the C convention of a null-terminated +string. + +Compile the code. See that the output remains the same. + +“An unsound function can still work correctly for some inputs. Just because your +tests pass, does not mean that you have a sound function.” + +“Can anyone spot any issues?” + +- Readability: difficult to quickly scan code +- `source` pointer might be null +- `source` pointer might be dangling, i.e. point to freed or uninitialized + memory +- `source` might not be null-terminated + +“Assume that we cannot change the function signature, what improvements could we +make to the code to address these issues?” + +- Null pointer: Add null check with early return + (`if source.is_null() { return; }`) +- Readability: Use a well-tested library rather than implementing “find first + null byte” ourselves + +“Some safety requirements are impossible to defensively check for, however, +i.e.:” + +- dangling pointer +- no null termination byte + +“How can we make this function sound?” + +- Either + - Change the type of the `source` input argument to something that has a known + length, i.e. use a slice like the previous example. +- Or + - Mark the function as unsafe + - Document the safety preconditions + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/copying-memory/safe.md b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/safe.md new file mode 100644 index 000000000000..73ef4fe7bbe2 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/copying-memory/safe.md @@ -0,0 +1,54 @@ +--- +minutes: 5 +--- + +# Safe Rust + +```rust,editable +pub fn copy(dest: &mut [u8], source: &[u8]) { + for (dest, src) in dest.iter_mut().zip(source) { + *dest = *src; + } +} + +fn main() { + let a = &[114, 117, 115, 116]; + let b = &mut [82, 85, 83, 84]; + + println!("{}", String::from_utf8_lossy(b)); + copy(b, a); + println!("{}", String::from_utf8_lossy(b)); +} +``` + +
+ +“The implementation only uses unsafe Rust + +What can we learn from this? + +“It is impossible for `copy` to trigger memory safety issues when implemented in +Safe Rust. This is true for all possible input arguments.” + +“For example, by using Rust’s iterators, we can ensure that we’ll never trigger +errors relating to handling pointers directly, such as needing null pointer or +bounds checks.” + +Ask: “Can you think of any others?” + +- No aliasing issues +- Dangling pointers are impossible +- Alignment will be correct +- Cannot accidentally read from uninitialized memory + +“We can say that the `copy` function is _sound_ because Rust ensures that all of +the safety preconditions are satisfied.” + +“From the point of view of the programmer, as this function is implemented in +safe Rust, we can think of it as having no safety preconditions.” + +“This does not mean that `copy` will always do what the caller might want. If +there is insufficient space available in the `dest` slice, then data will not be +copied across.” + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/rust-is-sound.md b/src/unsafe-deep-dive/rules-of-the-game/rust-is-sound.md new file mode 100644 index 000000000000..956492d8a707 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/rust-is-sound.md @@ -0,0 +1,31 @@ +--- +minutes: 5 +--- + +# Rust is sound + +- Soundness is fundamental to Rust +- Soundness ≈ impossible to cause memory safety problems +- Sound functions have common “shapes” + +
+ +“A fundamental principle of Rust code is that it is sound. + +“We’ll create a formal definition of the term soundness shortly. In the +meantime, think of sound code as code that cannot trigger memory safety +problems. + +“Sound code is made up of _sound functions_ and _sound operations_. + +“A sound function as a function where none of its possible inputs could provoke +soundness problems. + +Sound functions have common shapes. + +Those shapes are what we’ll look at now. + +“We’ll start with one that’s implemented in Safe Rust, and then see what could +happen when we introduce `unsafe` to different parts. + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/soundness-proof.md b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof.md new file mode 100644 index 000000000000..321eba722740 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof.md @@ -0,0 +1 @@ +# Soundness Proof diff --git a/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/corollary.md b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/corollary.md new file mode 100644 index 000000000000..b0ed860b74f3 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/corollary.md @@ -0,0 +1,33 @@ +--- +minutes: 2 +--- + +# Soundness Proof (Part 2) + +A sound function is one that can't trigger UB if its safety preconditions are +satisfied. + +Corollary: All functions implemented in pure safe Rust are sound. + +Proof: + +- Safe Rust code has no safety preconditions. + +- Therefore, callers of functions implemented in pure safe Rust always trivially + satisfy the empty set of preconditions. + +- Safe Rust code can't trigger UB. + +QED. + +
+ +- Read the corollary. + +- Explain the proof. + +- Translate into informal terms: all safe Rust code is nice. It does not have + safety preconditions that the programmer has to think of, always plays by the + rules, and never triggers UB. + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/soundness.md b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/soundness.md new file mode 100644 index 000000000000..b449213b5441 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/soundness.md @@ -0,0 +1,21 @@ +--- +minutes: 2 +--- + +# Soundness + +A sound function is one that can't trigger UB if its safety preconditions are +satisfied. + +
+ +- Read the definition of sound functions. + +- Remind the students that the programmer who implements the caller is + responsible to satisfy the safety precondition, the compiler is not helping. + +- Translate into informal terms. Soundness means that the function is nice and + plays by the rules. It documents its safety preconditions, and when the caller + satisfies them, the function behaves well (no UB). + +
diff --git a/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/unsoundness.md b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/unsoundness.md new file mode 100644 index 000000000000..0cb739104a18 --- /dev/null +++ b/src/unsafe-deep-dive/rules-of-the-game/soundness-proof/unsoundness.md @@ -0,0 +1,27 @@ +--- +minutes: 2 +--- + +# Unsoundness + +A sound function is one that can't trigger UB if its safety preconditions are +satisfied. + +An unsound function can trigger UB even if you satisfy the documented safety +preconditions. + +Unsound code is _bad_. + +
+ +- Read the definition of unsound functions. + +- Translate into infomal terms: unsound code is not nice. No, that's an + understatement. Unsound code is BAD. Even if you play by the documented rules, + unsound code can still trigger UB! + +- We don't want any unsound code in our repositories. + +- Finding unsound code is the **primary** goal of the code review + +
diff --git a/src/unsafe-deep-dive/safety-preconditions.md b/src/unsafe-deep-dive/safety-preconditions.md new file mode 100644 index 000000000000..3a856812de01 --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions.md @@ -0,0 +1,18 @@ +# Safety Preconditions + +Safety preconditions are conditions on an action that must be satisfied before +that action will be safe. + +
+ +“Safety preconditions are conditions on code that must be satisfied to maintain +Rust's safety guarantees + +“You're likely to see a strong affinity between safety preconditions and the +rules of Safe Rust.” + +Q: Can you list any? + +(Fuller list in the next slide) + +
diff --git a/src/unsafe-deep-dive/safety-preconditions/ascii.md b/src/unsafe-deep-dive/safety-preconditions/ascii.md new file mode 100644 index 000000000000..273b90a637d3 --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/ascii.md @@ -0,0 +1,45 @@ +--- +minutes: 5 +--- + +# Example: ASCII Type + +```rust,editable +/// Text that is guaranteed to be encoded within 7-bit ASCII. +pub struct Ascii<'a>(&'a mut [u8]); + +impl<'a> Ascii<'a> { + pub fn new(bytes: &'a mut [u8]) -> Option { + bytes.iter().all(|&b| b.is_ascii()).then(|| Ascii(bytes)) + } + + /// Creates a new `Ascii` from a byte slice without checking for ASCII + /// validity. + /// + /// # Safety + /// + /// Providing non-ASCII bytes results in undefined behavior. + pub unsafe fn new_unchecked(bytes: &'a mut [u8]) -> Self { + Ascii(bytes) + } +} +``` + +
+ +"The `Ascii` type is a minimal wrapper around a byte slice. Internally, they +share the same representation. However, `Ascii` requires that the high bit must +is not be used." + +Optional: Extend the example to mention that it's possible to use +`debug_assert!` to test the preconditions during tests without impacting release +builds. + +```rust,ignore +unsafe fn new_unchecked(bytes: &mut [u8]) -> Self { + debug_assert!(bytes.iter().all(|&b| b.is_ascii())) + Ascii(bytes) +} +``` + +
diff --git a/src/unsafe-deep-dive/safety-preconditions/common-preconditions.md b/src/unsafe-deep-dive/safety-preconditions/common-preconditions.md new file mode 100644 index 000000000000..672bb0224db4 --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/common-preconditions.md @@ -0,0 +1,77 @@ +--- +minutes: 6 +--- + +# Common safety preconditions + +- Aliasing and Mutability +- Alignment +- Array access is in-bound +- Initialization +- Lifetimes +- Pointer provenance +- Validity +- Memory + +
+ +Avoid spending too much time explaining every precondition: we will be working +through the details during the course. The intent is to show that there are +several. + +"An incomplete list, but these are a few of the major safety preconditions to +get us thinking." + +- Validity. Values must be valid values of the type that they represent. Rust's + references may not be null. Creating one with `unsafe` causes the. +- Alignment. References to values must be well-aligned, which means th +- Aliasing. All Rust code must uphold Rust's borrowing rules. If you are + manually creating mutable references (`&mut T`) from pointers, then you may + only create one +- Initialization. All instances of Rust types must be fully initialized. To + create a value from raw memory, we need to make sure that we've written +- Pointer provenance. The origin of a pointer is important. Casting a `usize` to + a raw pointer is no longer allowed. +- Lifetimes. References must not outlive their referent. + +Some conditions are even more subtle than they first seem. + +Consider "in-bounds array access". Reading from the memory location, i.e. +dereferencing, is not required to break the program. Creating an out-of-bounds +reference already break's the compiler's assumptions, leading to erratic +behavior. + +Rust tells LLVM to use its `getelementptr inbounds` assumption. That assumption +will cause later optimization passes within the compiler to misbehave (because +out-of-bounds memory access cannot occur). + +Optional: open [the playground][1], which shows the code below. Explain that +this is essentially a C function written in Rust syntax that gets items from an +array. Generate the LLVM IR with the **Show LLVM IR** button. Highlight +`getelementptr inbounds i32, ptr %array, i64 %offset`. + +```rust,ignore +#[unsafe(no_mangle)] +pub unsafe fn get(array: *const i32, offset: isize) -> i32 { + unsafe { *array.offset(offset) } +} +``` + +Expected output (the line to highlight starts with `%_3): + +```llvm +define noundef i32 @get(ptr noundef readonly captures(none) %array, i64 noundef %offset) unnamed_addr #0 { +start: + %_3 = getelementptr inbounds i32, ptr %array, i64 %offset + %_0 = load i32, ptr %_3, align 4, !noundef !3 + ret i32 %_0 +} +``` + +[1]: https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=4116c4de01c863cac918f193448210b1 + +Bounds: You correctly noted that creating an out-of-bounds pointer (beyond the +"one-past-the-end" rule) is UB, even without dereferencing, due to LLVM's +inbounds assumptions. + +
diff --git a/src/unsafe-deep-dive/safety-preconditions/defining.md b/src/unsafe-deep-dive/safety-preconditions/defining.md new file mode 100644 index 000000000000..7beee82a700a --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/defining.md @@ -0,0 +1,8 @@ +--- +minutes: 2 +--- + +# Defining your own preconditions + +- User-defined types are entitled to have their own safety preconditions +- Include documentation so that they can later be determined and satisfied diff --git a/src/unsafe-deep-dive/safety-preconditions/determining.md b/src/unsafe-deep-dive/safety-preconditions/determining.md new file mode 100644 index 000000000000..0c99960e545a --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/determining.md @@ -0,0 +1,48 @@ +# Determining Preconditions + +Where do you find the safety preconditions? + +```rust,editable,ignore +fn main() { + let b: *mut i32 = std::ptr::null_mut(); + println!("{:?}", b.as_mut()); +} +``` + +
+ +Attempt to compile the program to trigger the compiler error ("error\[E0133\]: +call to unsafe function ..."). + +Ask: “Where would you look if you wanted to know the preconditions for a +function? Here we need to understand when it's safe to convert from a null +pointer to a mutable reference.” + +Locations to look: + +- A function's API documentation, especially its safety section +- The source code and its internal safety comments +- Module documentation +- Rust Reference + +Consult [the documentation] for the `as_mut` method. + +Highlight Safety section. + +> **Safety** +> +> When calling this method, you have to ensure that either the pointer is null +> or the pointer is convertible to a reference. + +Click the "convertible to a reference" hyperlink to the "Pointer to reference +conversion" + +Track down the rules for converting a pointer to a reference, aka is +"_deferencerable_". + +Consider the implications of this excerpt (Rust 1.90.0) "You must enforce Rust’s +aliasing rules. The exact aliasing rules are not decided yet, ..." + +
+ +[the documentation]: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_mut diff --git a/src/unsafe-deep-dive/safety-preconditions/getter.md b/src/unsafe-deep-dive/safety-preconditions/getter.md new file mode 100644 index 000000000000..a879fe75fa7e --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/getter.md @@ -0,0 +1,46 @@ +# Getter example + +```rust,editable +/// Return the element at `index` from `arr` +unsafe fn get(arr: *const i32, index: usize) -> i32 { + unsafe { *arr.add(index) } +} +``` + +
+ +“Safety preconditions are conditions on code that must be satisfied to maintain +Rust's safety guarantees + +“You're likely to see a strong affinity between safety preconditions and the +rules of Safe Rust.” + +Ask: “What are the safety preconditions of `get`?” + +- The pointer `arr` is non-null, well-aligned and refers to an array of `i32` +- `index` is in-bounds + +Add safety comments: + +```rust,ignore +/// Return the element at `index` from `arr` +/// +/// # Safety +/// +/// - `arr` is non-null, correctly aligned and points to a valid `i32` +/// - `index` is in-bounds for the array +unsafe fn get(arr: *const i32, index: usize) -> i32 { + // SAFETY: Caller guarantees that index is inbounds + unsafe { *arr.add(index) } +} +``` + +Optional: Add runtime checks can be added in debug builds to provide some extra +robustness. + +```rust,ignore +debug_assert!(!arr.is_null()); +debug_assert_eq!(arr as usize % std::mem::align_of::(), 0); +``` + +
diff --git a/src/unsafe-deep-dive/safety-preconditions/references.md b/src/unsafe-deep-dive/safety-preconditions/references.md new file mode 100644 index 000000000000..47ad956c6d6b --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/references.md @@ -0,0 +1,101 @@ +--- +minutes: 10 +--- + +# Example: references + +```rust,editable,ignore +fn main() { + let mut boxed = Box::new(123); + let a: *mut i32 = &mut *boxed as *mut i32; + let b: *mut i32 = std::ptr::null_mut(); + + println!("{:?}", *a); + println!("{:?}", b.as_mut()); +} +``` + +Confirm understanding of the syntax + +- `Box` type is a reference to an integer on the heap that is owned by the + box. +- `*mut i32` type is a so-called raw pointer to an integer that the compiler + does not know the ownership of. Programmers need to ensure the rules are + enforced without assistance from the compiler. + - Note: raw pointers do not provide ownership info to Rust. A pointer can be + semantically owning the data, or semantically borrowing, but that + information only exists in the programmer's mind + +- `&mut *boxed as *mut _` expression: + - `*boxed` is ... + - `&mut *boxed` is ... + - finally, `as *mut i32` casts the reference to a pointer. + +- References, such as `&mut i32`, "borrow" their referent. This is Rust's + ownership system. + +Confirm understanding of ownership + +- Step through code + - (Line 3) Creates raw pointer to the `123` by de-referencing the box, + creating a new reference and casting the new reference as a pointer + - (Line 4) Creates raw pointer with a NULL value + - (Line 7) Converts the raw pointer to an Option with `.as_mut()` + +- Highlight that pointers are nullable in Rust (unlike references). + +- Compile to reveal the error messages + +- Discuss + - (Line 6) `println!("{:?}", *a);` + - Prefix star dereferences a raw pointer + - It is an explicit operation. Whereas regular references have implicit + dereferencing most of the time thanks to the Deref trait. This is referred + to as "auto-deref". + - Dereferencing a raw pointer is an unsafe operation + - Requires an unsafe block + - (Line 7) `println!("{:?}", b.as_mut());` + - `as_mut()` is an unsafe function. + - Calling unsafe function requires an unsafe block + +- Demonstrate: Fix the code (add unsafe blocks) and compile again to show the + working program + +- Demonstrate: Replace `as *mut i32` with `as *mut _`, show that it compiles. + + - We can partially omit the target type in the cast. The Rust compiler knows + that the source of the cast is a `&mut i32`. This reference type can only be + converted to one pointer type, `*mut i32`. + +- Add safety comments + - We said that the unsafe code marks the responsibility shift from the + compiler to the programmer. + - How do we convey that we thought about our unusual responsibilities while + writing unsafe code? Safety comments. + - Safety comments explain why unsafe code is correct. + - Without a safety comment, unsafe code is not safe. + +- Discuss: Whether to use one large unsafe block or two smaller ones + - Possibility of using a single unsafe block rather than multiple + - Using more allows safety comments as specific as possible + +[ptr-as_mut]: https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.as_mut + +_Suggested Solution_ + +```rust +fn main() { + let mut boxed = Box::new(123); + let a: *mut i32 = &mut *boxed as *mut i32; + let b: *mut i32 = std::ptr::null_mut(); + + // SAFETY: `a` is a non-null pointer to i32, it is initialized and still + // allocated. + println!("{:?}", unsafe { *a }); + + // SAFETY: `b` is a null pointer, which `as_mut()` converts to `None`. + println!("{:?}", unsafe { b.as_mut() }); +} +``` + +
diff --git a/src/unsafe-deep-dive/safety-preconditions/semantic-preconditions.md b/src/unsafe-deep-dive/safety-preconditions/semantic-preconditions.md new file mode 100644 index 000000000000..44c72bf0b239 --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/semantic-preconditions.md @@ -0,0 +1 @@ +# Semantic preconditions diff --git a/src/unsafe-deep-dive/safety-preconditions/u8-to-bool.md b/src/unsafe-deep-dive/safety-preconditions/u8-to-bool.md new file mode 100644 index 000000000000..a9f7287dce9d --- /dev/null +++ b/src/unsafe-deep-dive/safety-preconditions/u8-to-bool.md @@ -0,0 +1 @@ +# Example: u8 to bool diff --git a/src/unsafe-deep-dive/welcome.md b/src/unsafe-deep-dive/welcome.md index 2291281c1bbd..6d3c7853b2e7 100644 --- a/src/unsafe-deep-dive/welcome.md +++ b/src/unsafe-deep-dive/welcome.md @@ -1,11 +1,9 @@ --- -course: Unsafe -session: Day 1 Morning -target_minutes: 300 +course: Unsafe Deep Dive +session: Unsafe Deep Dive +target_minutes: 600 --- -# Welcome to Unsafe Rust - > IMPORTANT: THIS MODULE IS IN AN EARLY STAGE OF DEVELOPMENT > > Please do not consider this module of Comprehensive Rust to be complete. With @@ -17,30 +15,47 @@ target_minutes: 300 [GitHub issue tracker]: https://github.com/google/comprehensive-rust/issues -The `unsafe` keyword is easy to type, but hard to master. When used -appropriately, it forms a useful and indeed essential part of the Rust -programming language. +# Welcome to the Unsafe Rust Deep Dive + +This deep dive aims to enable you to work productively with Unsafe Rust. + +We’ll work on three areas: + +- establishing a mental model of Unsafe Rust +- practicing reading & writing Unsafe Rust +- practicing code review for Unsafe Rust + +
+ +The goal of this class is to teach you enough Unsafe Rust for you to be able to +review easy cases yourself, and distinguish difficult cases that need to be +reviewed my more experienced Unsafe Rust engineers. -By the end of this deep dive, you'll know how to work with `unsafe` code, review -others' changes that include the `unsafe` keyword, and produce your own. +- Establishing a mental model of Unsafe Rust + - what the `unsafe` keyword means + - a shared vocabulary for talking about safety + - a mental model of how memory works + - common patterns + - expectations for code that uses `unsafe` -What you'll learn: +- Practice working with unsafe + - reading and writing both code and documentation + - use unsafe APIs + - design and implement them -- What the terms undefined behavior, soundness, and safety mean -- Why the `unsafe` keyword exists in the Rust language -- How to write your own code using `unsafe` safely -- How to review `unsafe` code +- Review code + - the confidence to self-review easy cases + - the knowledge to detect difficult cases -## Links to other sections of the course +“We'll be using a spiral model of teaching. This means that we revisit the same +topic multiple times with increasing depth.” -The `unsafe` keyword has treatment in: +A round of introductions is useful, particularly if the class participants don't +know each other well. Ask everyone to introduce themselves, noting down any +particular goals for the class. -- _Rust Fundamentals_, the main module of Comprehensive Rust, includes a session - on [Unsafe Rust] in its last day. -- _Rust in Chromium_ discusses how to [interoperate with C++]. Consult that - material if you are looking into FFI. -- _Bare Metal Rust_ uses unsafe heavily to interact with the underlying host, - among other things. +- Who are you? +- What are you working on? +- What are your goals for this class? -[interoperate with C++]: ../chromium/interoperability-with-cpp.md -[Unsafe Rust]: ../unsafe-rust.html +
diff --git a/src/unsafe-rust/Cargo.toml b/src/unsafe-rust/Cargo.toml index 9daa52099d31..eda2a2672b38 100644 --- a/src/unsafe-rust/Cargo.toml +++ b/src/unsafe-rust/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" publish = false [dependencies] -tempfile = "3.23.0" +tempfile = "3.24.0" [[bin]] name = "listdir" diff --git a/tests/package-lock.json b/tests/package-lock.json index f2b71b225822..b6f1124b1f55 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -3243,39 +3243,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -3303,6 +3304,22 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",