Auto merge of #149022 - RalfJung:miri, r=RalfJung

miri subtree update

Contains the fix for https://github.com/rust-lang/miri/issues/4698, which we should get shipped ASAP.

Subtree update of `miri` to 0fea24aee0.

Created using https://github.com/rust-lang/josh-sync.

r? `@ghost`
This commit is contained in:
bors 2025-11-17 14:17:24 +00:00
commit 42ebbd2356
58 changed files with 2584 additions and 1210 deletions

View file

@ -396,7 +396,7 @@ dependencies = [
name = "cargo-miri"
version = "0.1.0"
dependencies = [
"cargo_metadata 0.21.0",
"cargo_metadata 0.23.1",
"directories",
"rustc-build-sysroot",
"rustc_tools_util 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -423,6 +423,15 @@ dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4"
dependencies = [
"serde",
]
[[package]]
name = "cargo-util-schemas"
version = "0.8.2"
@ -468,6 +477,20 @@ dependencies = [
"thiserror 2.0.15",
]
[[package]]
name = "cargo_metadata"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
dependencies = [
"camino",
"cargo-platform 0.3.1",
"semver",
"serde",
"serde_json",
"thiserror 2.0.15",
]
[[package]]
name = "cargotest2"
version = "0.1.0"
@ -2157,6 +2180,16 @@ dependencies = [
"windows-targets 0.53.3",
]
[[package]]
name = "libloading"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
dependencies = [
"cfg-if",
"windows-link 0.2.1",
]
[[package]]
name = "libm"
version = "0.2.15"
@ -2404,7 +2437,7 @@ dependencies = [
"ipc-channel",
"libc",
"libffi",
"libloading",
"libloading 0.9.0",
"measureme",
"nix",
"rand 0.9.2",
@ -4152,7 +4185,7 @@ version = "0.0.0"
dependencies = [
"bitflags",
"libc",
"libloading",
"libloading 0.8.8",
"odht",
"rustc_abi",
"rustc_ast",

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,7 @@ features = ['unprefixed_malloc_on_supported_platforms']
libc = "0.2"
# native-lib dependencies
libffi = { version = "5.0.0", optional = true }
libloading = { version = "0.8", optional = true }
libloading = { version = "0.9", optional = true }
serde = { version = "1.0.219", features = ["derive"], optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
@ -72,7 +72,7 @@ harness = false
default = ["stack-cache", "native-lib"]
genmc = ["dep:genmc-sys"]
stack-cache = []
stack-cache-consistency-check = ["stack-cache"]
expensive-consistency-checks = ["stack-cache"]
tracing = ["serde_json"]
native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"]

View file

@ -4,29 +4,23 @@ version = 4
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "bitflags"
version = "2.9.1"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "camino"
version = "1.1.10"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@ -44,38 +38,21 @@ dependencies = [
[[package]]
name = "cargo-platform"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4"
checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4"
dependencies = [
"serde",
]
[[package]]
name = "cargo-util-schemas"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830"
dependencies = [
"semver",
"serde",
"serde-untagged",
"serde-value",
"thiserror",
"toml",
"unicode-xid",
"url",
]
[[package]]
name = "cargo_metadata"
version = "0.21.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868"
checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
dependencies = [
"camino",
"cargo-platform",
"cargo-util-schemas",
"semver",
"serde",
"serde_json",
@ -84,9 +61,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.1"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "directories"
@ -106,18 +83,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"windows-sys",
]
[[package]]
@ -126,24 +92,14 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys",
]
[[package]]
@ -152,15 +108,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@ -169,139 +116,32 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
"wasip2",
]
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "indexmap"
version = "2.10.0"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown",
@ -315,15 +155,15 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libredox"
version = "0.1.4"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"libc",
@ -331,30 +171,15 @@ 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"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "memchr"
version = "2.7.5"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "once_cell"
@ -368,44 +193,20 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"zerovec",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
@ -418,9 +219,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_users"
version = "0.5.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
@ -429,9 +230,9 @@ dependencies = [
[[package]]
name = "rustc-build-sysroot"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd41ead66a69880951b2f7df3139db401d44451b4da123344d27eaa791b89c95"
checksum = "3b881c015c729b43105bbd3702a9bdecee28fafaa21126d1d62e454ec011a4b7"
dependencies = [
"anyhow",
"rustc_version",
@ -457,15 +258,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.8"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.60.2",
"windows-sys",
]
[[package]]
@ -485,48 +286,38 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.26"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-untagged"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e"
dependencies = [
"erased-serde",
"serde",
"typeid",
]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -535,14 +326,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.141"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
@ -554,83 +346,50 @@ dependencies = [
"serde",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tempfile"
version = "3.20.0"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys",
]
[[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",
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "toml"
version = "0.8.23"
@ -673,40 +432,11 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "unicode-ident"
version = "1.0.18"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "walkdir"
@ -725,267 +455,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen-rt",
"wit-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.59.0",
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-targets 0.52.6",
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.12"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"

View file

@ -17,7 +17,7 @@ doctest = false # and no doc tests
directories = "6"
rustc_version = "0.4"
serde_json = "1.0.40"
cargo_metadata = "0.21"
cargo_metadata = "0.23"
rustc-build-sysroot = "0.5.10"
# Enable some feature flags that dev-dependencies need but dependencies

View file

@ -28,7 +28,7 @@ mod downloading {
/// The GenMC repository the we get our commit from.
pub(crate) const GENMC_GITHUB_URL: &str = "https://github.com/MPI-SWS/genmc.git";
/// The GenMC commit we depend on. It must be available on the specified GenMC repository.
pub(crate) const GENMC_COMMIT: &str = "d9527280bb99f1cef64326b1803ffd952e3880df";
pub(crate) const GENMC_COMMIT: &str = "aa10ed65117c3291524efc19253b5d443a4602ac";
/// Ensure that a local GenMC repo is present and set to the correct commit.
/// Return the path of the GenMC repo and whether the checked out commit was changed.
@ -140,51 +140,6 @@ mod downloading {
}
}
// FIXME(genmc,llvm): Remove once the LLVM dependency of the GenMC model checker is removed.
/// The linked LLVM version is in the generated `config.h`` file, which we parse and use to link to LLVM.
/// Returns c++ compiler definitions required for building with/including LLVM, and the include path for LLVM headers.
fn link_to_llvm(config_file: &Path) -> (String, String) {
/// Search a string for a line matching `//@VARIABLE_NAME: VARIABLE CONTENT`
fn extract_value<'a>(input: &'a str, name: &str) -> Option<&'a str> {
input
.lines()
.find_map(|line| line.strip_prefix("//@")?.strip_prefix(name)?.strip_prefix(": "))
}
let file_content = std::fs::read_to_string(&config_file).unwrap_or_else(|err| {
panic!("GenMC config file ({}) should exist, but got errror {err:?}", config_file.display())
});
let llvm_definitions = extract_value(&file_content, "LLVM_DEFINITIONS")
.expect("Config file should contain LLVM_DEFINITIONS");
let llvm_include_dirs = extract_value(&file_content, "LLVM_INCLUDE_DIRS")
.expect("Config file should contain LLVM_INCLUDE_DIRS");
let llvm_library_dir = extract_value(&file_content, "LLVM_LIBRARY_DIR")
.expect("Config file should contain LLVM_LIBRARY_DIR");
let llvm_config_path = extract_value(&file_content, "LLVM_CONFIG_PATH")
.expect("Config file should contain LLVM_CONFIG_PATH");
// Add linker search path.
let lib_dir = PathBuf::from_str(llvm_library_dir).unwrap();
println!("cargo::rustc-link-search=native={}", lib_dir.display());
// Add libraries to link.
let output = std::process::Command::new(llvm_config_path)
.arg("--libs") // Print the libraries to link to (space-separated list)
.output()
.expect("failed to execute llvm-config");
let llvm_link_libs =
String::try_from(output.stdout).expect("llvm-config output should be a valid string");
for link_lib in llvm_link_libs.trim().split(" ") {
let link_lib =
link_lib.strip_prefix("-l").expect("Linker parameter should start with \"-l\"");
println!("cargo::rustc-link-lib=dylib={link_lib}");
}
(llvm_definitions.to_string(), llvm_include_dirs.to_string())
}
/// Build the GenMC model checker library and the Rust-C++ interop library with cxx.rs
fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
// Give each step a separate build directory to prevent interference.
@ -204,6 +159,7 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
.always_configure(always_configure) // We force running the configure step when the GenMC commit changed.
.out_dir(genmc_build_dir)
.profile(GENMC_CMAKE_PROFILE)
.define("BUILD_LLI", "OFF")
.define("GENMC_DEBUG", if enable_genmc_debug { "ON" } else { "OFF" });
// The actual compilation happens here:
@ -214,19 +170,11 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
println!("cargo::rustc-link-search=native={}", cmake_lib_dir.display());
println!("cargo::rustc-link-lib=static={GENMC_MODEL_CHECKER}");
// FIXME(genmc,llvm): Remove once the LLVM dependency of the GenMC model checker is removed.
let config_file = genmc_install_dir.join("include").join("genmc").join("config.h");
let (llvm_definitions, llvm_include_dirs) = link_to_llvm(&config_file);
// Part 2:
// Compile the cxx_bridge (the link between the Rust and C++ code).
let genmc_include_dir = genmc_install_dir.join("include").join("genmc");
// FIXME(genmc,llvm): remove once LLVM dependency is removed.
// These definitions are parsed into a cmake list and then printed to the config.h file, so they are ';' separated.
let definitions = llvm_definitions.split(";");
// These are all the C++ files we need to compile, which needs to be updated if more C++ files are added to Miri.
// We use absolute paths since relative paths can confuse IDEs when attempting to go-to-source on a path in a compiler error.
let cpp_files_base_path = Path::new("cpp/src/");
@ -244,16 +192,12 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
if enable_genmc_debug {
bridge.define("ENABLE_GENMC_DEBUG", None);
}
for definition in definitions {
bridge.flag(definition);
}
bridge
.opt_level(2)
.debug(true) // Same settings that GenMC uses (default for cmake `RelWithDebInfo`)
.warnings(false) // NOTE: enabling this produces a lot of warnings.
.std("c++23")
.include(genmc_include_dir)
.include(llvm_include_dirs)
.include("./cpp/include")
.files(&cpp_files)
.out_dir(interface_build_dir)

View file

@ -220,7 +220,8 @@ struct MiriGenmcShim : private GenMCDriver {
auto handle_load_reset_if_none(ThreadId tid, std::optional<SVal> old_val, Ts&&... params)
-> HandleResult<SVal> {
const auto pos = inc_pos(tid);
const auto ret = GenMCDriver::handleLoad<k>(pos, old_val, std::forward<Ts>(params)...);
const auto ret =
GenMCDriver::handleLoad<k>(nullptr, pos, old_val, std::forward<Ts>(params)...);
// If we didn't get a value, we have to reset the index of the current thread.
if (!std::holds_alternative<SVal>(ret)) {
dec_pos(tid);

View file

@ -7,14 +7,16 @@
// GenMC headers:
#include "Verification/VerificationError.hpp"
#include <format>
#include <memory>
#include <sstream>
#include <string>
/** Information about an error, formatted as a string to avoid having to share an error enum and
* printing functionality with the Rust side. */
static auto format_error(VerificationError err) -> std::unique_ptr<std::string> {
auto buf = std::string();
auto s = llvm::raw_string_ostream(buf);
s << err;
std::stringstream s;
s << std::format("{}", err);
return std::make_unique<std::string>(s.str());
}

View file

@ -34,7 +34,7 @@
void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_type) {
BUG_ON(getExec().getGraph().isThreadBlocked(thread_id));
GenMCDriver::handleAssume(inc_pos(thread_id), assume_type);
GenMCDriver::handleAssume(nullptr, inc_pos(thread_id), assume_type);
}
/**** Memory access handling ****/
@ -76,6 +76,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_ty
) -> StoreResult {
const auto pos = inc_pos(thread_id);
const auto ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::Write>(
nullptr,
pos,
GenmcScalarExt::try_to_sval(old_val),
ord,
@ -100,7 +101,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_ty
void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
const auto pos = inc_pos(thread_id);
GenMCDriver::handleFence(pos, ord, EventDeps());
GenMCDriver::handleFence(nullptr, pos, ord, EventDeps());
}
[[nodiscard]] auto MiriGenmcShim::handle_read_modify_write(
@ -143,6 +144,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
const auto storePos = inc_pos(thread_id);
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::FaiWrite>(
nullptr,
storePos,
GenmcScalarExt::try_to_sval(old_val),
ordering,
@ -210,6 +212,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
const auto storePos = inc_pos(thread_id);
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::CasWrite>(
nullptr,
storePos,
GenmcScalarExt::try_to_sval(old_val),
success_ordering,
@ -242,6 +245,7 @@ auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t al
const auto address_space = AddressSpace::AS_User;
const SVal ret_val = GenMCDriver::handleMalloc(
nullptr,
pos,
size,
alignment,
@ -255,7 +259,7 @@ auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t al
auto MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) -> bool {
const auto pos = inc_pos(thread_id);
GenMCDriver::handleFree(pos, SAddr(address), EventDeps());
GenMCDriver::handleFree(nullptr, pos, SAddr(address), EventDeps());
// FIXME(genmc): use returned error from `handleFree` once implemented in GenMC.
return getResult().status.has_value();
}

View file

@ -22,13 +22,23 @@ auto MiriGenmcShim::schedule_next(
// a scheduling decision.
threads_action_[curr_thread_id].kind = curr_thread_next_instr_kind;
if (const auto result = GenMCDriver::scheduleNext(threads_action_))
return SchedulingResult { ExecutionState::Ok, static_cast<int32_t>(result.value()) };
if (getExec().getGraph().isBlocked())
return SchedulingResult { ExecutionState::Blocked, 0 };
if (getResult().status.has_value()) // the "value" here is a `VerificationError`
return SchedulingResult { ExecutionState::Error, 0 };
return SchedulingResult { ExecutionState::Finished, 0 };
auto result = GenMCDriver::scheduleNext(threads_action_);
return std::visit(
[](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
return SchedulingResult { ExecutionState::Ok, static_cast<int32_t>(arg) };
else if constexpr (std::is_same_v<T, Blocked>)
return SchedulingResult { ExecutionState::Blocked, 0 };
else if constexpr (std::is_same_v<T, Error>)
return SchedulingResult { ExecutionState::Error, 0 };
else if constexpr (std::is_same_v<T, Finished>)
return SchedulingResult { ExecutionState::Finished, 0 };
else
static_assert(false, "non-exhaustive visitor!");
},
result
);
}
/**** Execution start/end handling ****/

View file

@ -60,6 +60,7 @@ auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint
const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED;
if (is_lock_acquired) {
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::LockCasWrite>(
nullptr,
inc_pos(thread_id),
old_val,
address,
@ -93,6 +94,7 @@ auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address,
// a mutex to be "unlocked".
const auto old_val = MUTEX_UNLOCKED;
const auto load_ret = GenMCDriver::handleLoad<EventLabel::EventLabelKind::TrylockCasRead>(
nullptr,
++currPos,
old_val,
SAddr(address),
@ -115,6 +117,7 @@ auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address,
}
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::TrylockCasWrite>(
nullptr,
++currPos,
old_val,
SAddr(address),
@ -136,6 +139,7 @@ auto MiriGenmcShim::handle_mutex_unlock(ThreadId thread_id, uint64_t address, ui
-> StoreResult {
const auto pos = inc_pos(thread_id);
const auto ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::UnlockWrite>(
nullptr,
pos,
// As usual, we need to tell GenMC which value was stored at this location before this
// atomic access, if there previously was a non-atomic initializing access. We set the

View file

@ -147,9 +147,16 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
// that is allowed to leak and memory that is not.
conf->warnUnfreedMemory = false;
// FIXME(genmc,error handling): This function currently exits on error, but will return an
// error value in the future. The return value should be checked once this change is made.
checkConfig(*conf);
// Validate the config and exit if there are any errors
std::vector<std::string> warnings;
auto config_valid = conf->validate(warnings);
for (const auto& w : warnings)
WARN("{}", w);
if (auto* errors = std::get_if<ConfigErrorList>(&config_valid); errors) {
for (const auto& e : *errors)
LOG(VerbosityLevel::Error, "{}", e);
exit(EUSER);
}
// Create the actual driver and Miri-GenMC communication shim.
auto driver = std::make_unique<MiriGenmcShim>(std::move(conf), mode);

View file

@ -19,10 +19,11 @@ void MiriGenmcShim::handle_thread_create(ThreadId thread_id, ThreadId parent_id)
// FIXME(genmc): for supporting symmetry reduction, these will need to be properly set:
const unsigned fun_id = 0;
const SVal arg = SVal(0);
const ThreadInfo child_info = ThreadInfo { thread_id, parent_id, fun_id, arg };
const ThreadInfo child_info =
ThreadInfo { thread_id, parent_id, fun_id, arg, "unknown thread" };
// NOTE: Default memory ordering (`Release`) used here.
const auto child_tid = GenMCDriver::handleThreadCreate(pos, child_info, EventDeps());
const auto child_tid = GenMCDriver::handleThreadCreate(nullptr, pos, child_info, EventDeps());
// Sanity check the thread id, which is the index in the `threads_action_` array.
BUG_ON(child_tid != thread_id || child_tid <= 0 || child_tid != threads_action_.size());
threads_action_.push_back(Action(ActionKind::Load, Event(child_tid, 0)));
@ -33,7 +34,7 @@ void MiriGenmcShim::handle_thread_join(ThreadId thread_id, ThreadId child_id) {
const auto pos = inc_pos(thread_id);
// NOTE: Default memory ordering (`Acquire`) used here.
const auto ret = GenMCDriver::handleThreadJoin(pos, child_id, EventDeps());
const auto ret = GenMCDriver::handleThreadJoin(nullptr, pos, child_id, EventDeps());
// If the join failed, decrease the event index again:
if (!std::holds_alternative<SVal>(ret)) {
dec_pos(thread_id);
@ -46,10 +47,10 @@ void MiriGenmcShim::handle_thread_join(ThreadId thread_id, ThreadId child_id) {
void MiriGenmcShim::handle_thread_finish(ThreadId thread_id, uint64_t ret_val) {
const auto pos = inc_pos(thread_id);
// NOTE: Default memory ordering (`Release`) used here.
GenMCDriver::handleThreadFinish(pos, SVal(ret_val));
GenMCDriver::handleThreadFinish(nullptr, pos, SVal(ret_val));
}
void MiriGenmcShim::handle_thread_kill(ThreadId thread_id) {
const auto pos = inc_pos(thread_id);
GenMCDriver::handleThreadKill(pos);
GenMCDriver::handleThreadKill(nullptr, pos);
}

View file

@ -1 +1 @@
7a72c5459dd58f81b0e1a0e5436d145485889375
69d4d5fc0e4db60272aac85ef27ecccef5764f3a

View file

@ -158,19 +158,24 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
_: &rustc_interface::interface::Compiler,
tcx: TyCtxt<'tcx>,
) -> Compilation {
// Compilation is done, interpretation is starting. Deal with diagnostics from the
// compilation part. We cannot call `sess.finish_diagnostics()` as then "aborting due to
// previous errors" gets printed twice.
tcx.dcx().emit_stashed_diagnostics();
tcx.dcx().abort_if_errors();
tcx.dcx().flush_delayed();
// Miri is taking over. Start logging.
init_late_loggers(&EarlyDiagCtxt::new(tcx.sess.opts.error_format), tcx);
// Find the entry point.
if !tcx.crate_types().contains(&CrateType::Executable) {
tcx.dcx().fatal("miri only makes sense on bin crates");
}
let early_dcx = EarlyDiagCtxt::new(tcx.sess.opts.error_format);
init_late_loggers(&early_dcx, tcx);
let (entry_def_id, entry_type) = entry_fn(tcx);
let mut config = self.miri_config.take().expect("after_analysis must only be called once");
// Obtain and complete the Miri configuration.
let mut config = self.miri_config.take().expect("after_analysis must only be called once");
// Add filename to `miri` arguments.
config.args.insert(0, tcx.sess.io.input.filestem().to_string());
@ -179,6 +184,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
env::set_current_dir(cwd).unwrap();
}
// Emit warnings for some unusual configurations.
if tcx.sess.opts.optimize != OptLevel::No {
tcx.dcx().warn("Miri does not support optimizations: the opt-level is ignored. The only effect \
of selecting a Cargo profile that enables optimizations (such as --release) is to apply \
@ -193,6 +199,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
optimizations is usually marginal at best.");
}
// Invoke the interpreter.
let res = if config.genmc_config.is_some() {
assert!(self.many_seeds.is_none());
run_genmc_mode(tcx, &config, |genmc_ctx: Rc<GenmcCtx>| {
@ -209,7 +216,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
} else {
miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
};
// Process interpreter result.
if let Err(return_code) = res {
tcx.dcx().abort_if_errors();
exit(return_code.get());
@ -509,7 +516,6 @@ fn main() {
Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams {
precise_interior_mut: true,
}));
miri_config.provenance_mode = ProvenanceMode::Strict;
} else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" {
match &mut miri_config.borrow_tracker {
Some(BorrowTrackerMethod::TreeBorrows(params)) => {
@ -703,17 +709,6 @@ fn main() {
rustc_args.push(arg);
}
}
// Tree Borrows implies strict provenance, and is not compatible with native calls.
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) {
if miri_config.provenance_mode != ProvenanceMode::Strict {
fatal_error!(
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
);
}
if !miri_config.native_lib.is_empty() {
fatal_error!("Tree Borrows is not compatible with calling native functions");
}
}
// Native calls and strict provenance are not compatible.
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {

View file

@ -352,6 +352,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap();
// The body of this loop needs `borrow_tracker` immutably
// so we can't move this code inside the following `end_call`.
for (alloc_id, tag) in &frame
.extra
.borrow_tracker
@ -378,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
}
borrow_tracker.borrow_mut().end_call(&frame.extra);
interp_ok(())
}
}

View file

@ -153,7 +153,7 @@ impl<'tcx> Stack {
/// Panics if any of the caching mechanisms have broken,
/// - The StackCache indices don't refer to the parallel items,
/// - There are no Unique items outside of first_unique..last_unique
#[cfg(feature = "stack-cache-consistency-check")]
#[cfg(feature = "expensive-consistency-checks")]
fn verify_cache_consistency(&self) {
// Only a full cache needs to be valid. Also see the comments in find_granting_cache
// and set_unknown_bottom.
@ -197,7 +197,7 @@ impl<'tcx> Stack {
tag: ProvenanceExtra,
exposed_tags: &FxHashSet<BorTag>,
) -> Result<Option<usize>, ()> {
#[cfg(feature = "stack-cache-consistency-check")]
#[cfg(feature = "expensive-consistency-checks")]
self.verify_cache_consistency();
let ProvenanceExtra::Concrete(tag) = tag else {
@ -334,7 +334,7 @@ impl<'tcx> Stack {
// This primes the cache for the next access, which is almost always the just-added tag.
self.cache.add(new_idx, new);
#[cfg(feature = "stack-cache-consistency-check")]
#[cfg(feature = "expensive-consistency-checks")]
self.verify_cache_consistency();
}
@ -417,7 +417,7 @@ impl<'tcx> Stack {
self.unique_range.end = self.unique_range.end.min(disable_start);
}
#[cfg(feature = "stack-cache-consistency-check")]
#[cfg(feature = "expensive-consistency-checks")]
self.verify_cache_consistency();
interp_ok(())
@ -472,7 +472,7 @@ impl<'tcx> Stack {
self.unique_range = 0..0;
}
#[cfg(feature = "stack-cache-consistency-check")]
#[cfg(feature = "expensive-consistency-checks")]
self.verify_cache_consistency();
interp_ok(())
}

View file

@ -291,9 +291,10 @@ pub(super) struct TbError<'node> {
pub conflicting_info: &'node NodeDebugInfo,
// What kind of access caused this error (read, write, reborrow, deallocation)
pub access_cause: AccessCause,
/// Which tag the access that caused this error was made through, i.e.
/// Which tag, if any, the access that caused this error was made through, i.e.
/// which tag was used to read/write/deallocate.
pub accessed_info: &'node NodeDebugInfo,
/// Not set on wildcard accesses.
pub accessed_info: Option<&'node NodeDebugInfo>,
}
impl TbError<'_> {
@ -302,10 +303,20 @@ impl TbError<'_> {
use TransitionError::*;
let cause = self.access_cause;
let accessed = self.accessed_info;
let accessed_str =
self.accessed_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
let conflicting = self.conflicting_info;
let accessed_is_conflicting = accessed.tag == conflicting.tag;
// An access is considered conflicting if it happened through a
// different tag than the one who caused UB.
// When doing a wildcard access (where `accessed` is `None`) we
// do not know which precise tag the accessed happened from,
// however we can be certain that it did not come from the
// conflicting tag.
// This is because the wildcard data structure already removes
// all tags through which an access would cause UB.
let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
let title = format!(
"{cause} through {accessed} at {alloc_id:?}[{offset:#x}] is forbidden",
"{cause} through {accessed_str} at {alloc_id:?}[{offset:#x}] is forbidden",
alloc_id = self.alloc_id,
offset = self.error_offset
);
@ -316,7 +327,7 @@ impl TbError<'_> {
let mut details = Vec::new();
if !accessed_is_conflicting {
details.push(format!(
"the accessed tag {accessed} is a child of the conflicting tag {conflicting}"
"the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
));
}
let access = cause.print_as_access(/* is_foreign */ false);
@ -330,7 +341,7 @@ impl TbError<'_> {
let access = cause.print_as_access(/* is_foreign */ true);
let details = vec![
format!(
"the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
"the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
),
format!(
"this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
@ -343,7 +354,7 @@ impl TbError<'_> {
let conflicting_tag_name = "strongly protected";
let details = vec![
format!(
"the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}"
"the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
),
format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
];
@ -351,8 +362,10 @@ impl TbError<'_> {
}
};
let mut history = HistoryData::default();
if !accessed_is_conflicting {
history.extend(self.accessed_info.history.forget(), "accessed", false);
if let Some(accessed_info) = self.accessed_info
&& !accessed_is_conflicting
{
history.extend(accessed_info.history.forget(), "accessed", false);
}
history.extend(
self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
@ -363,6 +376,20 @@ impl TbError<'_> {
}
}
/// Cannot access this allocation with wildcard provenance, as there are no
/// valid exposed references for this access kind.
pub fn no_valid_exposed_references_error<'tcx>(
alloc_id: AllocId,
offset: u64,
access_cause: AccessCause,
) -> InterpErrorKind<'tcx> {
let title =
format!("{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden");
let details = vec![format!("there are no exposed tags which may perform this access here")];
let history = HistoryData::default();
err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
}
type S = &'static str;
/// Pretty-printing details
///
@ -623,10 +650,10 @@ impl DisplayRepr {
} else {
// We take this node
let rperm = tree
.rperms
.locations
.iter_all()
.map(move |(_offset, perms)| {
let perm = perms.get(idx);
.map(move |(_offset, loc)| {
let perm = loc.perms.get(idx);
perm.cloned()
})
.collect::<Vec<_>>();
@ -788,7 +815,7 @@ impl<'tcx> Tree {
show_unnamed: bool,
) -> InterpResult<'tcx> {
let mut indenter = DisplayIndent::new();
let ranges = self.rperms.iter_all().map(|(range, _perms)| range).collect::<Vec<_>>();
let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
if let Some(repr) = DisplayRepr::from(self, show_unnamed) {
repr.print(
&DEFAULT_FORMATTER,

View file

@ -14,6 +14,7 @@ mod foreign_access_skipping;
mod perms;
mod tree;
mod unimap;
mod wildcard;
#[cfg(test)]
mod exhaustive;
@ -54,16 +55,10 @@ impl<'tcx> Tree {
interpret::Pointer::new(alloc_id, range.start),
range.size.bytes(),
);
// TODO: for now we bail out on wildcard pointers. Eventually we should
// handle them as much as we can.
let tag = match prov {
ProvenanceExtra::Concrete(tag) => tag,
ProvenanceExtra::Wildcard => return interp_ok(()),
};
let global = machine.borrow_tracker.as_ref().unwrap();
let span = machine.current_user_relevant_span();
self.perform_access(
tag,
prov,
Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))),
global,
alloc_id,
@ -79,19 +74,9 @@ impl<'tcx> Tree {
size: Size,
machine: &MiriMachine<'tcx>,
) -> InterpResult<'tcx> {
// TODO: for now we bail out on wildcard pointers. Eventually we should
// handle them as much as we can.
let tag = match prov {
ProvenanceExtra::Concrete(tag) => tag,
ProvenanceExtra::Wildcard => return interp_ok(()),
};
let global = machine.borrow_tracker.as_ref().unwrap();
let span = machine.current_user_relevant_span();
self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span)
}
pub fn expose_tag(&mut self, _tag: BorTag) {
// TODO
self.dealloc(prov, alloc_range(Size::ZERO, size), global, alloc_id, span)
}
/// A tag just lost its protector.
@ -109,7 +94,11 @@ impl<'tcx> Tree {
) -> InterpResult<'tcx> {
let span = machine.current_user_relevant_span();
// `None` makes it the magic on-protector-end operation
self.perform_access(tag, None, global, alloc_id, span)
self.perform_access(ProvenanceExtra::Concrete(tag), None, global, alloc_id, span)?;
self.update_exposure_for_protector_release(tag);
interp_ok(())
}
}
@ -239,21 +228,22 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// This pointer doesn't come with an AllocId, so there's no
// memory to do retagging in.
let new_prov = place.ptr().provenance;
trace!(
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
new_tag,
"reborrow of size 0: reusing {:?} (pointee {})",
place.ptr(),
place.layout.ty,
);
log_creation(this, None)?;
// Keep original provenance.
return interp_ok(place.ptr().provenance);
return interp_ok(new_prov);
}
};
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
let orig_tag = match parent_prov {
ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle wildcard pointers
ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle retagging wildcard pointers
ProvenanceExtra::Concrete(tag) => tag,
};
@ -356,7 +346,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
tree_borrows.perform_access(
orig_tag,
parent_prov,
Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
this.machine.borrow_tracker.as_ref().unwrap(),
alloc_id,
@ -589,7 +579,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// uncovers a non-supported `extern static`.
let alloc_extra = this.get_alloc_extra(alloc_id)?;
trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag);
let global = this.machine.borrow_tracker.as_ref().unwrap();
let protected_tags = &global.borrow().protected_tags;
let protected = protected_tags.contains_key(&tag);
alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
}
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
// No tree borrows on these allocations.

View file

@ -53,6 +53,7 @@ enum PermissionPriv {
}
use self::PermissionPriv::*;
use super::foreign_access_skipping::IdempotentForeignAccess;
use super::wildcard::WildcardAccessLevel;
impl PartialOrd for PermissionPriv {
/// PermissionPriv is ordered by the reflexive transitive closure of
@ -372,6 +373,23 @@ impl Permission {
pub fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess {
self.inner.strongest_idempotent_foreign_access(prot)
}
/// Returns the strongest access allowed from a child to this node without
/// causing UB (only considers possible transitions to this permission).
pub fn strongest_allowed_child_access(&self, protected: bool) -> WildcardAccessLevel {
match self.inner {
// Everything except disabled can be accessed by read access.
Disabled => WildcardAccessLevel::None,
// Frozen references cannot be written to by a child.
Frozen => WildcardAccessLevel::Read,
// If the `conflicted` flag is set, then there was a foreign read
// during the function call that is still ongoing (still `protected`),
// this is UB (`noalias` violation).
ReservedFrz { conflicted: true } if protected => WildcardAccessLevel::Read,
// Everything else allows writes.
_ => WildcardAccessLevel::Write,
}
}
}
impl PermTransition {
@ -772,4 +790,32 @@ mod propagation_optimization_checks {
);
}
}
/// Checks that `strongest_allowed_child_access` correctly
/// represents which transitions are possible.
#[test]
fn strongest_allowed_child_access() {
for (permission, protected) in <(Permission, bool)>::exhaustive() {
let strongest_child_access = permission.strongest_allowed_child_access(protected);
let is_read_valid = Permission::perform_access(
AccessKind::Read,
AccessRelatedness::LocalAccess,
permission,
protected,
)
.is_some();
let is_write_valid = Permission::perform_access(
AccessKind::Write,
AccessRelatedness::LocalAccess,
permission,
protected,
)
.is_some();
assert_eq!(is_read_valid, strongest_child_access >= WildcardAccessLevel::Read);
assert_eq!(is_write_valid, strongest_child_access >= WildcardAccessLevel::Write);
}
}
}

View file

@ -18,9 +18,11 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_span::Span;
use smallvec::SmallVec;
use super::diagnostics::AccessCause;
use super::wildcard::WildcardState;
use crate::borrow_tracker::tree_borrows::Permission;
use crate::borrow_tracker::tree_borrows::diagnostics::{
self, NodeDebugInfo, TbError, TransitionError,
self, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error,
};
use crate::borrow_tracker::tree_borrows::foreign_access_skipping::IdempotentForeignAccess;
use crate::borrow_tracker::tree_borrows::perms::PermTransition;
@ -30,7 +32,7 @@ use crate::*;
mod tests;
/// Data for a single *location*.
/// Data for a reference at single *location*.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) struct LocationState {
/// A location is "accessed" when it is child-accessed for the first time (and the initial
@ -81,6 +83,49 @@ impl LocationState {
self.permission
}
/// Performs an access on this index and updates node,
/// perm and wildcard_state to reflect the transition.
fn perform_transition(
&mut self,
idx: UniIndex,
nodes: &mut UniValMap<Node>,
wildcard_accesses: &mut UniValMap<WildcardState>,
access_kind: AccessKind,
access_cause: AccessCause,
access_range: Option<AllocRange>,
relatedness: AccessRelatedness,
span: Span,
location_range: Range<u64>,
protected: bool,
) -> Result<(), TransitionError> {
// Call this function now (i.e. only if we know `relatedness`), which
// ensures it is only called when `skip_if_known_noop` returns
// `Recurse`, due to the contract of `traverse_this_parents_children_other`.
self.record_new_access(access_kind, relatedness);
let transition = self.perform_access(access_kind, relatedness, protected)?;
if !transition.is_noop() {
let node = nodes.get_mut(idx).unwrap();
// Record the event as part of the history.
node.debug_info.history.push(diagnostics::Event {
transition,
is_foreign: relatedness.is_foreign(),
access_cause,
access_range,
transition_range: location_range,
span,
});
// We need to update the wildcard state, if the permission
// of an exposed pointer changes.
if node.is_exposed {
let access_type = self.permission.strongest_allowed_child_access(protected);
WildcardState::update_exposure(idx, access_type, nodes, wildcard_accesses);
}
}
Ok(())
}
/// Apply the effect of an access to one location, including
/// - applying `Permission::perform_access` to the inner `Permission`,
/// - emitting protector UB if the location is accessed,
@ -211,30 +256,44 @@ impl fmt::Display for LocationState {
Ok(())
}
}
/// The state of the full tree for a particular location: for all nodes, the local permissions
/// of that node, and the tracking for wildcard accesses.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LocationTree {
/// Maps a tag to a perm, with possible lazy initialization.
///
/// NOTE: not all tags registered in `Tree::nodes` are necessarily in all
/// ranges of `perms`, because `perms` is in part lazily initialized.
/// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely
/// `unwrap` any `perm.get(key)`.
///
/// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)`
pub perms: UniValMap<LocationState>,
/// Maps a tag and a location to its wildcard access tracking information,
/// with possible lazy initialization.
///
/// If this allocation doesn't have any exposed nodes, then this map doesn't get
/// initialized. This way we only need to allocate the map if we need it.
///
/// NOTE: same guarantees on entry initialization as for `perms`.
pub wildcard_accesses: UniValMap<WildcardState>,
}
/// Tree structure with both parents and children since we want to be
/// able to traverse the tree efficiently in both directions.
#[derive(Clone, Debug)]
pub struct Tree {
/// Mapping from tags to keys. The key obtained can then be used in
/// any of the `UniValMap` relative to this allocation, i.e. both the
/// `nodes` and `rperms` of the same `Tree`.
/// any of the `UniValMap` relative to this allocation, i.e.
/// `nodes`, `LocationTree::perms` and `LocationTree::wildcard_accesses`
/// of the same `Tree`.
/// The parent-child relationship in `Node` is encoded in terms of these same
/// keys, so traversing the entire tree needs exactly one access to
/// `tag_mapping`.
pub(super) tag_mapping: UniKeyMap<BorTag>,
/// All nodes of this tree.
pub(super) nodes: UniValMap<Node>,
/// Maps a tag and a location to a perm, with possible lazy
/// initialization.
///
/// NOTE: not all tags registered in `nodes` are necessarily in all
/// ranges of `rperms`, because `rperms` is in part lazily initialized.
/// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely
/// `unwrap` any `perm.get(key)`.
///
/// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)`
pub(super) rperms: DedupRangeMap<UniValMap<LocationState>>,
/// Associates with each location its state and wildcard access tracking.
pub(super) locations: DedupRangeMap<LocationTree>,
/// The index of the root node.
pub(super) root: UniIndex,
}
@ -260,7 +319,9 @@ pub(super) struct Node {
/// in cases where there is no location state yet. See `foreign_access_skipping.rs`,
/// and `LocationState::idempotent_foreign_access` for more information
default_initial_idempotent_foreign_access: IdempotentForeignAccess,
/// Some extra information useful only for debugging purposes
/// Whether a wildcard access could happen through this node.
pub is_exposed: bool,
/// Some extra information useful only for debugging purposes.
pub debug_info: NodeDebugInfo,
}
@ -273,7 +334,7 @@ struct NodeAppArgs<'visit> {
/// The node map of this tree.
nodes: &'visit mut UniValMap<Node>,
/// The permissions map of this tree.
perms: &'visit mut UniValMap<LocationState>,
loc: &'visit mut LocationTree,
}
/// Data given to the error handler
struct ErrHandlerArgs<'node, InErr> {
@ -293,7 +354,7 @@ struct ErrHandlerArgs<'node, InErr> {
struct TreeVisitor<'tree> {
tag_mapping: &'tree UniKeyMap<BorTag>,
nodes: &'tree mut UniValMap<Node>,
perms: &'tree mut UniValMap<LocationState>,
loc: &'tree mut LocationTree,
}
/// Whether to continue exploring the children recursively or not.
@ -350,7 +411,7 @@ where
idx: UniIndex,
rel_pos: AccessRelatedness,
) -> ContinueTraversal {
let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms };
let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc };
(self.f_continue)(&args)
}
@ -360,14 +421,15 @@ where
idx: UniIndex,
rel_pos: AccessRelatedness,
) -> Result<(), OutErr> {
(self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms })
.map_err(|error_kind| {
(self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }).map_err(
|error_kind| {
(self.err_builder)(ErrHandlerArgs {
error_kind,
conflicting_info: &this.nodes.get(idx).unwrap().debug_info,
accessed_info: &this.nodes.get(self.initial).unwrap().debug_info,
})
})
},
)
}
fn go_upwards_from_accessed(
@ -577,6 +639,7 @@ impl Tree {
default_initial_perm: root_default_perm,
// The root may never be skipped, all accesses will be local.
default_initial_idempotent_foreign_access: IdempotentForeignAccess::None,
is_exposed: false,
debug_info,
},
);
@ -595,9 +658,10 @@ impl Tree {
IdempotentForeignAccess::None,
),
);
DedupRangeMap::new(size, perms)
let wildcard_accesses = UniValMap::default();
DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses })
};
Self { root: root_idx, nodes, rperms, tag_mapping }
Self { root: root_idx, nodes, locations: rperms, tag_mapping }
}
}
@ -633,11 +697,13 @@ impl<'tcx> Tree {
children: SmallVec::default(),
default_initial_perm: outside_perm,
default_initial_idempotent_foreign_access: default_strongest_idempotent,
is_exposed: false,
debug_info: NodeDebugInfo::new(new_tag, outside_perm, span),
},
);
let parent_node = self.nodes.get_mut(parent_idx).unwrap();
// Register new_tag as a child of parent_tag
self.nodes.get_mut(parent_idx).unwrap().children.push(idx);
parent_node.children.push(idx);
// We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`.
let mut min_sifa = default_strongest_idempotent;
@ -651,11 +717,19 @@ impl<'tcx> Tree {
);
min_sifa = cmp::min(min_sifa, perm.idempotent_foreign_access);
for (_perms_range, perms) in self
.rperms
for (_range, loc) in self
.locations
.iter_mut(Size::from_bytes(start) + base_offset, Size::from_bytes(end - start))
{
perms.insert(idx, perm);
loc.perms.insert(idx, perm);
}
}
// We need to ensure the consistency of the wildcard access tracking data structure.
// For this, we insert the correct entry for this tag based on its parent, if it exists.
for (_range, loc) in self.locations.iter_mut_all() {
if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) {
loc.wildcard_accesses.insert(idx, parent_access.for_new_child());
}
}
@ -689,9 +763,9 @@ impl<'tcx> Tree {
// as the default SIFA for not-yet-initialized locations.
// Record whether we did any change; if not, the invariant is restored and we can stop the traversal.
let mut any_change = false;
for (_, map) in self.rperms.iter_mut_all() {
for (_range, loc) in self.locations.iter_mut_all() {
// Check if this node has a state for this location (or range of locations).
if let Some(perm) = map.get_mut(current) {
if let Some(perm) = loc.perms.get_mut(current) {
// Update the per-location SIFA, recording if it changed.
any_change |=
perm.idempotent_foreign_access.ensure_no_stronger_than(strongest_allowed);
@ -720,28 +794,37 @@ impl<'tcx> Tree {
/// - the absence of Strong Protectors anywhere in the allocation
pub fn dealloc(
&mut self,
tag: BorTag,
prov: ProvenanceExtra,
access_range: AllocRange,
global: &GlobalState,
alloc_id: AllocId, // diagnostics
span: Span, // diagnostics
) -> InterpResult<'tcx> {
self.perform_access(
tag,
prov,
Some((access_range, AccessKind::Write, diagnostics::AccessCause::Dealloc)),
global,
alloc_id,
span,
)?;
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) {
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
// Check if this breaks any strong protector.
// (Weak protectors are already handled by `perform_access`.)
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
// The order in which we check if any nodes are invalidated only
// matters to diagnostics, so we use the root as a default tag.
let start_tag = match prov {
ProvenanceExtra::Concrete(tag) => tag,
ProvenanceExtra::Wildcard => self.nodes.get(self.root).unwrap().tag,
};
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
.traverse_this_parents_children_other(
tag,
// visit all children, skipping none
start_tag,
// Visit all children, skipping none.
|_| ContinueTraversal::Recurse,
|args: NodeAppArgs<'_>| -> Result<(), TransitionError> {
let node = args.nodes.get(args.idx).unwrap();
let perm = args.perms.entry(args.idx);
let perm = args.loc.perms.entry(args.idx);
let perm =
perm.get().copied().unwrap_or_else(|| node.default_location_state());
@ -764,9 +847,15 @@ impl<'tcx> Tree {
conflicting_info,
access_cause: diagnostics::AccessCause::Dealloc,
alloc_id,
error_offset: perms_range.start,
error_offset: loc_range.start,
error_kind,
accessed_info,
accessed_info: match prov {
ProvenanceExtra::Concrete(_) => Some(accessed_info),
// `accessed_info` contains the info of `start_tag`.
// On a wildcard access this is not the info of the accessed tag
// (as we don't know the accessed tag).
ProvenanceExtra::Wildcard => None,
},
}
.build()
},
@ -795,12 +884,15 @@ impl<'tcx> Tree {
/// - recording the history.
pub fn perform_access(
&mut self,
tag: BorTag,
prov: ProvenanceExtra,
access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>,
global: &GlobalState,
alloc_id: AllocId, // diagnostics
span: Span, // diagnostics
) -> InterpResult<'tcx> {
let ProvenanceExtra::Concrete(tag) = prov else {
return self.perform_wildcard_access(access_range_and_kind, global, alloc_id, span);
};
use std::ops::Range;
// Performs the per-node work:
// - insert the permission if it does not exist
@ -814,7 +906,7 @@ impl<'tcx> Tree {
// the `RangeMap` on which we are currently working).
let node_skipper = |access_kind: AccessKind, args: &NodeAppArgs<'_>| -> ContinueTraversal {
let node = args.nodes.get(args.idx).unwrap();
let perm = args.perms.get(args.idx);
let perm = args.loc.perms.get(args.idx);
let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
old_state.skip_if_known_noop(access_kind, args.rel_pos)
@ -825,29 +917,23 @@ impl<'tcx> Tree {
args: NodeAppArgs<'_>|
-> Result<(), TransitionError> {
let node = args.nodes.get_mut(args.idx).unwrap();
let mut perm = args.perms.entry(args.idx);
let mut perm = args.loc.perms.entry(args.idx);
let old_state = perm.or_insert(node.default_location_state());
// Call this function now, which ensures it is only called when
// `skip_if_known_noop` returns `Recurse`, due to the contract of
// `traverse_this_parents_children_other`.
old_state.record_new_access(access_kind, args.rel_pos);
let state = perm.or_insert(node.default_location_state());
let protected = global.borrow().protected_tags.contains_key(&node.tag);
let transition = old_state.perform_access(access_kind, args.rel_pos, protected)?;
// Record the event as part of the history
if !transition.is_noop() {
node.debug_info.history.push(diagnostics::Event {
transition,
is_foreign: args.rel_pos.is_foreign(),
access_cause,
access_range: access_range_and_kind.map(|x| x.0),
transition_range: perms_range,
span,
});
}
Ok(())
state.perform_transition(
args.idx,
args.nodes,
&mut args.loc.wildcard_accesses,
access_kind,
access_cause,
/* access_range */ access_range_and_kind.map(|x| x.0),
args.rel_pos,
span,
perms_range,
protected,
)
};
// Error handler in case `node_app` goes wrong.
@ -863,7 +949,7 @@ impl<'tcx> Tree {
alloc_id,
error_offset: perms_range.start,
error_kind,
accessed_info,
accessed_info: Some(accessed_info),
}
.build()
};
@ -871,14 +957,13 @@ impl<'tcx> Tree {
if let Some((access_range, access_kind, access_cause)) = access_range_and_kind {
// Default branch: this is a "normal" access through a known range.
// We iterate over affected locations and traverse the tree for each of them.
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size)
{
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
.traverse_this_parents_children_other(
tag,
|args| node_skipper(access_kind, args),
|args| node_app(perms_range.clone(), access_kind, access_cause, args),
|args| err_handler(perms_range.clone(), access_cause, args),
|args| node_app(loc_range.clone(), access_kind, access_cause, args),
|args| err_handler(loc_range.clone(), access_cause, args),
)?;
}
} else {
@ -891,21 +976,21 @@ impl<'tcx> Tree {
// See the test case `returned_mut_is_usable` from
// `tests/pass/tree_borrows/tree-borrows.rs` for an example of
// why this is important.
for (perms_range, perms) in self.rperms.iter_mut_all() {
for (loc_range, loc) in self.locations.iter_mut_all() {
let idx = self.tag_mapping.get(&tag).unwrap();
// Only visit accessed permissions
if let Some(p) = perms.get(idx)
if let Some(p) = loc.perms.get(idx)
&& let Some(access_kind) = p.permission.protector_end_access()
&& p.accessed
{
let access_cause = diagnostics::AccessCause::FnExit(access_kind);
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
.traverse_nonchildren(
tag,
|args| node_skipper(access_kind, args),
|args| node_app(perms_range.clone(), access_kind, access_cause, args),
|args| err_handler(perms_range.clone(), access_cause, args),
)?;
tag,
|args| node_skipper(access_kind, args),
|args| node_app(loc_range.clone(), access_kind, access_cause, args),
|args| err_handler(loc_range.clone(), access_cause, args),
)?;
}
}
}
@ -921,7 +1006,7 @@ impl Tree {
// merge some adjacent ranges that were made equal by the removal of some
// tags (this does not necessarily mean that they have identical internal representations,
// see the `PartialEq` impl for `UniValMap`)
self.rperms.merge_adjacent_thorough();
self.locations.merge_adjacent_thorough();
}
/// Checks if a node is useless and should be GC'ed.
@ -953,10 +1038,14 @@ impl Tree {
let child = self.nodes.get(child_idx).unwrap();
// Check that for that one child, `can_be_replaced_by_child` holds for the permission
// on all locations.
for (_, data) in self.rperms.iter_all() {
let parent_perm =
data.get(idx).map(|x| x.permission).unwrap_or_else(|| node.default_initial_perm);
let child_perm = data
for (_range, loc) in self.locations.iter_all() {
let parent_perm = loc
.perms
.get(idx)
.map(|x| x.permission)
.unwrap_or_else(|| node.default_initial_perm);
let child_perm = loc
.perms
.get(child_idx)
.map(|x| x.permission)
.unwrap_or_else(|| child.default_initial_perm);
@ -980,8 +1069,9 @@ impl Tree {
// before we can safely apply `UniKeyMap::remove` to truly remove
// this tag from the `tag_mapping`.
let node = self.nodes.remove(this).unwrap();
for (_perms_range, perms) in self.rperms.iter_mut_all() {
perms.remove(this);
for (_range, loc) in self.locations.iter_mut_all() {
loc.perms.remove(this);
loc.wildcard_accesses.remove(this);
}
self.tag_mapping.remove(&node.tag);
}
@ -1058,6 +1148,126 @@ impl Tree {
}
}
/// Methods for wildcard accesses.
impl<'tcx> Tree {
/// Analogous to `perform_access`, but we do not know from which exposed
/// reference the access happens.
pub fn perform_wildcard_access(
&mut self,
access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>,
global: &GlobalState,
alloc_id: AllocId, // diagnostics
span: Span, // diagnostics
) -> InterpResult<'tcx> {
#[cfg(feature = "expensive-consistency-checks")]
self.verify_wildcard_consistency(global);
if let Some((access_range, access_kind, access_cause)) = access_range_and_kind {
// This does a traversal starting from the root through the tree updating
// the permissions of each node.
// The difference to `perform_access` is that we take the access
// relatedness from the wildcard tracking state of the node instead of
// from the visitor itself.
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
let root_tag = self.nodes.get(self.root).unwrap().tag;
TreeVisitor { loc, nodes: &mut self.nodes, tag_mapping: &self.tag_mapping }
.traverse_this_parents_children_other(
root_tag,
|args: &NodeAppArgs<'_>| -> ContinueTraversal {
let node = args.nodes.get(args.idx).unwrap();
let perm = args.loc.perms.get(args.idx);
let wildcard_state = args
.loc
.wildcard_accesses
.get(args.idx)
.cloned()
.unwrap_or_default();
let old_state =
perm.copied().unwrap_or_else(|| node.default_location_state());
// If we know where, relative to this node, the wildcard access occurs,
// then check if we can skip the entire subtree.
if let Some(relatedness) =
wildcard_state.access_relatedness(access_kind)
&& let Some(relatedness) = relatedness.to_relatedness()
{
// We can use the usual SIFA machinery to skip nodes.
old_state.skip_if_known_noop(access_kind, relatedness)
} else {
ContinueTraversal::Recurse
}
},
|args| {
let node = args.nodes.get_mut(args.idx).unwrap();
let mut entry = args.loc.perms.entry(args.idx);
let perm = entry.or_insert(node.default_location_state());
let protected = global.borrow().protected_tags.contains_key(&node.tag);
let Some(wildcard_relatedness) = args
.loc
.wildcard_accesses
.get(args.idx)
.and_then(|s| s.access_relatedness(access_kind))
else {
// There doesn't exist a valid exposed reference for this access to
// happen through.
// If this fails for one id, then it fails for all ids so this.
// Since we always check the root first, this means it should always
// fail on the root.
assert_eq!(self.root, args.idx);
return Err(no_valid_exposed_references_error(
alloc_id,
loc_range.start,
access_cause,
));
};
let Some(relatedness) = wildcard_relatedness.to_relatedness() else {
// If the access type is Either, then we do not apply any transition
// to this node, but we still update each of its children.
// This is an imprecision! In the future, maybe we can still do some sort
// of best-effort update here.
return Ok(());
};
// We know the exact relatedness, so we can actually do precise checks.
perm.perform_transition(
args.idx,
args.nodes,
&mut args.loc.wildcard_accesses,
access_kind,
access_cause,
Some(access_range),
relatedness,
span,
loc_range.clone(),
protected,
)
.map_err(|trans| {
let node = args.nodes.get(args.idx).unwrap();
TbError {
conflicting_info: &node.debug_info,
access_cause,
alloc_id,
error_offset: loc_range.start,
error_kind: trans,
accessed_info: None,
}
.build()
})
},
|err| err.error_kind,
)?;
}
} else {
// This is for the special access when a protector gets released.
// Wildcard pointers are never protected, so this is unreachable.
unreachable!()
};
interp_ok(())
}
}
impl Node {
pub fn default_location_state(&self) -> LocationState {
LocationState::new_non_accessed(
@ -1071,7 +1281,15 @@ impl VisitProvenance for Tree {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
// To ensure that the root never gets removed, we visit it
// (the `root` node of `Tree` is not an `Option<_>`)
visit(None, Some(self.nodes.get(self.root).unwrap().tag))
visit(None, Some(self.nodes.get(self.root).unwrap().tag));
// We also need to keep around any exposed tags through which
// an access could still happen.
for (_id, node) in self.nodes.iter() {
if node.is_exposed {
visit(None, Some(node.tag))
}
}
}
}

View file

@ -12,6 +12,7 @@
#![allow(dead_code)]
use std::fmt::Debug;
use std::hash::Hash;
use std::mem;
@ -20,10 +21,15 @@ use rustc_data_structures::fx::FxHashMap;
use crate::helpers::ToUsize;
/// Intermediate key between a UniKeyMap and a UniValMap.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct UniIndex {
idx: u32,
}
impl Debug for UniIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.idx.fmt(f)
}
}
/// From K to UniIndex
#[derive(Debug, Clone, Default)]
@ -201,6 +207,19 @@ impl<V> UniValMap<V> {
mem::swap(&mut res, &mut self.data[idx.idx.to_usize()]);
res
}
/// Returns true if the map is empty.
pub fn is_empty(&self) -> bool {
self.data.iter().all(|v| v.is_none())
}
/// Iterates over all key-value pairs in the map.
pub fn iter(&self) -> impl Iterator<Item = (UniIndex, &V)> {
self.data
.iter()
.enumerate()
.filter_map(|(i, v)| v.as_ref().map(|r| (UniIndex { idx: i.try_into().unwrap() }, r)))
}
}
/// An access to a single value of the map.

View file

@ -0,0 +1,519 @@
use std::cmp::max;
use std::fmt::Debug;
use super::Tree;
use super::tree::{AccessRelatedness, Node};
use super::unimap::{UniIndex, UniValMap};
use crate::BorTag;
use crate::borrow_tracker::AccessKind;
#[cfg(feature = "expensive-consistency-checks")]
use crate::borrow_tracker::GlobalState;
/// Represents the maximum access level that is possible.
///
/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters:
/// None < Read < Write. Do not change that order.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum WildcardAccessLevel {
#[default]
None,
Read,
Write,
}
/// Where the access happened relative to the current node.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WildcardAccessRelatedness {
/// The access definitively happened through a local node.
LocalAccess,
/// The access definitively happened through a foreign node.
ForeignAccess,
/// We do not know if the access is foreign or local.
EitherAccess,
}
impl WildcardAccessRelatedness {
pub fn to_relatedness(self) -> Option<AccessRelatedness> {
match self {
Self::LocalAccess => Some(AccessRelatedness::LocalAccess),
Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess),
Self::EitherAccess => None,
}
}
}
/// State per location per node keeping track of where relative to this
/// node exposed nodes are and what access permissions they have.
///
/// Designed to be completely determined by its parent, siblings and
/// direct children's max_local_access/max_foreign_access.
#[derive(Clone, Default, PartialEq, Eq)]
pub struct WildcardState {
/// How many of this node's direct children have `max_local_access()==Write`.
child_writes: u16,
/// How many of this node's direct children have `max_local_access()>=Read`.
child_reads: u16,
/// The maximum access level that could happen from an exposed node
/// that is foreign to this node.
///
/// This is calculated as the `max()` of the parent's `max_foreign_access`,
/// `exposed_as` and the siblings' `max_local_access()`.
max_foreign_access: WildcardAccessLevel,
/// At what access level this node itself is exposed.
exposed_as: WildcardAccessLevel,
}
impl Debug for WildcardState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WildcardState")
.field("child_r/w", &(self.child_reads, self.child_writes))
.field("foreign", &self.max_foreign_access)
.field("exposed_as", &self.exposed_as)
.finish()
}
}
impl WildcardState {
/// The maximum access level that could happen from an exposed
/// node that is local to this node.
fn max_local_access(&self) -> WildcardAccessLevel {
use WildcardAccessLevel::*;
max(
self.exposed_as,
if self.child_writes > 0 {
Write
} else if self.child_reads > 0 {
Read
} else {
None
},
)
}
/// From where relative to the node with this wildcard info a read or write access could happen.
pub fn access_relatedness(&self, kind: AccessKind) -> Option<WildcardAccessRelatedness> {
match kind {
AccessKind::Read => self.read_access_relatedness(),
AccessKind::Write => self.write_access_relatedness(),
}
}
/// From where relative to the node with this wildcard info a read access could happen.
fn read_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
let has_foreign = self.max_foreign_access >= WildcardAccessLevel::Read;
let has_local = self.max_local_access() >= WildcardAccessLevel::Read;
use WildcardAccessRelatedness as E;
match (has_foreign, has_local) {
(true, true) => Some(E::EitherAccess),
(true, false) => Some(E::ForeignAccess),
(false, true) => Some(E::LocalAccess),
(false, false) => None,
}
}
/// From where relative to the node with this wildcard info a write access could happen.
fn write_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
let has_foreign = self.max_foreign_access == WildcardAccessLevel::Write;
let has_local = self.max_local_access() == WildcardAccessLevel::Write;
use WildcardAccessRelatedness as E;
match (has_foreign, has_local) {
(true, true) => Some(E::EitherAccess),
(true, false) => Some(E::ForeignAccess),
(false, true) => Some(E::LocalAccess),
(false, false) => None,
}
}
/// Gets the access tracking information for a new child node of a parent with this
/// wildcard info.
/// The new node doesn't have any child reads/writes, but calculates `max_foreign_access`
/// from its parent.
pub fn for_new_child(&self) -> Self {
Self {
max_foreign_access: max(self.max_foreign_access, self.max_local_access()),
..Default::default()
}
}
/// Pushes the nodes of `children` onto the stack who's `max_foreign_access`
/// needs to be updated.
///
/// * `children`: A list of nodes with the same parent. `children` doesn't
/// necessarily have to contain all children of parent, but can just be
/// a subset.
///
/// * `child_reads`, `child_writes`: How many of `children` have `max_local_access()`
/// of at least `read`/`write`
///
/// * `new_foreign_access`, `old_foreign_access`:
/// The max possible access level that is foreign to all `children`
/// (i.e., it is not local to *any* of them).
/// This can be calculated as the max of the parent's `exposed_as()`, `max_foreign_access`
/// and of all `max_local_access()` of any nodes with the same parent that are
/// not listed in `children`.
///
/// This access level changed from `old` to `new`, which is why we need to
/// update `children`.
fn push_relevant_children(
stack: &mut Vec<(UniIndex, WildcardAccessLevel)>,
new_foreign_access: WildcardAccessLevel,
old_foreign_access: WildcardAccessLevel,
child_reads: u16,
child_writes: u16,
children: impl Iterator<Item = UniIndex>,
wildcard_accesses: &UniValMap<WildcardState>,
) {
use WildcardAccessLevel::*;
// Nothing changed so we don't need to update anything.
if new_foreign_access == old_foreign_access {
return;
}
// We need to consider that the children's `max_local_access()` affect each
// other's `max_foreign_access`, but do not affect their own `max_foreign_access`.
// The new `max_foreign_acces` for children with `max_local_access()==Write`.
let write_foreign_access = max(
new_foreign_access,
if child_writes > 1 {
// There exists at least one more child with exposed write access.
// This means that a foreign write through that node is possible.
Write
} else if child_reads > 1 {
// There exists at least one more child with exposed read access,
// but no other with write access.
// This means that a foreign read but no write through that node
// is possible.
Read
} else {
// There are no other nodes with read or write access.
// This means no foreign writes through other children are possible.
None
},
);
// The new `max_foreign_acces` for children with `max_local_access()==Read`.
let read_foreign_access = max(
new_foreign_access,
if child_writes > 0 {
// There exists at least one child with write access (and it's not this one).
Write
} else if child_reads > 1 {
// There exists at least one more child with exposed read access,
// but no other with write access.
Read
} else {
// There are no other nodes with read or write access,
None
},
);
// The new `max_foreign_acces` for children with `max_local_access()==None`.
let none_foreign_access = max(
new_foreign_access,
if child_writes > 0 {
// There exists at least one child with write access (and it's not this one).
Write
} else if child_reads > 0 {
// There exists at least one child with read access (and it's not this one),
// but none with write access.
Read
} else {
// No children are exposed as read or write.
None
},
);
stack.extend(children.filter_map(|child| {
let state = wildcard_accesses.get(child).cloned().unwrap_or_default();
let new_foreign_access = match state.max_local_access() {
Write => write_foreign_access,
Read => read_foreign_access,
None => none_foreign_access,
};
if new_foreign_access != state.max_foreign_access {
Some((child, new_foreign_access))
} else {
Option::None
}
}));
}
/// Update the tracking information of a tree, to reflect that the node specified by `id` is
/// now exposed with `new_exposed_as`.
///
/// Propagates the Willard access information over the tree. This needs to be called every
/// time the access level of an exposed node changes, to keep the state in sync with
/// the rest of the tree.
pub fn update_exposure(
id: UniIndex,
new_exposed_as: WildcardAccessLevel,
nodes: &UniValMap<Node>,
wildcard_accesses: &mut UniValMap<WildcardState>,
) {
let mut entry = wildcard_accesses.entry(id);
let src_state = entry.or_insert(Default::default());
let old_exposed_as = src_state.exposed_as;
// If the exposure doesn't change, then we don't need to update anything.
if old_exposed_as == new_exposed_as {
return;
}
let src_old_local_access = src_state.max_local_access();
src_state.exposed_as = new_exposed_as;
let src_new_local_access = src_state.max_local_access();
// Stack of nodes for which the max_foreign_access field needs to be updated.
// Will be filled with the children of this node and its parents children before
// we begin downwards traversal.
let mut stack: Vec<(UniIndex, WildcardAccessLevel)> = Vec::new();
// Add the direct children of this node to the stack.
{
let node = nodes.get(id).unwrap();
Self::push_relevant_children(
&mut stack,
// new_foreign_access
max(src_state.max_foreign_access, new_exposed_as),
// old_foreign_access
max(src_state.max_foreign_access, old_exposed_as),
// Consider all children.
src_state.child_reads,
src_state.child_writes,
node.children.iter().copied(),
wildcard_accesses,
);
}
// We need to propagate the tracking info up the tree, for this we traverse
// up the parents.
// We can skip propagating info to the parent and siblings of a node if its
// access didn't change.
{
// The child from which we came.
let mut child = id;
// This is the `max_local_access()` of the child we came from, before
// this update...
let mut old_child_access = src_old_local_access;
// and after this update.
let mut new_child_access = src_new_local_access;
while let Some(parent_id) = nodes.get(child).unwrap().parent {
let parent_node = nodes.get(parent_id).unwrap();
let mut entry = wildcard_accesses.entry(parent_id);
let parent_state = entry.or_insert(Default::default());
let old_parent_local_access = parent_state.max_local_access();
use WildcardAccessLevel::*;
// Updating this node's tracking state for its children.
match (old_child_access, new_child_access) {
(None | Read, Write) => parent_state.child_writes += 1,
(Write, None | Read) => parent_state.child_writes -= 1,
_ => {}
}
match (old_child_access, new_child_access) {
(None, Read | Write) => parent_state.child_reads += 1,
(Read | Write, None) => parent_state.child_reads -= 1,
_ => {}
}
let new_parent_local_access = parent_state.max_local_access();
{
// We need to update the `max_foreign_access` of `child`'s
// siblings. For this we can reuse the `push_relevant_children`
// function.
//
// We pass it just the siblings without child itself. Since
// `child`'s `max_local_access()` is foreign to all of its
// siblings we can pass it as part of the foreign access.
let parent_access =
max(parent_state.exposed_as, parent_state.max_foreign_access);
// This is how many of `child`'s siblings have read/write local access.
// If `child` itself has access, then we need to subtract its access from the count.
let sibling_reads =
parent_state.child_reads - if new_child_access >= Read { 1 } else { 0 };
let sibling_writes =
parent_state.child_writes - if new_child_access >= Write { 1 } else { 0 };
Self::push_relevant_children(
&mut stack,
// new_foreign_access
max(parent_access, new_child_access),
// old_foreign_access
max(parent_access, old_child_access),
// Consider only siblings of child.
sibling_reads,
sibling_writes,
parent_node.children.iter().copied().filter(|id| child != *id),
wildcard_accesses,
);
}
if old_parent_local_access == new_parent_local_access {
// We didn't change `max_local_access()` for parent, so we don't need to propagate further upwards.
break;
}
old_child_access = old_parent_local_access;
new_child_access = new_parent_local_access;
child = parent_id;
}
}
// Traverses down the tree to update max_foreign_access fields of children and cousins who need to be updated.
while let Some((id, new_access)) = stack.pop() {
let node = nodes.get(id).unwrap();
let mut entry = wildcard_accesses.entry(id);
let state = entry.or_insert(Default::default());
let old_access = state.max_foreign_access;
state.max_foreign_access = new_access;
Self::push_relevant_children(
&mut stack,
// new_foreign_access
max(state.exposed_as, new_access),
// old_foreign_access
max(state.exposed_as, old_access),
// Consider all children.
state.child_reads,
state.child_writes,
node.children.iter().copied(),
wildcard_accesses,
);
}
}
}
impl Tree {
/// Marks the tag as exposed & updates the wildcard tracking data structure
/// to represent its access level.
/// Also takes as an argument whether the tag is protected or not.
pub fn expose_tag(&mut self, tag: BorTag, protected: bool) {
let id = self.tag_mapping.get(&tag).unwrap();
let node = self.nodes.get_mut(id).unwrap();
node.is_exposed = true;
let node = self.nodes.get(id).unwrap();
// When the first tag gets exposed then we initialize the
// wildcard state for every node and location in the tree.
for (_, loc) in self.locations.iter_mut_all() {
let perm = loc
.perms
.get(id)
.map(|p| p.permission())
.unwrap_or_else(|| node.default_location_state().permission());
let access_type = perm.strongest_allowed_child_access(protected);
WildcardState::update_exposure(
id,
access_type,
&self.nodes,
&mut loc.wildcard_accesses,
);
}
}
/// This updates the wildcard tracking data structure to reflect the release of
/// the protector on `tag`.
pub(super) fn update_exposure_for_protector_release(&mut self, tag: BorTag) {
let idx = self.tag_mapping.get(&tag).unwrap();
// We check if the node is already exposed, as we don't want to expose any
// nodes which aren't already exposed.
if self.nodes.get(idx).unwrap().is_exposed {
// Updates the exposure to the new permission on every location.
self.expose_tag(tag, /* protected */ false);
}
}
}
#[cfg(feature = "expensive-consistency-checks")]
impl Tree {
/// Checks that the wildcard tracking data structure is internally consistent and
/// has the correct `exposed_as` values.
pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
let protected_tags = &global.borrow().protected_tags;
for (_, loc) in self.locations.iter_all() {
let wildcard_accesses = &loc.wildcard_accesses;
let perms = &loc.perms;
// Checks if accesses is empty.
if wildcard_accesses.is_empty() {
return;
}
for (id, node) in self.nodes.iter() {
let state = wildcard_accesses.get(id).unwrap();
let expected_exposed_as = if node.is_exposed {
let perm = perms.get(id).unwrap();
perm.permission()
.strongest_allowed_child_access(protected_tags.contains_key(&node.tag))
} else {
WildcardAccessLevel::None
};
// The foreign wildcard accesses possible at a node are determined by which
// accesses can originate from their siblings, their parent, and from above
// their parent.
let expected_max_foreign_access = if let Some(parent) = node.parent {
let parent_node = self.nodes.get(parent).unwrap();
let parent_state = wildcard_accesses.get(parent).unwrap();
let max_sibling_access = parent_node
.children
.iter()
.copied()
.filter(|child| *child != id)
.map(|child| {
let state = wildcard_accesses.get(child).unwrap();
state.max_local_access()
})
.fold(WildcardAccessLevel::None, max);
max_sibling_access
.max(parent_state.max_foreign_access)
.max(parent_state.exposed_as)
} else {
WildcardAccessLevel::None
};
// Count how many children can be the source of wildcard reads or writes
// (either directly, or via their children).
let child_accesses = node.children.iter().copied().map(|child| {
let state = wildcard_accesses.get(child).unwrap();
state.max_local_access()
});
let expected_child_reads =
child_accesses.clone().filter(|a| *a >= WildcardAccessLevel::Read).count();
let expected_child_writes =
child_accesses.filter(|a| *a >= WildcardAccessLevel::Write).count();
assert_eq!(
expected_exposed_as, state.exposed_as,
"tag {:?} (id:{id:?}) should be exposed as {expected_exposed_as:?} but is exposed as {:?}",
node.tag, state.exposed_as
);
assert_eq!(
expected_max_foreign_access, state.max_foreign_access,
"expected {:?}'s (id:{id:?}) max_foreign_access to be {:?} instead of {:?}",
node.tag, expected_max_foreign_access, state.max_foreign_access
);
let child_reads: usize = state.child_reads.into();
assert_eq!(
expected_child_reads, child_reads,
"expected {:?}'s (id:{id:?}) child_reads to be {} instead of {}",
node.tag, expected_child_reads, child_reads
);
let child_writes: usize = state.child_writes.into();
assert_eq!(
expected_child_writes, child_writes,
"expected {:?}'s (id:{id:?}) child_writes to be {} instead of {}",
node.tag, expected_child_writes, child_writes
);
}
}
}
}

View file

@ -181,13 +181,13 @@ impl EpollInterestTable {
.borrow_mut()
.extract_if(range_for_id(id), |_, _| true)
// Consume the iterator.
.for_each(|_| ());
.for_each(drop);
epoll
.ready_set
.borrow_mut()
.extract_if(range_for_id(id), |_| true)
// Consume the iterator.
.for_each(|_| ());
.for_each(drop);
}
}
}

View file

@ -0,0 +1,21 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that deallocation through a wildcard ref fails,
/// if all exposed references are disabled.
pub fn main() {
use std::alloc::Layout;
let x = unsafe { std::alloc::alloc_zeroed(Layout::new::<u32>()) as *mut u32 };
let ref1 = unsafe { &mut *x };
let ref2 = unsafe { &mut *x };
let int = ref1 as *mut u32 as usize;
let wild = int as *mut u32;
// Disables ref1 and therefore also wild.
*ref2 = 14;
// Tries to dealloc through a wildcard reference even though all exposed
// references are disabled.
unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) }; //~ ERROR: /deallocation through <wildcard> .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/dealloc.rs:LL:CC
|
LL | unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,20 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
#[path = "../../../utils/mod.rs"]
mod utils;
/// Checks that the garbage collector doesn't remove any exposed tags.
fn main() {
let mut _x: u32 = 4;
let int = {
let y = &_x;
y as *const u32 as usize
};
// If y wasn't exposed, this would gc it.
utils::run_provenance_gc();
// This should disable y.
_x = 5;
let wild = int as *const u32;
let _fail = unsafe { *wild }; //~ ERROR: /read access through <wildcard> at .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/gc.rs:LL:CC
|
LL | let _fail = unsafe { *wild };
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,64 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that disabling an exposed reference correctly narrows the
/// possible locations a wildcard access could happen from.
/// Also checks that an access is treated as foreign, if all exposed
/// (non-disabled) references are ancestors.
pub fn main() {
let mut x: u32 = 42;
let ref1 = &mut x;
let int1 = ref1 as *mut u32 as usize;
let ref2 = &mut *ref1;
let ref3 = &mut *ref2;
let _int3 = ref3 as *mut u32 as usize;
// Write through ref3 so that all references are active.
*ref3 = 43;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ref1(Act)* │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref2(Act) │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref3(Act)* │
// │ │
// └────────────┘
// Writes through either ref1 or ref3, which is either a child or foreign
// access to ref2.
unsafe { wild.write(42) };
// Reading from ref2 still works, since the previous access could have been
// through its child.
// This also freezes ref3.
let _x = *ref2;
// We can still write through wild, as there is still the exposed ref1 with
// write permissions under proper exposed provenance, this would be UB as the
// only tag wild can assume to not invalidate ref2 is ref3, which we just
// invalidated.
//
// This disables ref2, ref3.
unsafe { wild.write(43) };
// Fails because ref2 is disabled.
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
LL | let _fail = *ref2;
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
LL | let ref2 = &mut *ref1;
| ^^^^^^^^^^
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
LL | unsafe { wild.write(43) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,47 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks if we correctly determine the correct exposed reference a write
/// access could happen through,
/// if there are also exposed reference through which only a read could happen.
pub fn main() {
let mut x: u32 = 42;
let ref1 = &mut x;
let int1 = ref1 as *mut u32 as usize;
let ref2 = &mut *ref1;
let ref3 = &*ref2;
let _int3 = ref3 as *const u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ref1(Res)* │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref2(Res) │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref3(Frz)* │
// │ │
// └────────────┘
// Writes through ref1 as we cannot write through ref3 since it's frozen.
// Disables ref2, ref3.
unsafe { wild.write(42) };
// ref2 is disabled.
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
LL | let _fail = *ref2;
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
LL | let ref2 = &mut *ref1;
| ^^^^^^^^^^
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
LL | unsafe { wild.write(42) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,38 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks with multiple exposed nodes, that if they are all disabled
/// then no wildcard accesses are possible.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let ref3 = unsafe { &mut *ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├──────────────┬───────────────────┐
// │ │ │ │
// └──────┬─────┘ │ │
// │ │ │
// │ │ │
// ▼ ▼ ▼
// ┌────────────┐ ┌────────────┐ ┌───────────┐
// │ │ │ │ │ │
// │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │
// │ │ │ │ │ │
// └────────────┘ └────────────┘ └───────────┘
// Disables ref1,ref2.
*ref3 = 13;
// Both exposed references are disabled so this fails.
let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs:LL:CC
|
LL | let _fail = unsafe { *wild };
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,38 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that if for a node all exposed references are foreign,
/// that a wildcard access gets treated as foreign to it.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let ref3 = unsafe { &mut *ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├──────────────┬───────────────────┐
// │ │ │ │
// └──────┬─────┘ │ │
// │ │ │
// │ │ │
// ▼ ▼ ▼
// ┌────────────┐ ┌────────────┐ ┌───────────┐
// │ │ │ │ │ │
// │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │
// │ │ │ │ │ │
// └────────────┘ └────────────┘ └───────────┘
// Disables ref3 as both exposed pointers are foreign to it.
unsafe { wild.write(13) };
// Fails because ref3 is disabled.
let _fail = *ref3; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
LL | let _fail = *ref3;
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
LL | let ref3 = unsafe { &mut *ptr_base };
| ^^^^^^^^^^^^^^
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
LL | unsafe { wild.write(13) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,50 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks if a local access gets correctly triggered, if we know that
/// all exposed references are local to a node.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *mut u32 as usize;
let wild = int1 as *mut u32;
// Activates ptr_base.
unsafe { wild.write(41) };
// ┌─────────────┐
// │ │
// │ x (Act) │
// │ │
// └──────┬──────┘
// │
// │
// ▼
// ┌────────────────┐
// │ │
// │ ptr_base (Act) ├──────────┐
// │ │ │
// └──────┬─────────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌────────────┐
// │ │ │ │
// │ ref1(Res)* │ │ ref2(Res)* │
// │ │ │ │
// └────────────┘ └────────────┘
// We read from x causing a foreign access to ptr_base, freezing it
// (as the previous wildcard access has made it active).
let _y = x;
// While both exposed references are still enabled for writes, any write
// through them would cause UB at ptr_base.
unsafe { wild.write(0) }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
}

View file

@ -0,0 +1,32 @@
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
LL | unsafe { wild.write(0) };
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <wildcard> is a child of the conflicting tag <TAG>
= help: the conflicting tag <TAG> has state Frozen which forbids this child write access
help: the conflicting tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
LL | let ptr_base = &mut x as *mut u32;
| ^^^^^^
help: the conflicting tag <TAG> later transitioned to Unique due to a child write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
LL | unsafe { wild.write(41) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
LL | let _y = x;
| ^
= help: this transition corresponds to a loss of write permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,38 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks if we correctly determine the correct exposed reference a write
/// access could happen through, if there are also exposed reference
/// through which only a read access could happen.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &*ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *const u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├──────────────┐
// │ │ │
// └──────┬─────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌────────────┐
// │ │ │ │
// │ ref1(Res)* │ │ ref2(Frz)* │
// │ │ │ │
// └────────────┘ └────────────┘
// Disables ref2 as the only write could happen through ref1.
unsafe { wild.write(13) };
// Fails because ref2 is disabled.
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
LL | let _fail = *ref2;
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
help: the accessed tag <TAG> was created here, in the initial state Frozen
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
LL | let ref2 = unsafe { &*ptr_base };
| ^^^^^^^^^^
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
LL | unsafe { wild.write(13) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of read permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,26 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that wildcard accesses correctly infers the allowed permissions
/// on protected conflicted pointers.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let protect = |arg: &mut u32| {
// Expose arg.
let int = arg as *mut u32 as usize;
let wild = int as *mut u32;
// Does a foreign read to arg marking it as conflicted and making child
// writes UB while it's protected.
let _x = *ref2;
// The only exposed reference (arg) doesn't allow child writes, so this is UB.
unsafe { *wild = 4 }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
};
protect(ref1);
}

View file

@ -0,0 +1,21 @@
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
|
LL | unsafe { *wild = 4 };
| ^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
= note: BACKTRACE:
= note: inside closure at tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
note: inside `main`
--> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
|
LL | protect(ref1);
| ^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,35 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that with only one exposed reference, that if this reference gets
/// disabled no wildcard accesses are possible.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let int1 = ref1 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├───────────┐
// │ │ │
// └──────┬─────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌───────────┐
// │ │ │ │
// │ ref1(Res)* │ │ ref2(Res) │
// │ │ │ │
// └────────────┘ └───────────┘
// Disables ref1.
*ref2 = 13;
// Tries to do a wildcard access through the only exposed reference ref1,
// which is disabled.
let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/single_exposed_disable.rs:LL:CC
|
LL | let _fail = unsafe { *wild };
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,34 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks that with only one exposed reference, wildcard accesses
/// correctly cause foreign accesses.
pub fn main() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let int1 = ref1 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├───────────┐
// │ │ │
// └──────┬─────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌───────────┐
// │ │ │ │
// │ ref1(Res)* │ │ ref2(Res) │
// │ │ │ │
// └────────────┘ └───────────┘
// Write through the wildcard to the only exposed reference ref1,
// disabling ref2.
unsafe { wild.write(13) };
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
}

View file

@ -0,0 +1,25 @@
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
LL | let _fail = *ref2;
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
help: the accessed tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
LL | let ref2 = unsafe { &mut *ptr_base };
| ^^^^^^^^^^^^^^
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
LL | unsafe { wild.write(13) };
| ^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of read and write permissions
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,22 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// Checks if a local access gets correctly triggered during wildcard access,
/// if we know that the only exposed reference is local to the node.
pub fn main() {
let mut x: u32 = 0;
let ref1 = &mut x;
let int = ref1 as *mut u32 as usize;
let wild = int as *mut u32;
// Activates ref1.
unsafe { wild.write(41) };
// Reads from x causing a foreign read on ref1, freezing it
// (because it was active).
let _y = x;
// The only exposed reference (ref1) is frozen, so wildcard writes are UB.
unsafe { wild.write(0) }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/single_exposed_local.rs:LL:CC
|
LL | unsafe { wild.write(0) };
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,11 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
/// If we have only exposed read-only pointers, doing a write through a
/// wildcard ptr should fail.
fn main() {
let mut x = 0;
let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic
let addr = (&x as *const i32).expose_provenance();
let ptr = std::ptr::with_exposed_provenance_mut::<i32>(addr);
unsafe { *ptr = 0 }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
}

View file

@ -0,0 +1,14 @@
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
--> tests/fail/tree_borrows/wildcard/single_exposed_only_ro.rs:LL:CC
|
LL | unsafe { *ptr = 0 };
| ^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: there are no exposed tags which may perform this access here
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -0,0 +1,15 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
//@error-in-other-file: /deallocation through .* is forbidden/
fn inner(x: &mut i32, f: fn(usize)) {
// `f` may mutate, but it may not deallocate!
// `f` takes a raw pointer so that the only protector
// is that on `x`
f(x as *mut i32 as usize)
}
fn main() {
inner(Box::leak(Box::new(0)), |raw| {
drop(unsafe { Box::from_raw(raw as *mut i32) });
});
}

View file

@ -0,0 +1,42 @@
error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
LL | self.1.deallocate(From::from(ptr.cast()), layout);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the allocation of the accessed tag <wildcard> also contains the strongly protected tag <TAG>
= help: the strongly protected tag <TAG> disallows deallocations
help: the strongly protected tag <TAG> was created here, in the initial state Reserved
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
LL | fn inner(x: &mut i32, f: fn(usize)) {
| ^
= note: BACKTRACE (of the first span):
= note: inside `<std::boxed::Box<i32> as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC
= note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
= note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
note: inside closure
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
LL | drop(unsafe { Box::from_raw(raw as *mut i32) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: inside `<{closure@tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC} as std::ops::FnOnce<(usize,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC
note: inside `inner`
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
LL | f(x as *mut i32 as usize)
| ^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `main`
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
LL | / inner(Box::leak(Box::new(0)), |raw| {
LL | | drop(unsafe { Box::from_raw(raw as *mut i32) });
LL | | });
| |______^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -1,4 +1,6 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-permissive-provenance
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::ptr;
// Just to make sure that casting a ref to raw, to int and back to raw

View file

@ -1,3 +1,6 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-permissive-provenance
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::cell::Cell;
fn main() {

View file

@ -0,0 +1,10 @@
// This test seems to involve a "stashed diagnostic" (or at least it used to at the time of
// writing). Ensure we handle that correctly.
pub trait Trait {
type Assoc: Assoc;
}
pub trait Assoc {}
fn main() {}

View file

@ -1,12 +0,0 @@
warning: integer-to-pointer cast
--> tests/pass/stacked_borrows/issue-miri-2389.rs:LL:CC
|
LL | let wildcard = &root0 as *const Cell<i32> as usize as *const Cell<i32>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning

View file

@ -0,0 +1,145 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
// NOTE: This file documents UB that is not detected by wildcard provenance.
pub fn main() {
uncertain_provenance();
protected_exposed();
protected_wildcard();
}
/// Currently, if we do not know for a tag if an access is local or foreign,
/// then we do not do any state transitions on that tag. However, to implement
/// proper provenance we would have to instead pick the correct transition
/// non-deterministically.
///
/// This test contains such UB that will not be detectable with wildcard provenance,
/// but would be detectable if we implemented this behavior correctly.
pub fn uncertain_provenance() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
// We create 2 mutable references, each with a unique tag.
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *mut u32 as usize;
//ref1 : Reserved
//ref2 : Reserved
// We need to pick the "correct" tag for wild from the exposed tags.
let wild = int1 as *mut u32;
// wild=ref1 wild=ref2
//ref1 : Reserved Reserved
//ref2 : Reserved Reserved
// We write to wild, disabling the other tag.
unsafe { wild.write(13) };
// wild=ref1 wild=ref2
//ref1 : Unique Disabled
//ref2 : Disabled Unique
// We access both references, even though one of them should be
// disabled under proper exposed provenance.
// This is UB, however, wildcard provenance cannot detect this.
assert_eq!(*ref1, 13);
// wild=ref1 wild=ref2
//ref1 : Unique UB
//ref2 : Disabled Frozen
assert_eq!(*ref2, 13);
// wild=ref1 wild=ref2
//ref1 : Frozen UB
//ref2 : UB Frozen
}
/// If a reference is protected, then all foreign writes to it cause UB.
/// This effectively means any write needs to happen through a child of
/// the protected reference.
/// With this information we could further narrow the possible candidates
/// for a wildcard write.
/// However, currently tree borrows doesn't do this, so this test has UB
/// that isn't detected.
pub fn protected_exposed() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let _int2 = ref2 as *mut u32 as usize;
fn protect(ref3: &mut u32) {
let int3 = ref3 as *mut u32 as usize;
// ┌────────────┐
// │ │
// │ ptr_base ├──────────────┐
// │ │ │
// └──────┬─────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌────────────┐
// │ │ │ │
// │ ref1(Res) │ │ ref2(Res)* │
// │ │ │ │
// └──────┬─────┘ └────────────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref3(Res)* │
// │ │
// └────────────┘
// Since ref3 is protected, we could know that every write from outside it will be UB.
// This means we know that the access is through ref3, disabling ref2.
let wild = int3 as *mut u32;
unsafe { wild.write(13) }
}
protect(ref1);
// ref2 is disabled, so this read causes UB, but we currently don't protect this.
let _fail = *ref2;
}
/// Currently, we do not assign protectors to wildcard references.
/// This test has UB because it does a foreign write to a protected reference.
/// However, that reference is a wildcard, so this doesn't get detected.
#[allow(unused_variables)]
pub fn protected_wildcard() {
let mut x: u32 = 32;
let ref1 = &mut x;
let ref2 = &mut *ref1;
let int = ref2 as *mut u32 as usize;
let wild = int as *mut u32;
let wild_ref = unsafe { &mut *wild };
let mut protect = |arg: &mut u32| {
// arg is a protected pointer with wildcard provenance.
// ┌────────────┐
// │ │
// │ ref1(Res) │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref2(Res)* │
// │ │
// └────────────┘
// Writes to ref1, disabling ref2, i.e. disabling all exposed references.
// Since a wildcard reference is protected, this is UB. But we currently don't detect this.
*ref1 = 13;
};
// We pass a pointer with wildcard provenance to the function.
protect(wild_ref);
}

View file

@ -0,0 +1,171 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
pub fn main() {
multiple_exposed_siblings();
multiple_exposed_child();
dealloc();
protector();
protector_conflicted_release();
returned_mut_is_usable();
}
/// Checks that an access through a wildcard reference
/// doesn't disable any exposed references.
/// It tests this with exposed references that are siblings of each other.
pub fn multiple_exposed_siblings() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
// Both references get exposed.
let int1 = ref1 as *mut u32 as usize;
let _int2 = ref2 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ptr_base ├────────────┐
// │ │ │
// └──────┬─────┘ │
// │ │
// │ │
// ▼ ▼
// ┌────────────┐ ┌────────────┐
// │ │ │ │
// │ ref1(Res)* │ │ ref2(Res)* │
// │ │ │ │
// └────────────┘ └────────────┘
// Writes through either of the two exposed references.
// We do not know which so we cannot disable the other.
unsafe { wild.write(13) };
// Reading through either of these references should be valid.
assert_eq!(*ref2, 13);
}
/// Checks that an access through a wildcard reference
/// doesn't disable any exposed references.
/// It tests this with exposed references where one is the ancestor of the other.
pub fn multiple_exposed_child() {
let mut x: u32 = 42;
let ref1 = &mut x;
let int1 = ref1 as *mut u32 as usize;
let ref2 = &mut *ref1;
let ref3 = &mut *ref2;
let _int3 = ref3 as *mut u32 as usize;
let wild = int1 as *mut u32;
// ┌────────────┐
// │ │
// │ ref1(Res)* │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref2(Res) │
// │ │
// └──────┬─────┘
// │
// │
// ▼
// ┌────────────┐
// │ │
// │ ref3(Res)* │
// │ │
// └────────────┘
// This writes either through ref1 or ref3, which is either a child or foreign access to ref2.
unsafe { wild.write(42) };
// Reading from ref2 still works, since the previous access could have been through its child.
// This also freezes ref3.
let _x = *ref2;
// We can still write through wild, as there is still the exposed ref1 with write permissions.
unsafe { wild.write(43) };
}
/// Checks that we can deallocate through a wildcard reference.
fn dealloc() {
use std::alloc::Layout;
let x = unsafe { std::alloc::alloc_zeroed(Layout::new::<u32>()) as *mut u32 };
let ref1 = unsafe { &mut *x };
let int = ref1 as *mut u32 as usize;
let wild = int as *mut u32;
unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) };
}
/// Checks that we can pass a wildcard reference to a function.
fn protector() {
fn protect(arg: &mut u32) {
*arg = 4;
}
let mut x: u32 = 32;
let ref1 = &mut x;
let int = ref1 as *mut u32 as usize;
let wild = int as *mut u32;
let wild_ref = unsafe { &mut *wild };
protect(wild_ref);
assert_eq!(*ref1, 4);
}
/// Checks whether we correctly handle the protector being released on
/// a conflicted exposed reference.
fn protector_conflicted_release() {
let mut x: u32 = 42;
let ptr_base = &mut x as *mut u32;
let ref1 = unsafe { &mut *ptr_base };
let ref2 = unsafe { &mut *ptr_base };
let protect = |arg: &mut u32| {
// Expose arg.
let int = arg as *mut u32 as usize;
let wild = int as *mut u32;
// Do a foreign read to arg marking it as conflicted and making child_writes UB while its protected.
let _x = *ref2;
return wild;
};
let wild = protect(ref1);
// The protector on arg got released so writes through arg should work again.
unsafe { *wild = 4 };
}
/// Analogous to same test in `../tree-borrows.rs` but with a protected wildcard reference.
fn returned_mut_is_usable() {
// NOTE: Currently we ignore protectors on wildcard references.
fn reborrow(x: &mut u8) -> &mut u8 {
let y = &mut *x;
// Activate the reference so that it is vulnerable to foreign reads.
*y = *y;
y
// An implicit read through `x` is inserted here.
}
let mut x: u8 = 0;
let ref1 = &mut x;
let int = ref1 as *mut u8 as usize;
let wild = int as *mut u8;
let wild_ref = unsafe { &mut *wild };
let y = reborrow(wild_ref);
*y = 1;
}