├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Data ├── BugReport.xlsx └── RustStudy.xlsx ├── LICENSE ├── README.md ├── detect.sh ├── measure_time.sh ├── rust-toolchain.toml ├── src ├── analysis │ ├── callgraph │ │ └── mod.rs │ ├── controldep │ │ ├── mod.rs │ │ └── tests.rs │ ├── datadep │ │ └── mod.rs │ ├── defuse │ │ └── mod.rs │ ├── mod.rs │ ├── pointsto │ │ └── mod.rs │ └── postdom │ │ ├── mod.rs │ │ └── tests.rs ├── bin │ └── cargo-lockbud.rs ├── callbacks.rs ├── detector │ ├── atomic │ │ ├── mod.rs │ │ └── report.rs │ ├── lock │ │ ├── mod.rs │ │ └── report.rs │ ├── memory │ │ ├── invalid_free.rs │ │ ├── mod.rs │ │ └── use_after_free.rs │ ├── mod.rs │ ├── panic │ │ └── mod.rs │ └── report.rs ├── interest │ ├── concurrency │ │ ├── atomic.rs │ │ ├── chan.rs │ │ ├── condvar.rs │ │ ├── lock.rs │ │ └── mod.rs │ ├── memory │ │ ├── cast.rs │ │ ├── mod.rs │ │ ├── ownership.rs │ │ ├── rawptr.rs │ │ └── uninit.rs │ └── mod.rs ├── main.rs └── options.rs └── toys ├── atomic-violation ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── call-no-deadlock ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── condvar-closure ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── condvar-struct ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── conflict-inter ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── conflict ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── inter ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── intra ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── invalid-free ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── issue71 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── lock-closure ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── panic ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── recursive-no-deadlock ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── static-ref ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── tikv-wrapper ├── Cargo.lock ├── Cargo.toml └── src │ ├── main.rs │ └── util.rs ├── use-after-free ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── wait-lock-no-deadlock ├── Cargo.lock ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | #Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | 15 | /target 16 | **/target 17 | 18 | /results 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lockbud" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [[bin]] 9 | name = "lockbud" 10 | path = "src/main.rs" 11 | 12 | [[bin]] 13 | name = "cargo-lockbud" 14 | path = "src/bin/cargo-lockbud.rs" 15 | 16 | [dependencies] 17 | cargo_metadata = { version = "0.12.1", optional = true } 18 | directories = { version = "3.0.1", optional = true } 19 | rustc_version = { version = "0.3.0", optional = true } 20 | getrandom = { version = "0.2.0", features = ["std"] } 21 | byteorder = "1.3" 22 | env_logger = "0.8.2" 23 | log = "0.4.11" 24 | shell-escape = "0.1.5" 25 | hex = "0.4.0" 26 | rand = "0.8.0" 27 | clap = "3.1.12" 28 | shellwords = "1.1.0" 29 | petgraph = "0.6.0" 30 | bitflags = "1.3.2" 31 | smallvec = "1.8.0" 32 | serde_json = "1.0.81" 33 | serde = { version = "1.0", features = ["derive"] } 34 | regex = "1.6.0" 35 | once_cell = "1.13.1" 36 | 37 | [profile.dev] 38 | incremental = false 39 | 40 | [package.metadata.rust-analyzer] 41 | rustc_private=true 42 | -------------------------------------------------------------------------------- /Data/BugReport.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurtonQin/lockbud/b2427ff793e89d7c0f0651b84274fd501887d925/Data/BugReport.xlsx -------------------------------------------------------------------------------- /Data/RustStudy.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurtonQin/lockbud/b2427ff793e89d7c0f0651b84274fd501887d925/Data/RustStudy.xlsx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Boqin Qin(秦 伯钦) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lockbud 2 | Statically detect memory, concurrency bugs and possible panic locations for Rust. 3 | 4 | ## Introduction 5 | 6 | This project, "lockbud", is the artifact of the research paper ["Understanding and Detecting Real-World Safety Issues in Rust"](https://songlh.github.io/paper/rust-tse.pdf), published in TSE'24. It builds upon our previous work ["Understanding Memory and Thread Safety Practices and Issues in Real-World Rust Programs"](https://songlh.github.io/paper/rust-study.pdf), published in PLDI'20. 7 | 8 | The project includes detectors for the following types of issues: 9 | 10 | - Concurrency Bugs 11 | - Blocking Bugs (use `-k deadlock`) 12 | - Double-Lock 13 | - Conflicting-Lock-Order 14 | - Condvar Misuse (not appeared in paper) 15 | - Non-blocking Bugs 16 | - Atomicity-Violation (use `-k atomicity_violation`) 17 | - Memory Bugs (use `-k memory`) 18 | - Use-After-Free 19 | - Invalid-Free (not appeared in paper) 20 | - Panic Locations (use `-k panic`) 21 | 22 | The `Data` directory contains two Excel files: 23 | 24 | - BugStudy.xlsx: Records the results of the research study. 25 | - BugReport.xlsx: Records the experimental results. 26 | 27 | ## Announcements 28 | 29 | The codebase was implemented quickly, and I plan to refactor it in the future. The todo list is in #58. 30 | 31 | For now, when checking your project, please ignore the bug reports originating from the standard library and common dependencies (especially the memory detectors), as they are mostly false positives. To focus on your own project instead of dependencies, you can pass the `-l your_project_name` flag to lockbud. 32 | 33 | The deadlock detectors (double-lock and conflicting-lock-order) perform better than the other detectors, as the project was initially designed for deadlock detection. The bug patterns are based on the observations from our research, and I've tried to minimize false positives and false negatives caused by the program analysis over-approximation. 34 | 35 | To reduce the detectors' overhead, I did not introduce SMTs or other expensive analyses. As a result, the panic location detector may report nearly all the panic locations, making it less useful. I hope that a new, unified static analysis framework for Rust will emerge soon to address these limitations. 36 | 37 | ## Install 38 | Currently supports rustc nightly-2024-12-01 (Thanks to @mokhaled2992). 39 | ``` 40 | $ git clone https://github.com/BurtonQin/lockbud.git 41 | $ cd lockbud 42 | $ rustup +nightly-2025-02-01 component add rust-src rustc-dev llvm-tools-preview 43 | $ cargo +nightly-2025-02-01 install --path . 44 | ``` 45 | 46 | Note that you must use the same rustc nightly version as lockbud to detect your project! 47 | You can either override your rustc version or specify rust-toolchains in your project. 48 | 49 | ## Example 50 | ## Test toys 51 | ``` 52 | $ ./detect.sh toys/inter 53 | ``` 54 | It will print 15 doublelock bugs in json format, like the following one: 55 | 56 | ``` 57 | { 58 | "DoubleLock": { 59 | "bug_kind": "DoubleLock", 60 | "possibility": "Possibly", 61 | "diagnosis": { 62 | "first_lock_type": "ParkingLotWrite(i32)", 63 | "first_lock_span": "src/main.rs:77:16: 77:32 (#0)", 64 | "second_lock_type": "ParkingLotRead(i32)", 65 | "second_lock_span": "src/main.rs:84:18: 84:33 (#0)", 66 | "callchains": [ 67 | [ 68 | [ 69 | "src/main.rs:79:20: 79:52 (#0)" 70 | ] 71 | ] 72 | ] 73 | }, 74 | "explanation": "The first lock is not released when acquiring the second lock" 75 | } 76 | } 77 | ``` 78 | 79 | The output shows that there is possibly a doublelock bug. The DeadlockDiagnosis reads that the first lock is a parking_lot WriteLock acquired on src/main.rs:77 and the second lock is a parking_lot ReadLock aquired on src/main.rs:84. The first lock reaches the second lock through callsites src/main.rs:79. The explanation demonstrates the reason for doubelock. 80 | 81 | ``` 82 | $ ./detect.sh toys/conflict-inter 83 | ``` 84 | It will print one conflictlock bug 85 | 86 | ``` 87 | { 88 | "ConflictLock": { 89 | "bug_kind": "ConflictLock", 90 | "possibility": "Possibly", 91 | "diagnosis": [ 92 | { 93 | "first_lock_type": "StdRwLockRead(i32)", 94 | "first_lock_span": "src/main.rs:29:16: 29:40 (#0)", 95 | "second_lock_type": "StdMutex(i32)", 96 | "second_lock_span": "src/main.rs:36:10: 36:34 (#0)", 97 | "callchains": [ 98 | [ 99 | [ 100 | "src/main.rs:31:20: 31:38 (#0)" 101 | ] 102 | ] 103 | ] 104 | }, 105 | { 106 | "first_lock_type": "StdMutex(i32)", 107 | "first_lock_span": "src/main.rs:18:16: 18:40 (#0)", 108 | "second_lock_type": "StdRwLockWrite(i32)", 109 | "second_lock_span": "src/main.rs:25:10: 25:35 (#0)", 110 | "callchains": [ 111 | [ 112 | [ 113 | "src/main.rs:20:20: 20:35 (#0)" 114 | ] 115 | ] 116 | ] 117 | } 118 | ], 119 | "explanation": "Locks mutually wait for each other to form a cycle" 120 | } 121 | } 122 | ``` 123 | 124 | The output shows that there is possibly a conflictlock bug. The DeadlockDiagnosis is similar to doublelock bugs except that there are at least two diagnosis records. All the diagnosis records form a cycle, e.g. A list of records [(first_lock, second_lock), (second_lock', first_lock')] means that it is possible that first_lock is aquired and waits for second_lock in one thread, while second_lock' is aquired and waits for first_lock' in another thread, which incurs a conflictlock bug. 125 | 126 | `detect.sh` is mainly for development of the detector and brings more flexibility. 127 | You can modify `detect.sh` to use release vesion of lockbud to detect large and complex projects. 128 | And you can use `LOCKBUD_FLAGS` to select detectors and projects to be checked. See the commented `export LOCKBUD_FLAGS=...` in `detect.sh` 129 | 130 | ## Using `cargo lockbud` 131 | For ease of use, you can also run cargo lockbud 132 | ``` 133 | $ cd toys/inter; cargo clean; cargo lockbud -k deadlock 134 | ``` 135 | Note that you need to run 136 | ``` 137 | cargo clean 138 | ``` 139 | before re-running lockbud. 140 | 141 | You can also specify blacklist or whitelist of crate names. 142 | 143 | The `-b` implies the list is a blacklist. 144 | 145 | The `-l` is followed by a list of crate names seperated by commas. 146 | ``` 147 | $ cd YourProject; cargo clean; cargo lockbud -k deadlock -b -l cc,tokio_util,indicatif 148 | ``` 149 | 150 | You have ommitted the checking of dependencies cc, tokio_util, indicatif . 151 | 152 | You can also ensure to use the right version of the compiler by running: 153 | 154 | ``` 155 | $ cargo +nightly-2024-12-01 lockbud 156 | ``` 157 | 158 | ### Using by docker 159 | 160 | Current available docker image is `burtonqin/lockbud`[^1] 161 | 162 | ```shell 163 | docker run --rm -it -v ./toys/inter/:/volume burtonqin/lockbud -k deadlock 164 | ``` 165 | 166 | lockbud will execute `cargo clean && cargo lockbud -k deadlock` in `/volume` directory. 167 | 168 | > **Note** 169 | > It will compile your project in docker, so you need to manualy remove the target directory before you are ready for working. 170 | > The lockbud version in docker may not be the latest. Please help test it. 171 | 172 | ### Using in CI 173 | 174 | ```yaml 175 | name: Lockbud 176 | 177 | on: workflow_dispatch 178 | 179 | jobs: 180 | test: 181 | name: lockbud 182 | runs-on: ubuntu-latest 183 | container: 184 | image: burtonqin/lockbud 185 | steps: 186 | - name: Checkout repository 187 | uses: actions/checkout@v3 188 | 189 | - name: Generate code coverage 190 | run: | 191 | cargo lockbud -k deadlock 192 | ``` 193 | 194 | > **Note** 195 | > Currently lockbud output in stdout 196 | 197 | ## How it works 198 | In Rust, a lock operation returns a lockguard. The lock will be unlocked when the lockguard is dropped. 199 | So we can track the lifetime of lockguards to detect lock-related bugs. 200 | For each crate (the crate to be detected and its dependencies) 201 | 1. Collect the caller-callee info to generate a callgraph. 202 | 2. Collect LockGuard info, including 203 | - The lockguard type and span; 204 | - Where it is created and where it is dropped. 205 | 3. Apply a GenKill algorithm on the callgraph to find pairs of lockguards (a, b) s.t. 206 | - a not dropped when b is created. 207 | 4. A pair (a, b) can doublelock if 208 | - the lockguard types of a & b can deadlock; 209 | - and a & b may point to the same lock (obtained from points-to analysis). 210 | 5. For (a, b), (c, d) in the remaining pairs 211 | - if b and c can deadlock then add an edge from (a, b) to (c, d) into a graph. 212 | 6. The cycle in the graph implies a conflictlock. 213 | 214 | I will write some doc to explain the implementation details of other checkers. 215 | 216 | ## Caveats 217 | 218 | Deadlock Detectors 219 | 220 | 1. Currently only supports `std::sync::{Mutex, RwLock}`, `parking_lot::{Mutex, RwLock}`, `spin::{Mutex, RwLock}` 221 | 2. The callgraph is crate-specific (the callers and callees are in the same crate) and cannot track indirect call. 222 | 3. The points-to analysis is imprecise and makes heuristic assumptions for function calls and assignments. 223 | - A common FP comes from `cc`, where points-to analysis incorrectly assumes that two unrelated lockguards are from the same lock. Thus blacklist `cc` in `detector.sh`. 224 | 225 | Memory and Panic Location Detectors 226 | 227 | See Announcements. 228 | 229 | ## Results 230 | Found dozens of bugs in many repositories: openethereum, grin, winit, sonic, lighthouse, etc. 231 | Some of the repositories are dependencies of other large projects. 232 | We try to strike a balance between FP and FN to make the detector usable. 233 | 234 | Some typical Bugs detected and fixed (one PR may fix multiple bugs) in the follwing list. For a complete bug report list, please refer to `Data/BugReport.xlsx`. 235 | 236 | 1. https://github.com/openethereum/openethereum/pull/289 237 | 2. https://github.com/openethereum/parity-ethereum/pull/11764 238 | 3. https://github.com/sigp/lighthouse/pull/1241 239 | 4. https://github.com/solana-labs/solana/pull/10466 240 | 5. https://github.com/solana-labs/solana/pull/10469 241 | 6. https://github.com/wasmerio/wasmer/pull/1466 242 | 7. https://github.com/paritytech/substrate/pull/6277 243 | 8. https://github.com/mimblewimble/grin/pull/3337 244 | 9. https://github.com/mimblewimble/grin/pull/3340 245 | 10. https://github.com/sigp/lighthouse/pull/1241 246 | 11. https://github.com/rust-windowing/winit/pull/1579 247 | 12. https://github.com/solana-labs/solana/pull/26046 248 | 13. https://github.com/solana-labs/solana/pull/26047 249 | 14. https://github.com/solana-labs/solana/pull/26053 250 | 15. https://github.com/qdrant/qdrant/issues/724 251 | 16. https://github.com/apache/incubator-teaclave-sgx-sdk/pull/269 252 | 253 | ## Disclaimer 254 | 255 | This open-source project is provided "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 256 | 257 | The purpose of this project is to provide a tool for detecting and identifying potential vulnerabilities in software applications. However, the accuracy and effectiveness of the tool is not guaranteed. Users of this tool should exercise caution and judgment when interpreting the results, and should not rely solely on the tool's findings to make decisions about the security of their systems. 258 | 259 | The authors and contributors of this project do not endorse or encourage the use of this tool for any unlawful or unethical purposes, such as hacking, breaking into systems, or exploiting vulnerabilities without authorization. Users of this tool are solely responsible for their actions and the consequences thereof. 260 | 261 | By using this open-source project, you acknowledge and agree to the terms of this disclaimer. If you do not agree with the terms of this disclaimer, you should not use this project. 262 | 263 | ## License 264 | The lockbud Project is licensed under BSD-3. 265 | -------------------------------------------------------------------------------- /detect.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this script's location 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | if [ -z "$1" ]; then 7 | echo "No detecting directory is provided" 8 | echo "Usage: ./detect.sh DIRNAME" 9 | exit 1 10 | fi 11 | # Build lockbud 12 | cargo build 13 | # For development of lockbud use debug 14 | export RUSTC_WRAPPER=${PWD}/target/debug/lockbud 15 | # For usage use release 16 | # cargo build --release 17 | # export RUSTC_WRAPPER=${PWD}/target/release/lockbud 18 | export RUST_BACKTRACE=full 19 | export LOCKBUD_LOG=info 20 | # To only detect inter,intra 21 | #export LOCKBUD_FLAGS="--detector-kind deadlock --crate-name-list inter,intra" 22 | # or shorter 23 | #export LOCKBUD_FLAGS="-k deadlock -l inter,intra" 24 | # To skip detecting inter or intra 25 | #export LOCKBUD_FLAGS="--detector-kind deadlock --blacklist-mode --crate-name-list inter,intra" 26 | # or shorter 27 | #export LOCKBUD_FLAGS="-k deadlock -b -l inter,intra" 28 | #export LOCKBUD_FLAGS="-k deadlock -b -l cc" 29 | #export LOCKBUD_FLAGS="-k atomicity_violation" 30 | #export LOCKBUD_FLAGS="-k memory" 31 | #export LOCKBUD_FLAGS="-k panic" 32 | export LOCKBUD_FLAGS="-k all" 33 | 34 | # Find all Cargo.tomls recursively under the detecting directory 35 | # and record them in cargo_dir.txt 36 | cargo_dir_file=$(realpath $DIR/cargo_dir.txt) 37 | rm -f $cargo_dir_file 38 | touch $cargo_dir_file 39 | 40 | pushd "$1" > /dev/null 41 | cargo clean 42 | cargo_tomls=$(find . -name "Cargo.toml") 43 | for cargo_toml in ${cargo_tomls[@]} 44 | do 45 | echo $(dirname $cargo_toml) >> $cargo_dir_file 46 | done 47 | 48 | IFS=$'\n' read -d '' -r -a lines < ${cargo_dir_file} 49 | for cargo_dir in ${lines[@]} 50 | do 51 | pushd ${cargo_dir} > /dev/null 52 | cargo build 53 | popd > /dev/null 54 | done 55 | popd > /dev/null 56 | 57 | rm -f $cargo_dir_file 58 | -------------------------------------------------------------------------------- /measure_time.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this script's location 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | if [ -z "$1" ]; then 7 | echo "No detecting directory is provided" 8 | echo "Usage: ./detect.sh DIRNAME" 9 | exit 1 10 | fi 11 | # Build lockbud 12 | # cargo build 13 | # For development of lockbud use debug 14 | # export RUSTC_WRAPPER=${PWD}/target/debug/lockbud 15 | # For usage use release 16 | cargo build --release 17 | export RUSTC_WRAPPER=${PWD}/target/release/lockbud 18 | export RUST_BACKTRACE=full 19 | export LOCKBUD_LOG=info 20 | # To only detect inter,intra 21 | #export LOCKBUD_FLAGS="--detector-kind deadlock --crate-name-list inter,intra" 22 | # or shorter 23 | #export LOCKBUD_FLAGS="-k deadlock -l inter,intra" 24 | # To skip detecting inter or intra 25 | #export LOCKBUD_FLAGS="--detector-kind deadlock --blacklist-mode --crate-name-list inter,intra" 26 | # or shorter 27 | #export LOCKBUD_FLAGS="-k deadlock -b -l inter,intra" 28 | #export LOCKBUD_FLAGS="-k deadlock -b -l cc" 29 | export LOCKBUD_FLAGS="-k deadlock" 30 | time_log=time.log 31 | time_err=time.err 32 | pushd "$1" > /dev/null 33 | # cargo clean 34 | begin=$(date +%s) 35 | CARGO_BUILD_JOBS=1 cargo build 1>${time_log} 2>${time_err} 36 | # for wasmer 37 | # CARGO_BUILD_JOBS=1 cargo build --manifest-path lib/cli/Cargo.toml --features cranelift,singlepass --bin wasmer 1>${time_log} 2>${time_err} 38 | # firecracker use build instead of targets, so will be skippped. Must change code. 39 | end=$(date +%s) 40 | total_time=$(($end-$begin)) 41 | echo $total_time 42 | grep '^Elapsed: ' $time_log | cut -d' ' -f2 | awk '{s+=$1} END {print s}' 43 | popd > /dev/null 44 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-02-01" 3 | components = [ "rust-src", "rustc-dev", "llvm-tools-preview", "clippy" ] 4 | -------------------------------------------------------------------------------- /src/analysis/callgraph/mod.rs: -------------------------------------------------------------------------------- 1 | //! Generate a CallGraph for instances in each crate. 2 | //! You can roughly think of instances as a monomorphic function. 3 | //! If an instance calls another instance, then we have an edge 4 | //! from caller to callee with callsite locations as edge weight. 5 | //! This is a fundamental analysis for other analysis, 6 | //! e.g., points-to analysis, lockguard collector, etc. 7 | //! We also track where a closure is defined rather than called 8 | //! to record the defined function and the parameter of the closure, 9 | //! which is pointed to by upvars. 10 | use petgraph::algo; 11 | use petgraph::dot::{Config, Dot}; 12 | use petgraph::graph::NodeIndex; 13 | use petgraph::visit::IntoNodeReferences; 14 | use petgraph::Direction::Incoming; 15 | use petgraph::{Directed, Graph}; 16 | 17 | use rustc_middle::mir::visit::Visitor; 18 | use rustc_middle::mir::{Body, Local, LocalDecl, LocalKind, Location, Terminator, TerminatorKind}; 19 | use rustc_middle::ty::{self, EarlyBinder, Instance, TyCtxt, TyKind, TypingEnv}; 20 | 21 | /// The NodeIndex in CallGraph, denoting a unique instance in CallGraph. 22 | pub type InstanceId = NodeIndex; 23 | 24 | /// The location where caller calls callee. 25 | /// Support direct call for now, where callee resolves to FnDef. 26 | /// Also support tracking the parameter of a closure (pointed to by upvars) 27 | /// TODO(boqin): Add support for FnPtr. 28 | #[derive(Copy, Clone, Debug)] 29 | pub enum CallSiteLocation { 30 | Direct(Location), 31 | ClosureDef(Local), 32 | // Indirect(Location), 33 | } 34 | 35 | impl CallSiteLocation { 36 | pub fn location(&self) -> Option { 37 | match self { 38 | Self::Direct(loc) => Some(*loc), 39 | _ => None, 40 | } 41 | } 42 | } 43 | 44 | /// The CallGraph node wrapping an Instance. 45 | /// WithBody means the Instance owns body. 46 | #[derive(Debug, PartialEq, Eq)] 47 | pub enum CallGraphNode<'tcx> { 48 | WithBody(Instance<'tcx>), 49 | WithoutBody(Instance<'tcx>), 50 | } 51 | 52 | impl<'tcx> CallGraphNode<'tcx> { 53 | pub fn instance(&self) -> &Instance<'tcx> { 54 | match self { 55 | CallGraphNode::WithBody(inst) | CallGraphNode::WithoutBody(inst) => inst, 56 | } 57 | } 58 | 59 | pub fn match_instance(&self, other: &Instance<'tcx>) -> bool { 60 | matches!(self, CallGraphNode::WithBody(inst) | CallGraphNode::WithoutBody(inst) if inst == other) 61 | } 62 | } 63 | 64 | /// CallGraph 65 | /// The nodes of CallGraph are instances. 66 | /// The directed edges are CallSite Locations. 67 | /// e.g., `Instance1--|[CallSite1, CallSite2]|-->Instance2` 68 | /// denotes `Instance1` calls `Instance2` at locations `Callsite1` and `CallSite2`. 69 | pub struct CallGraph<'tcx> { 70 | pub graph: Graph, Vec, Directed>, 71 | } 72 | 73 | impl<'tcx> CallGraph<'tcx> { 74 | /// Create an empty CallGraph. 75 | pub fn new() -> Self { 76 | Self { 77 | graph: Graph::new(), 78 | } 79 | } 80 | 81 | /// Search for the InstanceId of a given instance in CallGraph. 82 | pub fn instance_to_index(&self, instance: &Instance<'tcx>) -> Option { 83 | self.graph 84 | .node_references() 85 | .find(|(_idx, inst)| inst.match_instance(instance)) 86 | .map(|(idx, _)| idx) 87 | } 88 | 89 | /// Get the instance by InstanceId. 90 | pub fn index_to_instance(&self, idx: InstanceId) -> Option<&CallGraphNode<'tcx>> { 91 | self.graph.node_weight(idx) 92 | } 93 | 94 | /// Perform callgraph analysis on the given instances. 95 | /// The instances should be **all** the instances with MIR available in the current crate. 96 | pub fn analyze( 97 | &mut self, 98 | instances: Vec>, 99 | tcx: TyCtxt<'tcx>, 100 | typing_env: TypingEnv<'tcx>, 101 | ) { 102 | let idx_insts = instances 103 | .into_iter() 104 | .map(|inst| { 105 | let idx = self.graph.add_node(CallGraphNode::WithBody(inst)); 106 | (idx, inst) 107 | }) 108 | .collect::>(); 109 | for (caller_idx, caller) in idx_insts { 110 | let body = tcx.instance_mir(caller.def); 111 | // Skip promoted src 112 | if body.source.promoted.is_some() { 113 | continue; 114 | } 115 | let mut collector = CallSiteCollector::new(caller, body, tcx, typing_env); 116 | collector.visit_body(body); 117 | for (callee, location) in collector.finish() { 118 | let callee_idx = if let Some(callee_idx) = self.instance_to_index(&callee) { 119 | callee_idx 120 | } else { 121 | self.graph.add_node(CallGraphNode::WithoutBody(callee)) 122 | }; 123 | if let Some(edge_idx) = self.graph.find_edge(caller_idx, callee_idx) { 124 | // Update edge weight. 125 | self.graph.edge_weight_mut(edge_idx).unwrap().push(location); 126 | } else { 127 | // Add edge if not exists. 128 | self.graph.add_edge(caller_idx, callee_idx, vec![location]); 129 | } 130 | } 131 | } 132 | } 133 | 134 | /// Find the callsites (weight) on the edge from source to target. 135 | pub fn callsites( 136 | &self, 137 | source: InstanceId, 138 | target: InstanceId, 139 | ) -> Option> { 140 | let edge = self.graph.find_edge(source, target)?; 141 | self.graph.edge_weight(edge).cloned() 142 | } 143 | 144 | /// Find all the callers that call target 145 | pub fn callers(&self, target: InstanceId) -> Vec { 146 | self.graph.neighbors_directed(target, Incoming).collect() 147 | } 148 | 149 | /// Find all simple paths from source to target. 150 | /// e.g., for one of the paths, `source --> instance1 --> instance2 --> target`, 151 | /// the return is [source, instance1, instance2, target]. 152 | pub fn all_simple_paths(&self, source: InstanceId, target: InstanceId) -> Vec> { 153 | algo::all_simple_paths::, _>(&self.graph, source, target, 0, None) 154 | .collect::>() 155 | } 156 | 157 | /// Print the callgraph in dot format. 158 | #[allow(dead_code)] 159 | pub fn dot(&self) { 160 | println!( 161 | "{:?}", 162 | Dot::with_config(&self.graph, &[Config::GraphContentOnly]) 163 | ); 164 | } 165 | } 166 | 167 | /// Visit Terminator and record callsites (callee + location). 168 | struct CallSiteCollector<'a, 'tcx> { 169 | caller: Instance<'tcx>, 170 | body: &'a Body<'tcx>, 171 | tcx: TyCtxt<'tcx>, 172 | typing_env: TypingEnv<'tcx>, 173 | callsites: Vec<(Instance<'tcx>, CallSiteLocation)>, 174 | } 175 | 176 | impl<'a, 'tcx> CallSiteCollector<'a, 'tcx> { 177 | fn new( 178 | caller: Instance<'tcx>, 179 | body: &'a Body<'tcx>, 180 | tcx: TyCtxt<'tcx>, 181 | typing_env: TypingEnv<'tcx>, 182 | ) -> Self { 183 | Self { 184 | caller, 185 | body, 186 | tcx, 187 | typing_env, 188 | callsites: Vec::new(), 189 | } 190 | } 191 | 192 | /// Consumes `CallSiteCollector` and returns its callsites when finished visiting. 193 | fn finish(self) -> impl IntoIterator, CallSiteLocation)> { 194 | self.callsites.into_iter() 195 | } 196 | } 197 | 198 | impl<'tcx> Visitor<'tcx> for CallSiteCollector<'_, 'tcx> { 199 | /// Resolve direct call. 200 | /// Inspired by rustc_mir/src/transform/inline.rs#get_valid_function_call. 201 | fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { 202 | if let TerminatorKind::Call { ref func, .. } = terminator.kind { 203 | let func_ty = func.ty(self.body, self.tcx); 204 | // Only after monomorphizing can Instance::try_resolve work 205 | let func_ty = self.caller.instantiate_mir_and_normalize_erasing_regions( 206 | self.tcx, 207 | self.typing_env, 208 | EarlyBinder::bind(func_ty), 209 | ); 210 | if let ty::FnDef(def_id, substs) = *func_ty.kind() { 211 | if let Some(callee) = 212 | Instance::try_resolve(self.tcx, self.typing_env, def_id, substs) 213 | .ok() 214 | .flatten() 215 | { 216 | self.callsites 217 | .push((callee, CallSiteLocation::Direct(location))); 218 | } 219 | } 220 | } 221 | self.super_terminator(terminator, location); 222 | } 223 | 224 | /// Find where the closure is defined rather than called, 225 | /// including the closure instance and the arg. 226 | /// 227 | /// e.g., let mut _20: [closure@src/main.rs:13:28: 16:6]; 228 | /// 229 | /// _20 is of type Closure, but it is actually the arg that captures 230 | /// the variables in the defining function. 231 | fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) { 232 | let func_ty = self.caller.instantiate_mir_and_normalize_erasing_regions( 233 | self.tcx, 234 | self.typing_env, 235 | EarlyBinder::bind(local_decl.ty), 236 | ); 237 | if let TyKind::Closure(def_id, substs) = func_ty.kind() { 238 | match self.body.local_kind(local) { 239 | LocalKind::Arg | LocalKind::ReturnPointer => {} 240 | _ => { 241 | if let Some(callee_instance) = 242 | Instance::try_resolve(self.tcx, self.typing_env, *def_id, substs) 243 | .ok() 244 | .flatten() 245 | { 246 | self.callsites 247 | .push((callee_instance, CallSiteLocation::ClosureDef(local))); 248 | } 249 | } 250 | } 251 | } 252 | self.super_local_decl(local, local_decl); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/analysis/controldep/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_data_structures; 2 | extern crate rustc_index; 3 | extern crate rustc_middle; 4 | 5 | use std::collections::VecDeque; 6 | 7 | use rustc_data_structures::fx::FxHashSet; 8 | use rustc_index::Idx; 9 | use rustc_index::IndexVec; 10 | use rustc_middle::mir::{BasicBlock, Location}; 11 | 12 | use crate::analysis::postdom::{post_dominators, EndsControlFlowGraph}; 13 | 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | pub fn influences(this: Location, other: Location, control_deps: &ControlDeps) -> bool { 18 | if this.block == other.block { 19 | return false; 20 | } 21 | control_deps.influences(this.block, other.block) 22 | } 23 | 24 | #[derive(Clone, Debug)] 25 | pub struct ControlDeps { 26 | parents: IndexVec>, 27 | } 28 | 29 | impl ControlDeps { 30 | pub fn influences(&self, influencer: Node, influencee: Node) -> bool { 31 | let mut worklist = VecDeque::from_iter([influencee]); 32 | let mut visited = FxHashSet::from_iter([influencee]); 33 | while let Some(n) = worklist.pop_front() { 34 | if n == influencer { 35 | return true; 36 | } 37 | for p in &self.parents[n] { 38 | if visited.insert(*p) { 39 | worklist.push_front(*p) 40 | } 41 | } 42 | } 43 | false 44 | } 45 | } 46 | 47 | pub fn control_deps(graph: G) -> ControlDeps { 48 | let mut parents = IndexVec::from_elem_n(FxHashSet::default(), graph.num_nodes()); 49 | let pdt = post_dominators(&graph); 50 | let nodes = IndexVec::from_elem_n((), graph.num_nodes()); 51 | for (a, _) in nodes.iter_enumerated() { 52 | for b in graph.successors(a) { 53 | if a != b && pdt.is_post_dominated_by(a, b) { 54 | continue; 55 | } 56 | if let Some(l) = pdt.find_nearest_common_dominator(a, b) { 57 | if a == l { 58 | parents[a].insert(a); 59 | } 60 | for c in pdt.post_dominators(b) { 61 | if c == l { 62 | break; 63 | } else { 64 | parents[c].insert(a); 65 | } 66 | } 67 | } else { 68 | // (fake) end node 69 | for c in pdt.post_dominators(b) { 70 | parents[c].insert(a); 71 | } 72 | } 73 | } 74 | } 75 | let root = graph.start_node(); 76 | for c in pdt.post_dominators(root) { 77 | parents[c].insert(root); 78 | } 79 | 80 | ControlDeps { parents } 81 | } 82 | -------------------------------------------------------------------------------- /src/analysis/controldep/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::analysis::postdom::WithEndNodes; 2 | use rustc_data_structures::fx::FxHashMap; 3 | use rustc_data_structures::graph::{DirectedGraph, Predecessors, StartNode, Successors}; 4 | use std::cmp::max; 5 | 6 | use super::*; 7 | 8 | pub struct TestGraph { 9 | num_nodes: usize, 10 | start_node: usize, 11 | successors: FxHashMap>, 12 | predecessors: FxHashMap>, 13 | } 14 | 15 | impl TestGraph { 16 | pub fn new(start_node: usize, edges: &[(usize, usize)]) -> Self { 17 | let mut graph = TestGraph { 18 | num_nodes: start_node + 1, 19 | start_node, 20 | successors: FxHashMap::default(), 21 | predecessors: FxHashMap::default(), 22 | }; 23 | for &(source, target) in edges { 24 | graph.num_nodes = max(graph.num_nodes, source + 1); 25 | graph.num_nodes = max(graph.num_nodes, target + 1); 26 | graph.successors.entry(source).or_default().push(target); 27 | graph.predecessors.entry(target).or_default().push(source); 28 | } 29 | for node in 0..graph.num_nodes { 30 | graph.successors.entry(node).or_default(); 31 | graph.predecessors.entry(node).or_default(); 32 | } 33 | graph 34 | } 35 | } 36 | 37 | impl DirectedGraph for TestGraph { 38 | type Node = usize; 39 | 40 | fn num_nodes(&self) -> usize { 41 | self.num_nodes 42 | } 43 | } 44 | 45 | impl StartNode for TestGraph { 46 | fn start_node(&self) -> Self::Node { 47 | self.start_node 48 | } 49 | } 50 | 51 | impl WithEndNodes for TestGraph { 52 | fn end_nodes(&self) -> Vec { 53 | let mut result = vec![]; 54 | for node in rustc_data_structures::graph::depth_first_search(self, self.start_node()) { 55 | if self.successors(node).count() == 0 { 56 | result.push(node); 57 | } 58 | } 59 | result.reverse(); 60 | result 61 | } 62 | } 63 | 64 | impl Predecessors for TestGraph { 65 | fn predecessors(&self, node: Self::Node) -> impl Iterator { 66 | self.predecessors[&node].iter().cloned() 67 | } 68 | } 69 | 70 | impl Successors for TestGraph { 71 | fn successors(&self, node: Self::Node) -> impl Iterator { 72 | self.successors[&node].iter().cloned() 73 | } 74 | } 75 | 76 | #[test] 77 | fn diamond_parents() { 78 | let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3)]); 79 | let control_deps = control_deps(graph); 80 | assert_eq!( 81 | format!("{:?}", control_deps.parents), 82 | "[{0}, {0}, {0}, {0}]" 83 | ); 84 | } 85 | 86 | #[test] 87 | fn multi_ends_parents() { 88 | let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 4), (2, 5)]); 89 | let control_deps = control_deps(graph); 90 | assert_eq!( 91 | format!("{:?}", control_deps.parents), 92 | "[{0}, {0}, {0}, {0}, {2}, {2}]" 93 | ); 94 | } 95 | 96 | #[test] 97 | fn multi_ends_influences() { 98 | let graph = TestGraph::new( 99 | 0, 100 | &[ 101 | (0, 1), 102 | (0, 2), 103 | (1, 3), 104 | (1, 4), 105 | (2, 4), 106 | (2, 5), 107 | (3, 6), 108 | (4, 6), 109 | ], 110 | ); 111 | let control_deps = control_deps(graph); 112 | assert_eq!( 113 | format!("{:?}", control_deps.parents), 114 | "[{0}, {0}, {0}, {1}, {1, 2}, {2}, {0, 2}]" 115 | ); 116 | let true_pairs = [(1, 3), (1, 4), (2, 4), (2, 5), (2, 6)]; 117 | for i in 0..7 { 118 | for j in 0..7 { 119 | if i == 0 || i == j || true_pairs.contains(&(i, j)) { 120 | assert!(control_deps.influences(i, j)); 121 | } else { 122 | assert!(!control_deps.influences(i, j)); 123 | } 124 | } 125 | } 126 | } 127 | 128 | #[test] 129 | fn multi_ends_influences2() { 130 | let graph = TestGraph::new( 131 | 0, 132 | &[ 133 | (0, 1), 134 | (1, 2), 135 | (2, 3), 136 | (3, 4), 137 | (3, 5), 138 | (4, 6), 139 | (5, 6), 140 | (6, 10), 141 | (6, 7), 142 | (7, 8), 143 | (8, 10), 144 | (8, 9), 145 | (9, 10), 146 | (10, 11), 147 | (11, 12), 148 | (12, 13), 149 | ], 150 | ); 151 | let control_deps = control_deps(graph); 152 | println!("control_deps = {control_deps:?}"); 153 | println!("{}", control_deps.influences(3, 3)); 154 | println!("{}", control_deps.influences(3, 4)); 155 | println!("{}", control_deps.influences(3, 5)); 156 | println!("{}", control_deps.influences(3, 6)); 157 | println!("{}", control_deps.influences(3, 7)); 158 | println!("{}", control_deps.influences(5, 6)); 159 | println!("{}", control_deps.influences(5, 7)); 160 | // assert_eq!( 161 | // format!("{:?}", control_deps.parents), 162 | // "[{0}, {0}, {0}, {1}, {1, 2}, {2}, {0, 2}]" 163 | // ); 164 | // let true_pairs = [(1, 3), (1, 4), (2, 4), (2, 5), (2, 6)]; 165 | // for i in 0..7 { 166 | // for j in 0..7 { 167 | // if i == 0 || i == j || true_pairs.contains(&(i, j)) { 168 | // assert!(control_deps.influences(i, j)); 169 | // } else { 170 | // assert!(!control_deps.influences(i, j)); 171 | // } 172 | // } 173 | // } 174 | } 175 | -------------------------------------------------------------------------------- /src/analysis/datadep/mod.rs: -------------------------------------------------------------------------------- 1 | //! BSD 3-Clause License 2 | //! 3 | //! Copyright (c) 2022, Boqin Qin(秦 伯钦) 4 | //! All rights reserved. 5 | //! 6 | //! Check if a Local var A is (arithmetically) data-dependent on another Local var B 7 | //! by tracking move, copy and arithmetic statements from A to B. 8 | //! For now this analysis is limited to intraprocedural analysis and 9 | //! is for atomicity violation detector only. 10 | 11 | extern crate rustc_data_structures; 12 | extern crate rustc_index; 13 | extern crate rustc_middle; 14 | 15 | use std::collections::VecDeque; 16 | 17 | use rustc_data_structures::fx::FxHashSet; 18 | use rustc_index::IndexVec; 19 | use rustc_middle::mir::visit::Visitor; 20 | use rustc_middle::mir::{Body, Local, Location, Place, Rvalue}; 21 | 22 | pub fn all_data_dep_on(a: Local, data_deps: &DataDeps) -> FxHashSet { 23 | let mut worklist = VecDeque::from_iter(data_deps.immediate_dep(a)); 24 | let mut visited = FxHashSet::default(); 25 | while let Some(n) = worklist.pop_front() { 26 | if !visited.insert(n) { 27 | continue; 28 | } 29 | for succ in data_deps.immediate_dep(n).into_iter() { 30 | worklist.push_front(succ); 31 | } 32 | } 33 | visited 34 | } 35 | 36 | pub fn data_deps(body: &Body<'_>) -> DataDeps { 37 | let local_num = body.local_decls.len(); 38 | let v = IndexVec::from_elem_n(false, local_num); 39 | let immediate_deps = IndexVec::from_elem_n(v, local_num); 40 | let mut data_deps = DataDeps { immediate_deps }; 41 | data_deps.visit_body(body); 42 | data_deps 43 | } 44 | 45 | #[derive(Clone, Debug)] 46 | pub struct DataDeps { 47 | immediate_deps: IndexVec>, 48 | } 49 | 50 | impl DataDeps { 51 | fn immediate_dep(&self, local: Local) -> FxHashSet { 52 | self.immediate_deps[local] 53 | .iter_enumerated() 54 | .filter_map(|(local, v)| if *v { Some(local) } else { None }) 55 | .collect() 56 | } 57 | } 58 | 59 | impl<'tcx> Visitor<'tcx> for DataDeps { 60 | fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { 61 | let lhs = place.local; 62 | match rvalue { 63 | Rvalue::Use(operand) | Rvalue::Cast(_, operand, _) | Rvalue::UnaryOp(_, operand) => { 64 | if let Some(rhs) = operand.place() { 65 | self.immediate_deps[rhs.local][lhs] = true; 66 | } 67 | } 68 | Rvalue::BinaryOp(_, box (rhs0, rhs1)) => { 69 | if let Some(rhs0) = rhs0.place() { 70 | self.immediate_deps[rhs0.local][lhs] = true; 71 | } 72 | if let Some(rhs1) = rhs1.place() { 73 | self.immediate_deps[rhs1.local][lhs] = true; 74 | } 75 | } 76 | _ => {} 77 | } 78 | self.super_assign(place, rvalue, location); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/analysis/defuse/mod.rs: -------------------------------------------------------------------------------- 1 | //! Modified from 2 | //! 3 | extern crate rustc_middle; 4 | 5 | use std::collections::BTreeSet; 6 | 7 | use rustc_middle::mir::visit::Visitor; 8 | use rustc_middle::mir::{Body, Local, Location}; 9 | 10 | use rustc_middle::mir::visit::{ 11 | MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, 12 | }; 13 | 14 | #[derive(Eq, PartialEq, Clone)] 15 | pub enum DefUse { 16 | Def, 17 | Use, 18 | Drop, 19 | } 20 | 21 | pub fn categorize(context: PlaceContext) -> Option { 22 | match context { 23 | /////////////////////////////////////////////////////////////////////////// 24 | // DEFS 25 | 26 | PlaceContext::MutatingUse(MutatingUseContext::Store) | 27 | 28 | // We let Call define the result in both the success and 29 | // unwind cases. This is not really correct, however it 30 | // does not seem to be observable due to the way that we 31 | // generate MIR. To do things properly, we would apply 32 | // the def in call only to the input from the success 33 | // path and not the unwind path. -nmatsakis 34 | PlaceContext::MutatingUse(MutatingUseContext::Call) | 35 | PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) | 36 | PlaceContext::MutatingUse(MutatingUseContext::Yield) | 37 | 38 | // Storage live and storage dead aren't proper defines, but we can ignore 39 | // values that come before them. 40 | PlaceContext::NonUse(NonUseContext::StorageLive) | 41 | PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def), 42 | 43 | /////////////////////////////////////////////////////////////////////////// 44 | // REGULAR USES 45 | // 46 | // These are uses that occur *outside* of a drop. For the 47 | // purposes of NLL, these are special in that **all** the 48 | // lifetimes appearing in the variable must be live for each regular use. 49 | 50 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) | 51 | PlaceContext::MutatingUse(MutatingUseContext::Projection) | 52 | 53 | // Borrows only consider their local used at the point of the borrow. 54 | // This won't affect the results since we use this analysis for generators 55 | // and we only care about the result at suspension points. Borrows cannot 56 | // cross suspension points so this behavior is unproblematic. 57 | PlaceContext::MutatingUse(MutatingUseContext::Borrow) | 58 | PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) | 59 | PlaceContext::NonMutatingUse(NonMutatingUseContext::FakeBorrow) | 60 | PlaceContext::NonMutatingUse(NonMutatingUseContext::PlaceMention)| 61 | 62 | PlaceContext::MutatingUse(MutatingUseContext::RawBorrow) | 63 | PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) | 64 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) | 65 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) | 66 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) | 67 | PlaceContext::NonUse(NonUseContext::AscribeUserTy(_)) | 68 | PlaceContext::MutatingUse(MutatingUseContext::Retag) => 69 | Some(DefUse::Use), 70 | 71 | /////////////////////////////////////////////////////////////////////////// 72 | // DROP USES 73 | // 74 | // These are uses that occur in a DROP (a MIR drop, not a 75 | // call to `std::mem::drop()`). For the purposes of NLL, 76 | // uses in drop are special because `#[may_dangle]` 77 | // attributes can affect whether lifetimes must be live. 78 | 79 | PlaceContext::MutatingUse(MutatingUseContext::Drop) => 80 | Some(DefUse::Drop), 81 | 82 | // Debug info is neither def nor use. 83 | PlaceContext::NonUse(NonUseContext::VarDebugInfo) => None, 84 | 85 | PlaceContext::MutatingUse(MutatingUseContext::Deinit | MutatingUseContext::SetDiscriminant) => { 86 | None 87 | } 88 | } 89 | } 90 | 91 | pub fn find_uses(body: &Body<'_>, local: Local) -> BTreeSet { 92 | let mut visitor = AllLocalUsesVisitor { 93 | for_local: local, 94 | uses: BTreeSet::default(), 95 | }; 96 | visitor.visit_body(body); 97 | visitor.uses 98 | } 99 | 100 | struct AllLocalUsesVisitor { 101 | for_local: Local, 102 | uses: BTreeSet, 103 | } 104 | 105 | impl Visitor<'_> for AllLocalUsesVisitor { 106 | fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { 107 | if local == self.for_local { 108 | if let Some(DefUse::Use) = categorize(context) { 109 | self.uses.insert(location); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/analysis/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod callgraph; 2 | pub mod controldep; 3 | pub mod datadep; 4 | pub mod defuse; 5 | pub mod pointsto; 6 | pub mod postdom; 7 | -------------------------------------------------------------------------------- /src/analysis/postdom/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | extern crate rustc_data_structures; 3 | extern crate rustc_index; 4 | extern crate rustc_middle; 5 | 6 | use rustc_data_structures::graph::{ControlFlowGraph, DirectedGraph, Predecessors, Successors}; 7 | use rustc_index::{Idx, IndexVec}; 8 | use rustc_middle::mir::{BasicBlock, BasicBlocks, Location, TerminatorKind}; 9 | use std::borrow::BorrowMut; 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | 14 | pub fn post_dominates( 15 | this: Location, 16 | other: Location, 17 | post_dominators: &PostDominators, 18 | ) -> bool { 19 | if this.block == other.block { 20 | other.statement_index <= this.statement_index 21 | } else { 22 | post_dominators.is_post_dominated_by(other.block, this.block) 23 | } 24 | } 25 | 26 | pub trait WithEndNodes: DirectedGraph { 27 | fn end_nodes(&self) -> Vec; 28 | } 29 | 30 | impl WithEndNodes for &G { 31 | fn end_nodes(&self) -> Vec { 32 | (**self).end_nodes() 33 | } 34 | } 35 | 36 | impl WithEndNodes for BasicBlocks<'_> { 37 | #[inline] 38 | fn end_nodes(&self) -> Vec { 39 | self.iter_enumerated() 40 | .filter_map(|(bb, bb_data)| { 41 | if self.successors(bb).count() == 0 { 42 | if bb_data.terminator().kind == TerminatorKind::Return { 43 | Some(bb) 44 | } else { 45 | None 46 | } 47 | } else { 48 | None 49 | } 50 | }) 51 | .collect() 52 | } 53 | } 54 | 55 | pub trait EndsControlFlowGraph: ControlFlowGraph + WithEndNodes { 56 | // convenient trait 57 | } 58 | 59 | impl EndsControlFlowGraph for T where T: ControlFlowGraph + WithEndNodes {} 60 | 61 | pub fn post_dominators(graph: G) -> PostDominators { 62 | let end_nodes = graph.end_nodes(); 63 | let rpo = postdom_reverse_post_order(&graph, end_nodes); 64 | post_dominators_given_rpo(graph, &rpo) 65 | } 66 | 67 | pub fn postdom_reverse_post_order( 68 | graph: &G, 69 | end_nodes: Vec, 70 | ) -> Vec { 71 | let mut vec = postdom_post_order_from(graph, end_nodes); 72 | vec.reverse(); 73 | vec 74 | } 75 | 76 | pub fn postdom_post_order_from( 77 | graph: &G, 78 | end_nodes: Vec, 79 | ) -> Vec { 80 | postdom_post_order_from_to(graph, end_nodes, None) 81 | } 82 | 83 | pub fn postdom_post_order_from_to( 84 | graph: &G, 85 | end_nodes: Vec, 86 | start_node: Option, 87 | ) -> Vec { 88 | let mut visited: IndexVec = IndexVec::from_elem_n(false, graph.num_nodes()); 89 | let mut result: Vec = Vec::with_capacity(graph.num_nodes()); 90 | if let Some(start_node) = start_node { 91 | visited[start_node] = true; 92 | } 93 | for end_node in end_nodes { 94 | postdom_post_order_walk(graph, end_node, &mut result, &mut visited); 95 | } 96 | result 97 | } 98 | 99 | fn postdom_post_order_walk( 100 | graph: &G, 101 | node: G::Node, 102 | result: &mut Vec, 103 | visited: &mut IndexVec, 104 | ) { 105 | if visited[node] { 106 | return; 107 | } 108 | visited[node] = true; 109 | 110 | for predecessor in graph.predecessors(node) { 111 | postdom_post_order_walk(graph, predecessor, result, visited); 112 | } 113 | 114 | result.push(node); 115 | } 116 | 117 | fn post_dominators_given_rpo + WithEndNodes>( 118 | mut graph: G, 119 | rpo: &[G::Node], 120 | ) -> PostDominators { 121 | let end_nodes = graph.borrow().end_nodes(); 122 | let end_idx = rpo.len() - 1; 123 | 124 | // compute the post order index (rank) for each node 125 | let mut post_order_rank: IndexVec = 126 | (0..graph.borrow().num_nodes()).map(|_| 0).collect(); 127 | for (index, node) in rpo.iter().rev().cloned().enumerate() { 128 | post_order_rank[node] = index; 129 | } 130 | for node in end_nodes.iter().copied() { 131 | post_order_rank[node] = end_idx; 132 | } 133 | 134 | let mut immediate_post_dominators: IndexVec> = 135 | (0..graph.borrow().num_nodes()) 136 | .map(|_| ExtNode::Real(None)) 137 | .collect(); 138 | 139 | for node in end_nodes.iter().copied() { 140 | immediate_post_dominators[node] = ExtNode::Real(Some(node)); 141 | } 142 | 143 | let mut changed = true; 144 | while changed { 145 | changed = false; 146 | for &node in rpo { 147 | if end_nodes.contains(&node) { 148 | continue; 149 | } 150 | let mut new_ipdom = ExtNode::Real(None); 151 | for succ in graph.borrow_mut().successors(node) { 152 | match immediate_post_dominators[succ] { 153 | ExtNode::Real(Some(_)) => { 154 | new_ipdom = match new_ipdom { 155 | ExtNode::Real(Some(new_ipdom)) => intersect( 156 | &post_order_rank, 157 | &immediate_post_dominators, 158 | &end_nodes, 159 | new_ipdom, 160 | succ, 161 | ), 162 | ExtNode::Real(None) => ExtNode::Real(Some(succ)), 163 | ExtNode::Fake => ExtNode::Fake, 164 | }; 165 | } 166 | ExtNode::Real(None) => { 167 | // pass 168 | } 169 | ExtNode::Fake => { 170 | new_ipdom = ExtNode::Fake; 171 | } 172 | } 173 | } 174 | 175 | if new_ipdom != immediate_post_dominators[node] { 176 | immediate_post_dominators[node] = new_ipdom; 177 | changed = true; 178 | } 179 | } 180 | } 181 | 182 | PostDominators { 183 | post_order_rank, 184 | immediate_post_dominators, 185 | } 186 | } 187 | 188 | fn intersect( 189 | post_order_rank: &IndexVec, 190 | immediate_post_dominators: &IndexVec>, 191 | end_nodes: &[Node], 192 | mut node1: Node, 193 | mut node2: Node, 194 | ) -> ExtNode { 195 | while node1 != node2 { 196 | if end_nodes.contains(&node1) && end_nodes.contains(&node2) { 197 | return ExtNode::Fake; 198 | } 199 | while post_order_rank[node1] < post_order_rank[node2] { 200 | match immediate_post_dominators[node1] { 201 | ExtNode::Real(Some(n)) => node1 = n, 202 | ExtNode::Real(None) | ExtNode::Fake => break, 203 | }; 204 | } 205 | 206 | while post_order_rank[node2] < post_order_rank[node1] { 207 | match immediate_post_dominators[node2] { 208 | ExtNode::Real(Some(n)) => node2 = n, 209 | ExtNode::Real(None) | ExtNode::Fake => break, 210 | }; 211 | } 212 | } 213 | 214 | ExtNode::Real(Some(node1)) 215 | } 216 | 217 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 218 | pub enum ExtNode { 219 | Real(Option), 220 | Fake, 221 | } 222 | 223 | impl ExtNode { 224 | pub fn is_none(&self) -> bool { 225 | matches!(self, ExtNode::Real(None)) 226 | } 227 | } 228 | 229 | #[derive(Clone, Debug)] 230 | pub struct PostDominators { 231 | post_order_rank: IndexVec, 232 | immediate_post_dominators: IndexVec>, 233 | } 234 | 235 | impl PostDominators { 236 | pub fn is_reachable(&self, node: Node) -> bool { 237 | match self.immediate_post_dominators[node] { 238 | ExtNode::Real(None) => false, 239 | ExtNode::Real(Some(_)) => true, 240 | ExtNode::Fake => true, 241 | } 242 | } 243 | 244 | pub fn immediate_post_dominator(&self, node: Node) -> ExtNode { 245 | // assert!(self.is_reachable(node), "node {:?} is not reachable", node); 246 | self.immediate_post_dominators[node] 247 | } 248 | 249 | pub fn post_dominators(&self, node: Node) -> Iter<'_, Node> { 250 | // assert!(self.is_reachable(node), "node {:?} is not reachable", node); 251 | Iter { 252 | post_dominators: self, 253 | node: ExtNode::Real(Some(node)), 254 | } 255 | } 256 | 257 | pub fn is_post_dominated_by(&self, node: Node, dom: Node) -> bool { 258 | // FIXME -- could be optimized by using post-order-rank 259 | self.post_dominators(node).any(|n| n == dom) 260 | } 261 | 262 | #[allow(clippy::needless_collect)] 263 | pub fn find_nearest_common_dominator(&self, node1: Node, node2: Node) -> Option { 264 | if node1 == node2 { 265 | return Some(node1); 266 | } 267 | 268 | let pd1: Vec<_> = self.post_dominators(node1).collect(); 269 | let pd2: Vec<_> = self.post_dominators(node2).collect(); 270 | let mut common = None; 271 | // post_dominators iter does not implement DoubleEndedIterator, thus cannot directly call `rev()` 272 | for (n1, n2) in pd1.into_iter().rev().zip(pd2.into_iter().rev()) { 273 | if n1 != n2 { 274 | break; 275 | } else { 276 | common = Some(n1); 277 | } 278 | } 279 | common 280 | } 281 | } 282 | 283 | pub struct Iter<'dom, Node: Idx> { 284 | post_dominators: &'dom PostDominators, 285 | node: ExtNode, 286 | } 287 | 288 | impl Iterator for Iter<'_, Node> { 289 | type Item = Node; 290 | 291 | fn next(&mut self) -> Option { 292 | match self.node { 293 | ExtNode::Real(Some(node)) => { 294 | match self.post_dominators.immediate_post_dominator(node) { 295 | ExtNode::Real(Some(dom)) => { 296 | if dom == node { 297 | self.node = ExtNode::Real(None); // reached the root 298 | } else { 299 | self.node = ExtNode::Real(Some(dom)); 300 | } 301 | Some(node) 302 | } 303 | ExtNode::Real(None) => { 304 | // panic!("post_dominators have uncomputed nodes"); 305 | None 306 | } 307 | ExtNode::Fake => { 308 | self.node = ExtNode::Fake; 309 | Some(node) 310 | } 311 | } 312 | } 313 | ExtNode::Real(None) => None, 314 | ExtNode::Fake => None, 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/analysis/postdom/tests.rs: -------------------------------------------------------------------------------- 1 | use rustc_data_structures::fx::FxHashMap; 2 | use std::cmp::max; 3 | 4 | use super::*; 5 | 6 | use rustc_data_structures::graph::{Predecessors, StartNode, Successors}; 7 | 8 | pub struct TestGraph { 9 | num_nodes: usize, 10 | start_node: usize, 11 | successors: FxHashMap>, 12 | predecessors: FxHashMap>, 13 | } 14 | 15 | impl TestGraph { 16 | pub fn new(start_node: usize, edges: &[(usize, usize)]) -> Self { 17 | let mut graph = TestGraph { 18 | num_nodes: start_node + 1, 19 | start_node, 20 | successors: FxHashMap::default(), 21 | predecessors: FxHashMap::default(), 22 | }; 23 | for &(source, target) in edges { 24 | graph.num_nodes = max(graph.num_nodes, source + 1); 25 | graph.num_nodes = max(graph.num_nodes, target + 1); 26 | graph.successors.entry(source).or_default().push(target); 27 | graph.predecessors.entry(target).or_default().push(source); 28 | } 29 | for node in 0..graph.num_nodes { 30 | graph.successors.entry(node).or_default(); 31 | graph.predecessors.entry(node).or_default(); 32 | } 33 | graph 34 | } 35 | } 36 | 37 | impl DirectedGraph for TestGraph { 38 | type Node = usize; 39 | 40 | fn num_nodes(&self) -> usize { 41 | self.num_nodes 42 | } 43 | } 44 | 45 | impl StartNode for TestGraph { 46 | fn start_node(&self) -> Self::Node { 47 | self.start_node 48 | } 49 | } 50 | 51 | impl WithEndNodes for TestGraph { 52 | fn end_nodes(&self) -> Vec { 53 | let mut result = vec![]; 54 | for node in rustc_data_structures::graph::depth_first_search(self, self.start_node()) { 55 | if self.successors(node).count() == 0 { 56 | result.push(node); 57 | } 58 | } 59 | result.reverse(); 60 | result 61 | } 62 | } 63 | 64 | impl Predecessors for TestGraph { 65 | fn predecessors(&self, node: Self::Node) -> impl Iterator { 66 | self.predecessors[&node].iter().cloned() 67 | } 68 | } 69 | 70 | impl Successors for TestGraph { 71 | fn successors(&self, node: Self::Node) -> impl Iterator { 72 | self.successors[&node].iter().cloned() 73 | } 74 | } 75 | 76 | #[test] 77 | fn diamond_post_order() { 78 | let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3)]); 79 | 80 | let result = postdom_post_order_from(&graph, vec![3usize]); 81 | assert_eq!(result, vec![0, 1, 2, 3]); 82 | let pdt = post_dominators(graph); 83 | assert_eq!(pdt.find_nearest_common_dominator(1, 3), Some(3)); 84 | assert_eq!(pdt.find_nearest_common_dominator(3, 1), Some(3)); 85 | assert_eq!(pdt.find_nearest_common_dominator(1, 2), Some(3)); 86 | assert_eq!(pdt.find_nearest_common_dominator(2, 1), Some(3)); 87 | assert_eq!(pdt.find_nearest_common_dominator(0, 1), Some(3)); 88 | assert_eq!(pdt.find_nearest_common_dominator(1, 0), Some(3)); 89 | assert_eq!(pdt.find_nearest_common_dominator(0, 2), Some(3)); 90 | assert_eq!(pdt.find_nearest_common_dominator(2, 0), Some(3)); 91 | assert_eq!(pdt.find_nearest_common_dominator(0, 0), Some(0)); 92 | assert_eq!(pdt.find_nearest_common_dominator(0, 3), Some(3)); 93 | assert_eq!(pdt.find_nearest_common_dominator(3, 0), Some(3)); 94 | assert_eq!(pdt.find_nearest_common_dominator(1, 1), Some(1)); 95 | assert_eq!(pdt.find_nearest_common_dominator(2, 2), Some(2)); 96 | assert_eq!(pdt.find_nearest_common_dominator(3, 3), Some(3)); 97 | } 98 | 99 | #[test] 100 | fn multi_ends_post_order() { 101 | let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 4), (2, 5)]); 102 | 103 | let result = postdom_post_order_from(&graph, vec![3, 4, 5]); 104 | assert_eq!(result, vec![0, 1, 3, 2, 4, 5]); 105 | let pdt = post_dominators(graph); 106 | assert_eq!(pdt.find_nearest_common_dominator(0, 0), Some(0)); 107 | assert_eq!(pdt.find_nearest_common_dominator(0, 1), None); 108 | assert_eq!(pdt.find_nearest_common_dominator(0, 3), None); 109 | assert_eq!(pdt.find_nearest_common_dominator(1, 2), None); 110 | assert_eq!(pdt.find_nearest_common_dominator(1, 3), Some(3)); 111 | assert_eq!(pdt.find_nearest_common_dominator(2, 4), None); 112 | assert_eq!(pdt.find_nearest_common_dominator(2, 5), None); 113 | assert_eq!(pdt.find_nearest_common_dominator(1, 4), None); 114 | assert_eq!(pdt.find_nearest_common_dominator(2, 3), None); 115 | assert_eq!(pdt.find_nearest_common_dominator(3, 4), None); 116 | assert_eq!(pdt.find_nearest_common_dominator(4, 5), None); 117 | } 118 | 119 | #[test] 120 | fn multi_ends_postdom() { 121 | let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 4), (2, 5), (3, 6), (4, 6)]); 122 | let pdt = post_dominators(graph); 123 | for n in 0..7usize { 124 | println!("node: {:?}", n); 125 | for pd in pdt.post_dominators(n) { 126 | println!("pdt: {:?}", pd); 127 | } 128 | } 129 | assert_eq!(pdt.find_nearest_common_dominator(3, 6), Some(6)); 130 | assert_eq!(pdt.find_nearest_common_dominator(1, 6), Some(6)); 131 | assert_eq!(pdt.find_nearest_common_dominator(4, 6), Some(6)); 132 | assert_eq!(pdt.find_nearest_common_dominator(1, 3), Some(3)); 133 | assert_eq!(pdt.find_nearest_common_dominator(2, 6), None); 134 | assert_eq!(pdt.find_nearest_common_dominator(2, 4), None); 135 | } 136 | -------------------------------------------------------------------------------- /src/bin/cargo-lockbud.rs: -------------------------------------------------------------------------------- 1 | //! `cargo lockbud $FLAGS $ARGS` calls `cargo build` with RUSTC_WRAPPER set to `lockbud`. 2 | //! The flags are passed to `lockbud` through env var `LOCKBUD_FLAGS`. 3 | //! The remainining args are unchanged. 4 | //! To re-run `cargo lockbud` with different flags on the same crate, please `cargo clean` first. 5 | use std::env; 6 | use std::ffi::OsString; 7 | use std::process::Command; 8 | 9 | const CARGO_LOCKBUD_HELP: &str = r#"Statically detect bugs on MIR 10 | Usage: 11 | cargo lockbud [options] [--] [...] 12 | Common options: 13 | -h, --help Print this message 14 | -V, --version Print version info and exit 15 | -k, --detector-kind Choose detector, deadlock 16 | -b, --blacklist-mode Use crate-name-list as blacklist, whitelist if not specified 17 | -l, --crate-name-list Will not white-or-black list the crates if not specified. 18 | 19 | Options after the first "--" are the same arguments that `cargo build` accepts. 20 | 21 | Examples: 22 | # only detect [mycrate1, mycrate2] 23 | cargo lockbud -k deadlock -l mycrate1,mycrate2 24 | # skip detecting [mycrate1, mycrate2] 25 | cargo lockbud -k deadlock -b -l mycrate1,mycrate2 26 | # canonical command with toolchain overridden and target triple specified 27 | cargo +nightly-2024-12-01 lockbud -k all -- --target riscv64gc-unknown-none-elf 28 | "#; 29 | 30 | fn show_help() { 31 | println!("{}", CARGO_LOCKBUD_HELP); 32 | } 33 | 34 | fn show_version() { 35 | println!("lockbud 0.2.0"); 36 | } 37 | 38 | fn cargo() -> Command { 39 | Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"))) 40 | } 41 | 42 | // Determines whether a `--flag` is present. 43 | fn has_arg_flag(name: &str) -> bool { 44 | let mut args = std::env::args().take_while(|val| val != "--"); 45 | args.any(|val| val == name) 46 | } 47 | 48 | fn in_cargo_lockbud() { 49 | // Now we run `cargo build $FLAGS $ARGS`, giving the user the 50 | // change to add additional arguments. `FLAGS` is set to identify 51 | // this target. The user gets to control what gets actually passed to lockbud. 52 | let mut cmd = cargo(); 53 | cmd.arg("build"); 54 | cmd.env("RUSTC_WRAPPER", "lockbud"); 55 | cmd.env("RUST_BACKTRACE", "full"); 56 | 57 | // Pass LOCKBUD_LOG if specified by the user. Default to info if not specified. 58 | const LOCKBUD_LOG: &str = "LOCKBUD_LOG"; 59 | let log_level = env::var(LOCKBUD_LOG).ok(); 60 | cmd.env(LOCKBUD_LOG, log_level.as_deref().unwrap_or("info")); 61 | 62 | let mut args = std::env::args().skip(2); 63 | 64 | let flags: Vec<_> = args.by_ref().take_while(|arg| arg != "--").collect(); 65 | let flags = flags.join(" "); 66 | cmd.env("LOCKBUD_FLAGS", flags); 67 | 68 | let exit_status = cmd 69 | .args(args) 70 | .spawn() 71 | .expect("could not run cargo") 72 | .wait() 73 | .expect("failed to wait for cargo?"); 74 | if !exit_status.success() { 75 | std::process::exit(exit_status.code().unwrap_or(-1)) 76 | }; 77 | } 78 | 79 | fn main() { 80 | if has_arg_flag("--help") || has_arg_flag("-h") { 81 | show_help(); 82 | return; 83 | } 84 | if has_arg_flag("--version") || has_arg_flag("-V") { 85 | show_version(); 86 | return; 87 | } 88 | if let Some("lockbud") = std::env::args().nth(1).as_deref() { 89 | in_cargo_lockbud(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/callbacks.rs: -------------------------------------------------------------------------------- 1 | //! The main functionality: callbacks for rustc plugin systems. 2 | //! Inspired by 3 | extern crate rustc_driver; 4 | extern crate rustc_hir; 5 | 6 | use std::path::PathBuf; 7 | 8 | use crate::analysis::pointsto::AliasAnalysis; 9 | use crate::detector::memory::{InvalidFreeDetector, UseAfterFreeDetector}; 10 | use crate::options::{CrateNameList, DetectorKind, Options}; 11 | use log::{debug, warn}; 12 | use rustc_driver::Compilation; 13 | use rustc_hir::def_id::LOCAL_CRATE; 14 | use rustc_interface::interface; 15 | use rustc_middle::mir::mono::MonoItem; 16 | use rustc_middle::ty::{Instance, TyCtxt, TypingEnv}; 17 | 18 | use crate::analysis::callgraph::CallGraph; 19 | 20 | use crate::detector::atomic::AtomicityViolationDetector; 21 | use crate::detector::lock::DeadlockDetector; 22 | use crate::detector::panic::PanicDetector; 23 | use crate::detector::report::Report; 24 | 25 | pub struct LockBudCallbacks { 26 | options: Options, 27 | file_name: String, 28 | output_directory: PathBuf, 29 | test_run: bool, 30 | } 31 | 32 | impl LockBudCallbacks { 33 | pub fn new(options: Options) -> Self { 34 | Self { 35 | options, 36 | file_name: String::new(), 37 | output_directory: PathBuf::default(), 38 | test_run: false, 39 | } 40 | } 41 | } 42 | 43 | impl rustc_driver::Callbacks for LockBudCallbacks { 44 | fn config(&mut self, config: &mut rustc_interface::interface::Config) { 45 | self.file_name = config 46 | .input 47 | .source_name() 48 | .prefer_remapped_unconditionaly() 49 | .to_string(); 50 | debug!("Processing input file: {}", self.file_name); 51 | if config.opts.test { 52 | debug!("in test only mode"); 53 | // self.options.test_only = true; 54 | } 55 | match &config.output_dir { 56 | None => { 57 | self.output_directory = std::env::temp_dir(); 58 | self.output_directory.pop(); 59 | } 60 | Some(path_buf) => self.output_directory.push(path_buf.as_path()), 61 | } 62 | } 63 | fn after_analysis( 64 | &mut self, 65 | compiler: &rustc_interface::interface::Compiler, 66 | tcx: TyCtxt<'_>, 67 | ) -> rustc_driver::Compilation { 68 | compiler.sess.dcx().abort_if_errors(); 69 | if self 70 | .output_directory 71 | .to_str() 72 | .expect("valid string") 73 | .contains("/build/") 74 | { 75 | // No need to analyze a build script, but do generate code. 76 | return Compilation::Continue; 77 | } 78 | self.analyze_with_lockbud(compiler, tcx); 79 | if self.test_run { 80 | // We avoid code gen for test cases because LLVM is not used in a thread safe manner. 81 | Compilation::Stop 82 | } else { 83 | // Although LockBud is only a checker, cargo still needs code generation to work. 84 | Compilation::Continue 85 | } 86 | } 87 | } 88 | 89 | impl LockBudCallbacks { 90 | fn analyze_with_lockbud<'tcx>(&mut self, _compiler: &interface::Compiler, tcx: TyCtxt<'tcx>) { 91 | // Skip crates by names (white or black list). 92 | let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); 93 | match &self.options.crate_name_list { 94 | CrateNameList::White(crates) if !crates.is_empty() && !crates.contains(&crate_name) => { 95 | return 96 | } 97 | CrateNameList::Black(crates) if crates.contains(&crate_name) => return, 98 | _ => {} 99 | }; 100 | if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() { 101 | return; 102 | } 103 | let cgus = tcx.collect_and_partition_mono_items(()).codegen_units; 104 | let instances: Vec> = cgus 105 | .iter() 106 | .flat_map(|cgu| { 107 | cgu.items().iter().filter_map(|(mono_item, _)| { 108 | if let MonoItem::Fn(instance) = mono_item { 109 | Some(*instance) 110 | } else { 111 | None 112 | } 113 | }) 114 | }) 115 | .collect(); 116 | let mut callgraph = CallGraph::new(); 117 | let typing_env = TypingEnv::fully_monomorphized(); 118 | callgraph.analyze(instances.clone(), tcx, typing_env); 119 | let mut alias_analysis = AliasAnalysis::new(tcx, &callgraph); 120 | match self.options.detector_kind { 121 | DetectorKind::Deadlock => { 122 | debug!("Detecting deadlock"); 123 | let mut deadlock_detector = DeadlockDetector::new(tcx, typing_env); 124 | let reports = deadlock_detector.detect(&callgraph, &mut alias_analysis); 125 | if !reports.is_empty() { 126 | let j = serde_json::to_string_pretty(&reports).unwrap(); 127 | warn!("{}", j); 128 | let stats = report_stats(&crate_name, &reports); 129 | warn!("{}", stats); 130 | } 131 | } 132 | DetectorKind::AtomicityViolation => { 133 | debug!("Detecting atomicity violation"); 134 | let mut atomicity_violation_detector = AtomicityViolationDetector::new(tcx); 135 | let reports = atomicity_violation_detector.detect(&callgraph, &mut alias_analysis); 136 | if !reports.is_empty() { 137 | let j = serde_json::to_string_pretty(&reports).unwrap(); 138 | warn!("{}", j); 139 | let stats = report_stats(&crate_name, &reports); 140 | warn!("{}", stats); 141 | } 142 | } 143 | DetectorKind::Memory => { 144 | debug!("Detecting memory bugs"); 145 | let mut reports = { 146 | let invalid_free_detector = InvalidFreeDetector::new(tcx); 147 | invalid_free_detector.detect(&callgraph, &mut alias_analysis) 148 | }; 149 | let reports2 = { 150 | let use_after_free_detector = UseAfterFreeDetector::new(tcx); 151 | use_after_free_detector.detect(&callgraph, &mut alias_analysis) 152 | }; 153 | reports.extend(reports2); 154 | if !reports.is_empty() { 155 | let j = serde_json::to_string_pretty(&reports).unwrap(); 156 | warn!("{}", j); 157 | let stats = report_stats(&crate_name, &reports); 158 | warn!("{}", stats); 159 | } 160 | } 161 | DetectorKind::All => { 162 | debug!("Detecting all bugs"); 163 | let mut reports; 164 | { 165 | let mut deadlock_detector = DeadlockDetector::new(tcx, typing_env); 166 | reports = deadlock_detector.detect(&callgraph, &mut alias_analysis); 167 | } 168 | { 169 | let mut atomicity_violation_detector = AtomicityViolationDetector::new(tcx); 170 | reports.extend( 171 | atomicity_violation_detector.detect(&callgraph, &mut alias_analysis), 172 | ); 173 | } 174 | { 175 | let invalid_free_detector = InvalidFreeDetector::new(tcx); 176 | reports.extend(invalid_free_detector.detect(&callgraph, &mut alias_analysis)); 177 | } 178 | { 179 | let use_after_free_detector = UseAfterFreeDetector::new(tcx); 180 | reports.extend(use_after_free_detector.detect(&callgraph, &mut alias_analysis)); 181 | } 182 | if !reports.is_empty() { 183 | let j = serde_json::to_string_pretty(&reports).unwrap(); 184 | warn!("{}", j); 185 | let stats = report_stats(&crate_name, &reports); 186 | warn!("{}", stats); 187 | } 188 | } 189 | DetectorKind::Panic => { 190 | debug!("Detecting panic sites"); 191 | let mut detector = PanicDetector::new(tcx); 192 | for instance in instances { 193 | detector.detect(instance); 194 | } 195 | for (i, (k, v)) in detector.result().iter().enumerate() { 196 | println!( 197 | "PANIC[{}#{}]: {:?}: span[{:?}], outermost_span[{:?}], {:?}", 198 | tcx.crate_name(LOCAL_CRATE), 199 | i, 200 | k, 201 | v.0, 202 | v.1, 203 | v.2 204 | ); 205 | } 206 | for (panic_api, cnt) in detector.statistics() { 207 | println!("{}: {:?}: {}", tcx.crate_name(LOCAL_CRATE), panic_api, cnt); 208 | } 209 | } 210 | } 211 | } 212 | } 213 | 214 | fn report_stats(crate_name: &str, reports: &[Report]) -> String { 215 | let ( 216 | mut doublelock_probably, 217 | mut doublelock_possibly, 218 | mut conflictlock_probably, 219 | mut conflictlock_possibly, 220 | mut condvar_deadlock_probably, 221 | mut condvar_deadlock_possibly, 222 | mut atomicity_violation_possibly, 223 | mut invalid_free_possibly, 224 | mut use_after_free_possibly, 225 | ) = (0, 0, 0, 0, 0, 0, 0, 0, 0); 226 | for report in reports { 227 | match report { 228 | Report::DoubleLock(doublelock) => match doublelock.possibility.as_str() { 229 | "Probably" => doublelock_probably += 1, 230 | "Possibly" => doublelock_possibly += 1, 231 | _ => {} 232 | }, 233 | Report::ConflictLock(conflictlock) => match conflictlock.possibility.as_str() { 234 | "Probably" => conflictlock_probably += 1, 235 | "Possibly" => conflictlock_possibly += 1, 236 | _ => {} 237 | }, 238 | Report::CondvarDeadlock(condvar_deadlock) => { 239 | match condvar_deadlock.possibility.as_str() { 240 | "Probably" => condvar_deadlock_probably += 1, 241 | "Possibly" => condvar_deadlock_possibly += 1, 242 | _ => {} 243 | } 244 | } 245 | Report::AtomicityViolation(_) => { 246 | atomicity_violation_possibly += 1; 247 | } 248 | Report::InvalidFree(_) => { 249 | invalid_free_possibly += 1; 250 | } 251 | Report::UseAfterFree(_) => { 252 | use_after_free_possibly += 1; 253 | } 254 | } 255 | } 256 | format!("crate {} contains bugs: {{ probably: {}, possibly: {} }}, conflictlock: {{ probably: {}, possibly: {} }}, condvar_deadlock: {{ probably: {}, possibly: {} }}, atomicity_violation: {{ possibly: {} }}, invalid_free: {{ possibly: {} }}, use_after_free: {{ possibly: {} }}", crate_name, doublelock_probably, doublelock_possibly, conflictlock_probably, conflictlock_possibly, condvar_deadlock_probably, condvar_deadlock_possibly, atomicity_violation_possibly, invalid_free_possibly, use_after_free_possibly) 257 | } 258 | 259 | #[cfg(test)] 260 | mod tests { 261 | use super::*; 262 | 263 | #[test] 264 | fn test_report_stats() { 265 | assert_eq!(report_stats("dummy", &[]), format!("crate {} contains bugs: {{ probably: {}, possibly: {} }}, conflictlock: {{ probably: {}, possibly: {} }}, condvar_deadlock: {{ probably: {}, possibly: {} }}, atomicity_violation: {{ possibly: {} }}, invalid_free: {{ possibly: {} }}, use_after_free: {{ possibly: {} }}", "dummy", 0, 0, 0, 0, 0, 0, 0, 0, 0)); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/detector/atomic/mod.rs: -------------------------------------------------------------------------------- 1 | //! Detect atomicity violation caused by misuse of atomic variables. 2 | //! Currently only support the following two patterns: 3 | //! ```no_run 4 | //! // atomic::store is control dep on atomic::load 5 | //! if atomic.load(order) == v1 { 6 | //! atomic.store(v2, order); 7 | //! } 8 | //! 9 | //! // atomic::store is data dep on atomic::load 10 | //! let v1 = atomic_load(order); 11 | //! let v2 = v1 + 1; 12 | //! atomic.store(v2, order); 13 | //! ``` 14 | extern crate rustc_data_structures; 15 | extern crate rustc_hash; 16 | extern crate rustc_middle; 17 | use rustc_hash::FxHashMap; 18 | use rustc_middle::mir::{BasicBlock, Body, Local, Location, Place, TerminatorKind}; 19 | use rustc_middle::ty::TyCtxt; 20 | 21 | pub mod report; 22 | use crate::analysis::callgraph::{CallGraph, InstanceId}; 23 | use crate::analysis::controldep; 24 | use crate::analysis::datadep; 25 | use crate::analysis::defuse; 26 | use crate::analysis::pointsto::{AliasAnalysis, AliasId, ApproximateAliasKind}; 27 | use crate::detector::report::{Report, ReportContent}; 28 | use crate::interest::concurrency::atomic::AtomicApi; 29 | use report::AtomicityViolationDiagnosis; 30 | 31 | use petgraph::visit::IntoNodeReferences; 32 | use std::collections::BTreeSet; 33 | 34 | pub struct AtomicityViolationDetector<'tcx> { 35 | tcx: TyCtxt<'tcx>, 36 | } 37 | 38 | impl<'tcx> AtomicityViolationDetector<'tcx> { 39 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 40 | Self { tcx } 41 | } 42 | 43 | /// Collect atomic APIs. 44 | /// Rerturn the atomic API's InstanceId and kind. 45 | fn collect_atomics(&self, callgraph: &CallGraph<'tcx>) -> FxHashMap { 46 | callgraph 47 | .graph 48 | .node_references() 49 | .filter_map(|(instance_id, node)| { 50 | AtomicApi::from_instance(*node.instance(), self.tcx) 51 | .map(|atomic_api| (instance_id, atomic_api)) 52 | }) 53 | .collect() 54 | } 55 | 56 | /// Detect atomicity violation intra-procedurally and returns bug report. 57 | pub fn detect<'a>( 58 | &mut self, 59 | callgraph: &'a CallGraph<'tcx>, 60 | alias_analysis: &mut AliasAnalysis<'a, 'tcx>, 61 | ) -> Vec { 62 | let mut reports = Vec::new(); 63 | let atomic_apis = self.collect_atomics(callgraph); 64 | if atomic_apis.is_empty() { 65 | return Vec::new(); 66 | } 67 | let mut atomic_reads = FxHashMap::default(); 68 | let mut atomic_writes = FxHashMap::default(); 69 | let mut atomic_read_writes = FxHashMap::default(); 70 | for (instance_id, atomic_api) in atomic_apis { 71 | let callers = callgraph.callers(instance_id); 72 | match atomic_api { 73 | AtomicApi::Read => atomic_reads.insert(instance_id, callers), 74 | AtomicApi::Write => atomic_writes.insert(instance_id, callers), 75 | AtomicApi::ReadWrite => atomic_read_writes.insert(instance_id, callers), 76 | }; 77 | } 78 | for (atomic_read, read_callers) in atomic_reads { 79 | for (atomic_write, write_callers) in atomic_writes.iter() { 80 | // Only track direct callers of atomic APIs. 81 | // Find the caller that calls both Atomic::load and Atomic::store 82 | let common_callers = read_callers 83 | .iter() 84 | .filter(|caller| write_callers.contains(caller)) 85 | .collect::>(); 86 | // Find the callsites of Atomic APIs 87 | for caller in common_callers { 88 | let read_write_callsites = atomic_read_writes 89 | .iter() 90 | .filter_map(|(atomic_read_write, read_write_callers)| { 91 | if read_write_callers.contains(caller) { 92 | callsite_locations(callgraph, *caller, *atomic_read_write) 93 | } else { 94 | None 95 | } 96 | }) 97 | .flatten() 98 | .collect::>(); 99 | let body = self 100 | .tcx 101 | .instance_mir(callgraph.index_to_instance(*caller).unwrap().instance().def); 102 | let mut uses_cache = FxHashMap::default(); 103 | let control_deps = controldep::control_deps(&body.basic_blocks); 104 | let data_deps = datadep::data_deps(body); 105 | let read_callsites = 106 | callsite_locations(callgraph, *caller, atomic_read).unwrap(); 107 | let write_callsites = 108 | callsite_locations(callgraph, *caller, *atomic_write).unwrap(); 109 | for read_callsite in read_callsites { 110 | for write_callsite in &write_callsites { 111 | let dep_kind = match atomic_uses_influences( 112 | read_callsite, 113 | *write_callsite, 114 | (*caller, body), 115 | alias_analysis, 116 | &mut uses_cache, 117 | &data_deps, 118 | &control_deps, 119 | ) { 120 | Some(dep_kind) => dep_kind, 121 | None => { 122 | continue; 123 | } 124 | }; 125 | if !read_write_callsites.iter().any(|read_write_callsite| { 126 | matches!( 127 | atomic_uses_influences( 128 | *read_write_callsite, 129 | *write_callsite, 130 | (*caller, body), 131 | alias_analysis, 132 | &mut uses_cache, 133 | &data_deps, 134 | &control_deps, 135 | ), 136 | Some(DependenceKind::Control) | Some(DependenceKind::Both) 137 | ) 138 | }) { 139 | let fn_name = self.tcx.def_path_str( 140 | callgraph 141 | .index_to_instance(*caller) 142 | .unwrap() 143 | .instance() 144 | .def_id(), 145 | ); 146 | let atomic_reader = 147 | format!("{:?}", body.source_info(read_callsite).span); 148 | let atomic_writer = 149 | format!("{:?}", body.source_info(*write_callsite).span); 150 | let dep_kind = format!("{dep_kind:?}"); 151 | let diagnosis = AtomicityViolationDiagnosis { 152 | fn_name, 153 | atomic_reader, 154 | atomic_writer, 155 | dep_kind, 156 | }; 157 | let report_content = ReportContent::new( 158 | "AtomicityViolation".to_owned(), 159 | "Possibly".to_owned(), 160 | diagnosis, 161 | "atomic::store is data/control dependent on atomic::load" 162 | .to_owned(), 163 | ); 164 | reports.push(Report::AtomicityViolation(report_content)); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | reports 172 | } 173 | } 174 | 175 | /// Get the first arg and the destination of an Atomic API. 176 | /// e.g., 177 | /// ```let value = atomic::load(atomic, ordering);``` 178 | /// Returns Some((atomic, value)); 179 | fn first_arg_and_dest<'tcx>( 180 | location: Location, 181 | body: &Body<'tcx>, 182 | ) -> Option<(Place<'tcx>, Place<'tcx>)> { 183 | if let TerminatorKind::Call { 184 | func: _func, 185 | args, 186 | destination, 187 | .. 188 | } = &body[location.block].terminator().kind 189 | { 190 | args.first() 191 | .and_then(|arg| arg.node.place()) 192 | .map(|place| (place, *destination)) 193 | } else { 194 | None 195 | } 196 | } 197 | 198 | fn first_two_args<'tcx>( 199 | location: Location, 200 | body: &Body<'tcx>, 201 | ) -> Option<(Place<'tcx>, Option>)> { 202 | if let TerminatorKind::Call { 203 | func: _func, args, .. 204 | } = &body[location.block].terminator().kind 205 | { 206 | let place0 = args.first()?.node.place()?; 207 | let place1 = args.get(1).and_then(|arg1| arg1.node.place()); 208 | Some((place0, place1)) 209 | } else { 210 | None 211 | } 212 | } 213 | 214 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 215 | enum DependenceKind { 216 | Control, 217 | Data, 218 | Both, 219 | } 220 | 221 | /// Returns the uses locations of `influencer` that may data or control influence `influencee` 222 | /// For 223 | fn atomic_uses_influences<'tcx>( 224 | influencer: Location, 225 | influencee: Location, 226 | instance_body: (InstanceId, &Body<'tcx>), 227 | alias_analysis: &mut AliasAnalysis<'_, 'tcx>, 228 | uses_cache: &mut FxHashMap>, 229 | data_deps: &datadep::DataDeps, 230 | control_deps: &controldep::ControlDeps, 231 | ) -> Option { 232 | let (instance_id, body) = instance_body; 233 | let (influencer_arg, influencer_dest) = first_arg_and_dest(influencer, body)?; 234 | let (influencee_arg, value_arg) = first_two_args(influencee, body)?; 235 | // atomic in influencer may alias atomic in influencee 236 | let influencer_id = AliasId { 237 | instance_id, 238 | local: influencer_arg.local, 239 | }; 240 | let influencee_id = AliasId { 241 | instance_id, 242 | local: influencee_arg.local, 243 | }; 244 | let alias_kind = alias_analysis.alias(influencer_id, influencee_id); 245 | if alias_kind < ApproximateAliasKind::Possibly { 246 | return None; 247 | } 248 | let local = influencer_dest.local; 249 | // locals that is data dep on influencer 250 | let deps = datadep::all_data_dep_on(local, data_deps); 251 | // data dependent 252 | let is_data_dep = match value_arg { 253 | Some(value_arg) => deps.contains(&value_arg.local), 254 | None => false, 255 | }; 256 | // uses of influencer and its deps influence influencee 257 | let mut use_locs_influenced = Vec::new(); 258 | for dep in deps.into_iter().chain(std::iter::once(local)) { 259 | let use_locs = uses_cache 260 | .entry(dep) 261 | .or_insert_with(|| defuse::find_uses(body, dep)); 262 | use_locs_influenced.extend( 263 | use_locs 264 | .iter() 265 | .cloned() 266 | .filter(|use_loc| controldep::influences(*use_loc, influencee, control_deps)), 267 | ); 268 | } 269 | if use_locs_influenced.is_empty() { 270 | if is_data_dep { 271 | Some(DependenceKind::Data) 272 | } else { 273 | None 274 | } 275 | } else if is_data_dep { 276 | Some(DependenceKind::Both) 277 | } else { 278 | Some(DependenceKind::Control) 279 | } 280 | } 281 | 282 | /// CallSite Locations from source to target 283 | fn callsite_locations( 284 | callgraph: &CallGraph<'_>, 285 | source: InstanceId, 286 | target: InstanceId, 287 | ) -> Option> { 288 | Some( 289 | callgraph 290 | .callsites(source, target)? 291 | .into_iter() 292 | .filter_map(|callsite| callsite.location()) 293 | .collect(), 294 | ) 295 | } 296 | -------------------------------------------------------------------------------- /src/detector/atomic/report.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[derive(Debug, Serialize)] 4 | pub struct AtomicityViolationDiagnosis { 5 | pub fn_name: String, 6 | pub atomic_reader: String, 7 | pub atomic_writer: String, 8 | pub dep_kind: String, 9 | } 10 | -------------------------------------------------------------------------------- /src/detector/lock/report.rs: -------------------------------------------------------------------------------- 1 | //! Reports for different kinds of bugs. 2 | //! ReportContent includes bug kind, possibility, diagnosis, and explanation. 3 | //! The diagnosis for different kinds of bugs may be different. 4 | //! e.g., doublelock diagnosis contains one deadlock diagnosis, 5 | //!while conflictlock diagnosis contanis a vector of deadlock diagnosis. 6 | //! Deadlock diagnosis consists of the first & second locks' type and span (a.k.a. src code location), 7 | //! and **all** possible callchains from first to second lock. 8 | use serde::Serialize; 9 | 10 | #[derive(Debug, Serialize)] 11 | pub struct DeadlockDiagnosis { 12 | pub first_lock_type: String, 13 | pub first_lock_span: String, 14 | pub second_lock_type: String, 15 | pub second_lock_span: String, 16 | pub callchains: Vec>>, 17 | } 18 | 19 | impl DeadlockDiagnosis { 20 | pub fn new( 21 | first_lock_type: String, 22 | first_lock_span: String, 23 | second_lock_type: String, 24 | second_lock_span: String, 25 | callchains: Vec>>, 26 | ) -> Self { 27 | Self { 28 | first_lock_type, 29 | first_lock_span, 30 | second_lock_type, 31 | second_lock_span, 32 | callchains, 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug, Serialize)] 38 | pub struct WaitNotifyLocks { 39 | pub wait_lock_type: String, 40 | pub wait_lock_span: String, 41 | pub notify_lock_type: String, 42 | pub notify_lock_span: String, 43 | } 44 | 45 | impl WaitNotifyLocks { 46 | pub fn new( 47 | wait_lock_type: String, 48 | wait_lock_span: String, 49 | notify_lock_type: String, 50 | notify_lock_span: String, 51 | ) -> Self { 52 | Self { 53 | wait_lock_type, 54 | wait_lock_span, 55 | notify_lock_type, 56 | notify_lock_span, 57 | } 58 | } 59 | } 60 | 61 | #[derive(Debug, Serialize)] 62 | pub struct CondvarDeadlockDiagnosis { 63 | pub condvar_wait_type: String, 64 | pub condvar_wait_callsite_span: String, 65 | pub condvar_notify_type: String, 66 | pub condvar_notify_callsite_span: String, 67 | pub deadlocks: Vec, 68 | } 69 | 70 | impl CondvarDeadlockDiagnosis { 71 | pub fn new( 72 | condvar_wait_type: String, 73 | condvar_wait_callsite_span: String, 74 | condvar_notify_type: String, 75 | condvar_notify_callsite_span: String, 76 | deadlocks: Vec, 77 | ) -> Self { 78 | Self { 79 | condvar_wait_type, 80 | condvar_wait_callsite_span, 81 | condvar_notify_type, 82 | condvar_notify_callsite_span, 83 | deadlocks, 84 | } 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use crate::detector::report::ReportContent; 92 | 93 | #[test] 94 | fn test_deadlock_diagnosis() { 95 | let d = DeadlockDiagnosis::new( 96 | "ParkingLotRead(loader::ModuleCache)".to_owned(), 97 | "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)".to_owned(), 98 | "ParkingLotRead(loader::ModuleCache)".to_owned(), 99 | "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)".to_owned(), 100 | vec![vec![vec![ 101 | "language/move-vm/runtime/src/loader.rs:518:13: 518:55 (#0)".to_owned(), 102 | ]]], 103 | ); 104 | assert_eq!( 105 | format!("{:?}", d), 106 | r#"DeadlockDiagnosis { first_lock_type: "ParkingLotRead(loader::ModuleCache)", first_lock_span: "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)", second_lock_type: "ParkingLotRead(loader::ModuleCache)", second_lock_span: "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)", callchains: [[["language/move-vm/runtime/src/loader.rs:518:13: 518:55 (#0)"]]] }"# 107 | ) 108 | } 109 | 110 | #[test] 111 | fn test_report_content() { 112 | let d = DeadlockDiagnosis::new( 113 | "ParkingLotRead(loader::ModuleCache)".to_owned(), 114 | "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)".to_owned(), 115 | "ParkingLotRead(loader::ModuleCache)".to_owned(), 116 | "language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)".to_owned(), 117 | vec![vec![vec![ 118 | "language/move-vm/runtime/src/loader.rs:518:13: 518:55 (#0)".to_owned(), 119 | ]]], 120 | ); 121 | let report_content = ReportContent::new( 122 | "DoubleLock".to_owned(), 123 | "Possibly".to_owned(), 124 | format!("{:?}", d), 125 | "The first lock is not released when acquiring the second lock".to_owned(), 126 | ); 127 | assert_eq!( 128 | format!("{:?}", report_content), 129 | r#"ReportContent { bug_kind: "DoubleLock", possibility: "Possibly", diagnosis: "DeadlockDiagnosis { first_lock_type: \"ParkingLotRead(loader::ModuleCache)\", first_lock_span: \"language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)\", second_lock_type: \"ParkingLotRead(loader::ModuleCache)\", second_lock_span: \"language/move-vm/runtime/src/loader.rs:510:13: 510:18 (#0)\", callchains: [[[\"language/move-vm/runtime/src/loader.rs:518:13: 518:55 (#0)\"]]] }", explanation: "The first lock is not released when acquiring the second lock" }"# 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/detector/memory/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_data_structures; 2 | extern crate rustc_middle; 3 | 4 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; 5 | use rustc_middle::mir::visit::Visitor; 6 | use rustc_middle::mir::{Body, Location, Place, Terminator, TerminatorKind}; 7 | use rustc_middle::ty::TyCtxt; 8 | 9 | use petgraph::visit::IntoNodeReferences; 10 | 11 | use crate::analysis::callgraph::{CallGraph, InstanceId}; 12 | 13 | mod invalid_free; 14 | mod use_after_free; 15 | 16 | pub use invalid_free::InvalidFreeDetector; 17 | pub use use_after_free::UseAfterFreeDetector; 18 | 19 | /// Find dest and the first arg of a Call 20 | fn dest_args0<'tcx>( 21 | body: &Body<'tcx>, 22 | loc: Location, 23 | ) -> Option<(Place<'tcx>, Option>)> { 24 | if let TerminatorKind::Call { 25 | func: _func, 26 | args, 27 | destination, 28 | .. 29 | } = &body[loc.block].terminator().kind 30 | { 31 | let args0 = args.first().and_then(|op| op.node.place()); 32 | return Some((*destination, args0)); 33 | } 34 | None 35 | } 36 | /// std::mem::drop(place); 37 | fn collect_manual_drop<'tcx>( 38 | callgraph: &CallGraph<'tcx>, 39 | tcx: TyCtxt<'tcx>, 40 | ) -> FxHashMap)>> { 41 | let mut manual_drops: FxHashMap> = FxHashMap::default(); 42 | for (callee_id, node) in callgraph.graph.node_references() { 43 | let instance = node.instance(); 44 | let path = tcx.def_path_str_with_args(instance.def_id(), instance.args); 45 | if !path.starts_with("std::mem::drop") && !path.starts_with("core::mem::drop") { 46 | continue; 47 | } 48 | let caller_ids = callgraph.callers(callee_id); 49 | for caller_id in caller_ids { 50 | let callsites = match callgraph.callsites(caller_id, callee_id) { 51 | Some(callsites) => callsites, 52 | None => continue, 53 | }; 54 | let caller_node = match callgraph.index_to_instance(caller_id) { 55 | Some(caller_node) => caller_node, 56 | None => continue, 57 | }; 58 | let caller = caller_node.instance(); 59 | let body = tcx.instance_mir(caller.def); 60 | for loc in callsites { 61 | let loc = match loc.location() { 62 | Some(loc) => loc, 63 | None => continue, 64 | }; 65 | let places0 = match dest_args0(body, loc) { 66 | Some((_, Some(places0))) => places0, 67 | _ => continue, 68 | }; 69 | manual_drops 70 | .entry(caller_id) 71 | .or_default() 72 | .push((loc, places0)); 73 | } 74 | } 75 | } 76 | manual_drops 77 | } 78 | 79 | /// Collect TerminatorKind::Drop 80 | struct AutoDropCollector<'tcx> { 81 | drop_locations: Vec<(Location, Place<'tcx>)>, 82 | } 83 | 84 | impl<'tcx> AutoDropCollector<'tcx> { 85 | fn new() -> Self { 86 | Self { 87 | drop_locations: Vec::new(), 88 | } 89 | } 90 | 91 | fn finish(self) -> Vec<(Location, Place<'tcx>)> { 92 | self.drop_locations 93 | } 94 | } 95 | 96 | impl<'tcx> Visitor<'tcx> for AutoDropCollector<'tcx> { 97 | fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { 98 | if let TerminatorKind::Drop { place, .. } = &terminator.kind { 99 | self.drop_locations.push((location, *place)); 100 | } 101 | } 102 | } 103 | 104 | fn is_reachable(from: Location, to: Location, body: &Body<'_>) -> bool { 105 | if from.block == to.block { 106 | return from.statement_index <= to.statement_index; 107 | } 108 | let from_block = from.block; 109 | let to_block = to.block; 110 | let mut worklist = Vec::new(); 111 | let mut visited = FxHashSet::default(); 112 | worklist.push(from_block); 113 | visited.insert(from_block); 114 | while let Some(curr) = worklist.pop() { 115 | if curr == to_block { 116 | return true; 117 | } 118 | for succ in body.basic_blocks[curr].terminator().successors() { 119 | if !visited.insert(succ) { 120 | continue; 121 | } 122 | worklist.push(succ); 123 | } 124 | } 125 | false 126 | } 127 | -------------------------------------------------------------------------------- /src/detector/memory/use_after_free.rs: -------------------------------------------------------------------------------- 1 | /// find raw ptrs and drop(place) 2 | /// raw ptr's provenance place 3 | /// raw ptr assigned to other place 4 | /// drop(place) 5 | /// after drop, raw ptr or its assignee is used 6 | extern crate rustc_data_structures; 7 | extern crate rustc_index; 8 | extern crate rustc_middle; 9 | 10 | use rustc_data_structures::fx::FxHashSet; 11 | use rustc_index::Idx; 12 | use rustc_middle::mir::visit::Visitor; 13 | use rustc_middle::mir::{Body, HasLocalDecls, Local, Location, Place}; 14 | use rustc_middle::ty::{Instance, TyCtxt}; 15 | 16 | use petgraph::visit::IntoNodeReferences; 17 | 18 | use super::{collect_manual_drop, is_reachable, AutoDropCollector}; 19 | use crate::analysis::callgraph::CallGraphNode; 20 | use crate::analysis::defuse::find_uses; 21 | use crate::analysis::pointsto::{ConstraintNode, PointsToMap}; 22 | use crate::analysis::{callgraph::CallGraph, pointsto::AliasAnalysis}; 23 | use crate::detector::report::{Report, ReportContent}; 24 | 25 | pub struct UseAfterFreeDetector<'tcx> { 26 | tcx: TyCtxt<'tcx>, 27 | } 28 | 29 | impl<'tcx> UseAfterFreeDetector<'tcx> { 30 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 31 | Self { tcx } 32 | } 33 | 34 | pub fn detect( 35 | &self, 36 | callgraph: &CallGraph<'tcx>, 37 | alias_analysis: &mut AliasAnalysis<'_, 'tcx>, 38 | ) -> Vec { 39 | let mut reports = Vec::new(); 40 | let manual_drops = collect_manual_drop(callgraph, self.tcx); 41 | for (instance_id, node) in callgraph.graph.node_references() { 42 | let instance = match node { 43 | CallGraphNode::WithBody(instance) => instance, 44 | CallGraphNode::WithoutBody(_) => continue, 45 | }; 46 | let local_manual_drops = manual_drops 47 | .get(&instance_id) 48 | .map(Vec::as_slice) 49 | .unwrap_or(&[]); 50 | reports.extend(self.detect_instance(instance, alias_analysis, local_manual_drops)); 51 | } 52 | reports 53 | } 54 | 55 | fn detect_instance( 56 | &self, 57 | instance: &Instance<'tcx>, 58 | alias_analysis: &mut AliasAnalysis<'_, 'tcx>, 59 | manual_drops: &[(Location, Place<'tcx>)], 60 | ) -> Vec { 61 | let mut diagnosis_set = FxHashSet::default(); 62 | let body = self.tcx.instance_mir(instance.def); 63 | let raw_ptrs = self.collect_raw_ptrs(body); 64 | if raw_ptrs.is_empty() { 65 | return vec![]; 66 | } 67 | let drops = self.collect_drops(body, manual_drops); 68 | let pts = alias_analysis.get_or_insert_pts(instance.def_id(), body); 69 | diagnosis_set.extend(detect_escape_to_global(pts, &drops, body, self.tcx)); 70 | diagnosis_set.extend(detect_escape_to_return_or_param( 71 | pts, &drops, body, self.tcx, 72 | )); 73 | diagnosis_set.extend(detect_use_after_drop(&raw_ptrs, pts, &drops, body)); 74 | diagnosis_set.into_iter().map(|diagnosis| Report::UseAfterFree(ReportContent::new("UseAfterFree".to_owned(), "Possibly".to_owned(), diagnosis, "Raw ptr is used or escapes the current function after the pointed value is dropped".to_owned()))).collect::>() 75 | } 76 | 77 | fn collect_raw_ptrs(&self, body: &Body<'tcx>) -> FxHashSet { 78 | body.local_decls 79 | .iter_enumerated() 80 | .filter_map(|(local, local_decl)| { 81 | if local_decl.ty.is_unsafe_ptr() { 82 | Some(local) 83 | } else { 84 | None 85 | } 86 | }) 87 | .collect() 88 | } 89 | 90 | fn collect_drops( 91 | &self, 92 | body: &Body<'tcx>, 93 | manual_drops: &[(Location, Place<'tcx>)], 94 | ) -> Vec<(Location, Place<'tcx>)> { 95 | let mut collector = AutoDropCollector::new(); 96 | collector.visit_body(body); 97 | let mut drops = collector.finish(); 98 | drops.extend(manual_drops.iter().cloned()); 99 | drops 100 | } 101 | } 102 | 103 | /// Collect raw ptrs escaping to Globals. 104 | /// Alloc(ptr) pointed to by ConstantDeref implies Place(ptr) escapes to Global. 105 | /// 1. forall c is ConstantDeref, collect pts(c) into S 106 | /// 2. find Alloc(ptr) in S and map it to Place(ptr) 107 | /// Returns (Place(ptr), c) 108 | fn collect_raw_ptrs_escape_to_global<'tcx>( 109 | pts: &PointsToMap<'tcx>, 110 | body: &Body<'tcx>, 111 | tcx: TyCtxt<'tcx>, 112 | ) -> FxHashSet<(ConstraintNode<'tcx>, ConstraintNode<'tcx>)> { 113 | let local_end = Local::new(body.local_decls().len()); 114 | pts.iter() 115 | .filter_map(|(ptr, ptes)| { 116 | if let ConstraintNode::ConstantDeref(_) = ptr { 117 | Some((ptr, ptes)) 118 | } else { 119 | None 120 | } 121 | }) 122 | .flat_map(|(ptr, ptes)| ptes.iter().map(|pte| (pte, ptr.clone()))) 123 | .filter_map(|(pte, ptr)| match pte { 124 | ConstraintNode::Alloc(place) 125 | if place.local < local_end && place.ty(body, tcx).ty.is_unsafe_ptr() => 126 | { 127 | Some((ConstraintNode::Place(*place), ptr)) 128 | } 129 | _ => None, 130 | }) 131 | .collect::>() 132 | } 133 | 134 | /// Raw ptr escapes to Global and points to a dropped place 135 | fn detect_escape_to_global<'tcx>( 136 | pts: &PointsToMap<'tcx>, 137 | drops: &[(Location, Place<'tcx>)], 138 | body: &Body<'tcx>, 139 | tcx: TyCtxt<'tcx>, 140 | ) -> FxHashSet { 141 | let mut diagnosis_set = FxHashSet::default(); 142 | let escapes = collect_raw_ptrs_escape_to_global(pts, body, tcx); 143 | for (escape, constant) in escapes { 144 | let ptes = match pts.get(&escape) { 145 | Some(ptes) => ptes, 146 | None => continue, 147 | }; 148 | for pte in ptes { 149 | let place = match pte { 150 | ConstraintNode::Place(place) => place, 151 | _ => continue, 152 | }; 153 | for (location, drop) in drops.iter() { 154 | if drop.as_ref() == *place { 155 | let escape_span = match escape { 156 | ConstraintNode::Place(ptr) => body.local_decls[ptr.local].source_info.span, 157 | _ => continue, 158 | }; 159 | let diagnosis = format!("Escape to Global: Raw ptr {:?} at {:?} escapes to {:?} but pointee is dropped at {:?}", escape, escape_span, constant, body.source_info(*location).span); 160 | diagnosis_set.insert(diagnosis); 161 | } 162 | } 163 | } 164 | } 165 | diagnosis_set 166 | } 167 | 168 | /// Raw ptr escapes to return/params and points to a dropped place 169 | /// Raw ptr points to Alloc(return/params) implies ptr escapes to return/params 170 | /// 1. Find places X alias with param/return: Place(X) -> Alloc(Param/Return) 171 | /// 2. Find raw ptr Y alias with X: Place(X) -> Alloc(Y) 172 | /// 3. Find pts(Y) that is dropped 173 | fn detect_escape_to_return_or_param<'tcx>( 174 | pts: &PointsToMap<'tcx>, 175 | drops: &[(Location, Place<'tcx>)], 176 | body: &Body<'tcx>, 177 | tcx: TyCtxt<'tcx>, 178 | ) -> FxHashSet { 179 | let mut diagnosis_set = FxHashSet::default(); 180 | let first_non_param_local = Local::new(body.arg_count); 181 | let local_end = Local::new(body.local_decls().len()); 182 | for (ptr, ptes) in pts { 183 | // Place X 184 | let ptr = match ptr { 185 | ConstraintNode::Place(ptr) => ptr, 186 | _ => continue, 187 | }; 188 | // Alias with param/return and raw ptr 189 | let mut alias_with_params = Vec::new(); 190 | let mut alias_with_raw_ptrs = Vec::new(); 191 | for pte in ptes { 192 | match pte { 193 | ConstraintNode::Alloc(pte) => { 194 | if pte.local < first_non_param_local { 195 | alias_with_params.push(pte) 196 | } else if pte.local < local_end 197 | && pte.projection.is_empty() 198 | && pte.ty(body, tcx).ty.is_unsafe_ptr() 199 | { 200 | alias_with_raw_ptrs.push(pte) 201 | } 202 | } 203 | _ => continue, 204 | }; 205 | } 206 | if alias_with_params.is_empty() { 207 | continue; 208 | } 209 | // Raw ptr points to a dropped place 210 | for raw_ptr in alias_with_raw_ptrs { 211 | let ptes = match pts.get(&ConstraintNode::Place(*raw_ptr)) { 212 | Some(ptes) => ptes, 213 | None => continue, 214 | }; 215 | for pte in ptes { 216 | let pte_place = match pte { 217 | ConstraintNode::Place(place) => place, 218 | _ => continue, 219 | }; 220 | for (location, drop_place) in drops { 221 | if body.basic_blocks[location.block].is_cleanup { 222 | continue; 223 | } 224 | if drop_place.as_ref() == *pte_place { 225 | let ptr_span = body.local_decls[ptr.local].source_info.span; 226 | let diagnosis = format!("Escape to Param/Return: Raw ptr {:?} at {:?} escapes to {:?} but pointee is dropped at {:?}", ptr, ptr_span, alias_with_params, body.source_info(*location).span); 227 | diagnosis_set.insert(diagnosis); 228 | } 229 | } 230 | } 231 | } 232 | } 233 | diagnosis_set 234 | } 235 | 236 | // drop(place): raw_ptr -> place 237 | // drop_loc reaches use(raw_ptr) 238 | fn detect_use_after_drop<'tcx>( 239 | raw_ptrs: &FxHashSet, 240 | pts: &PointsToMap<'tcx>, 241 | drops: &[(Location, Place<'tcx>)], 242 | body: &Body<'tcx>, 243 | ) -> FxHashSet { 244 | let mut diagnosis_set = FxHashSet::default(); 245 | for raw_ptr in raw_ptrs { 246 | let raw_ptr_node = ConstraintNode::Place(Place::from(*raw_ptr).as_ref()); 247 | let ptes = match pts.get(&raw_ptr_node) { 248 | Some(ptes) => ptes, 249 | None => continue, 250 | }; 251 | let raw_ptr_use_locations = find_uses(body, *raw_ptr); 252 | for pte in ptes { 253 | let pte = match pte { 254 | ConstraintNode::Place(pte) => pte, 255 | _ => continue, 256 | }; 257 | for (drop_loc, drop_place) in drops { 258 | if drop_place.as_ref() != *pte { 259 | continue; 260 | } 261 | for use_loc in &raw_ptr_use_locations { 262 | if is_reachable(*drop_loc, *use_loc, body) { 263 | let diagnosis = format!( 264 | "Raw ptr is used at {:?} after dropped at {:?}", 265 | body.source_info(*use_loc).span, 266 | body.source_info(*drop_loc).span 267 | ); 268 | diagnosis_set.insert(diagnosis); 269 | } 270 | } 271 | } 272 | } 273 | } 274 | diagnosis_set 275 | } 276 | -------------------------------------------------------------------------------- /src/detector/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod atomic; 2 | pub mod lock; 3 | pub mod memory; 4 | pub mod panic; 5 | pub mod report; 6 | -------------------------------------------------------------------------------- /src/detector/panic/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_hir; 2 | extern crate rustc_span; 3 | 4 | use once_cell::sync::Lazy; 5 | use regex::Regex; 6 | use rustc_hir::def_id::{DefId, LOCAL_CRATE}; 7 | use rustc_middle::mir::visit::Visitor; 8 | use rustc_middle::mir::{Body, Location, Terminator, TerminatorKind, OUTERMOST_SOURCE_SCOPE}; 9 | use rustc_middle::ty::EarlyBinder; 10 | use rustc_middle::ty::{self, TyCtxt, TyKind}; 11 | use rustc_middle::ty::{Instance, InstanceKind}; 12 | use rustc_span::Span; 13 | use std::collections::HashMap; 14 | 15 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 16 | pub enum PanicAPI { 17 | ResultUnwrap, 18 | ResultExpect, 19 | OptionUnwrap, 20 | OptionExpect, 21 | PanicFmt, 22 | AssertFailed, 23 | Panic, 24 | } 25 | 26 | static PANIC_API_REGEX: Lazy> = Lazy::new(|| { 27 | let mut m = HashMap::new(); 28 | m.insert( 29 | PanicAPI::ResultUnwrap, 30 | Regex::new(r"Result::<.+>::unwrap").unwrap(), 31 | ); 32 | m.insert( 33 | PanicAPI::ResultExpect, 34 | Regex::new(r"Result::<.+>::expect").unwrap(), 35 | ); 36 | m.insert( 37 | PanicAPI::OptionUnwrap, 38 | Regex::new(r"Option::<.+>::unwrap").unwrap(), 39 | ); 40 | m.insert( 41 | PanicAPI::OptionExpect, 42 | Regex::new(r"Option::<.+>::expect").unwrap(), 43 | ); 44 | m.insert(PanicAPI::PanicFmt, Regex::new(r"rt::panic_fmt").unwrap()); 45 | m.insert( 46 | PanicAPI::AssertFailed, 47 | Regex::new(r"panicking::assert_failed").unwrap(), 48 | ); 49 | m.insert(PanicAPI::Panic, Regex::new(r"panicking::panic").unwrap()); 50 | m 51 | }); 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | #[test] 57 | fn test_panic_api_regex() { 58 | assert!(PANIC_API_REGEX[&PanicAPI::ResultUnwrap].is_match("Result::::unwrap")); 59 | assert!(PANIC_API_REGEX[&PanicAPI::ResultExpect].is_match("Result::::expect")); 60 | assert!(PANIC_API_REGEX[&PanicAPI::OptionUnwrap].is_match("Option::::unwrap")); 61 | assert!(PANIC_API_REGEX[&PanicAPI::OptionExpect].is_match("Option::::expect")); 62 | assert!(PANIC_API_REGEX[&PanicAPI::PanicFmt].is_match("rt::panic_fmt")); 63 | assert!(PANIC_API_REGEX[&PanicAPI::AssertFailed].is_match("core::panicking::assert_failed")); 64 | assert!(PANIC_API_REGEX[&PanicAPI::Panic].is_match("core::panicking::panic")); 65 | assert!(!PANIC_API_REGEX[&PanicAPI::Panic].is_match("no_panic")); 66 | } 67 | } 68 | 69 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 70 | pub enum PanicInstance<'tcx> { 71 | ResultUnwrap(Instance<'tcx>), 72 | ResultExpect(Instance<'tcx>), 73 | OptionUnwrap(Instance<'tcx>), 74 | OptionExpect(Instance<'tcx>), 75 | PanicFmt(Instance<'tcx>), 76 | AssertFailed(Instance<'tcx>), 77 | Panic(Instance<'tcx>), 78 | } 79 | 80 | impl<'tcx> PanicInstance<'tcx> { 81 | fn new(instance: Instance<'tcx>, tcx: TyCtxt<'tcx>) -> Option { 82 | let def_path_str = tcx.def_path_str_with_args(instance.def_id(), instance.args); 83 | if PANIC_API_REGEX[&PanicAPI::ResultUnwrap].is_match(&def_path_str) { 84 | Some(PanicInstance::ResultUnwrap(instance)) 85 | } else if PANIC_API_REGEX[&PanicAPI::ResultExpect].is_match(&def_path_str) { 86 | Some(PanicInstance::ResultExpect(instance)) 87 | } else if PANIC_API_REGEX[&PanicAPI::OptionUnwrap].is_match(&def_path_str) { 88 | Some(PanicInstance::OptionUnwrap(instance)) 89 | } else if PANIC_API_REGEX[&PanicAPI::OptionExpect].is_match(&def_path_str) { 90 | Some(PanicInstance::OptionExpect(instance)) 91 | } else if PANIC_API_REGEX[&PanicAPI::PanicFmt].is_match(&def_path_str) { 92 | Some(PanicInstance::PanicFmt(instance)) 93 | } else if PANIC_API_REGEX[&PanicAPI::AssertFailed].is_match(&def_path_str) { 94 | Some(PanicInstance::AssertFailed(instance)) 95 | } else if PANIC_API_REGEX[&PanicAPI::Panic].is_match(&def_path_str) { 96 | Some(PanicInstance::Panic(instance)) 97 | } else { 98 | None 99 | } 100 | } 101 | 102 | fn to_panic_api(&self) -> PanicAPI { 103 | match self { 104 | PanicInstance::ResultUnwrap(_) => PanicAPI::ResultUnwrap, 105 | PanicInstance::ResultExpect(_) => PanicAPI::ResultExpect, 106 | PanicInstance::OptionUnwrap(_) => PanicAPI::OptionUnwrap, 107 | PanicInstance::OptionExpect(_) => PanicAPI::OptionExpect, 108 | PanicInstance::PanicFmt(_) => PanicAPI::PanicFmt, 109 | PanicInstance::AssertFailed(_) => PanicAPI::AssertFailed, 110 | PanicInstance::Panic(_) => PanicAPI::Panic, 111 | } 112 | } 113 | } 114 | 115 | pub struct PanicDetector<'tcx> { 116 | tcx: TyCtxt<'tcx>, 117 | result: HashMap<(DefId, Location), (Span, Span, PanicInstance<'tcx>)>, 118 | } 119 | 120 | impl<'tcx> PanicDetector<'tcx> { 121 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 122 | Self { 123 | tcx, 124 | result: Default::default(), 125 | } 126 | } 127 | pub fn detect(&mut self, instance: Instance<'tcx>) { 128 | if let Some(mut panic_finder) = PanicFinder::new(instance, self.tcx) { 129 | self.result.extend(panic_finder.detect()); 130 | } 131 | } 132 | pub fn result(&self) -> &HashMap<(DefId, Location), (Span, Span, PanicInstance<'tcx>)> { 133 | &self.result 134 | } 135 | pub fn statistics(&self) -> HashMap { 136 | let mut tally: HashMap = HashMap::new(); 137 | self.result.iter().for_each(|(_, (_, _, panic_instance))| { 138 | *tally.entry(panic_instance.to_panic_api()).or_default() += 1; 139 | }); 140 | tally 141 | } 142 | } 143 | 144 | struct PanicFinder<'tcx> { 145 | instance: Instance<'tcx>, 146 | body: &'tcx Body<'tcx>, 147 | tcx: TyCtxt<'tcx>, 148 | callsites: HashMap>, 149 | } 150 | 151 | impl<'tcx> PanicFinder<'tcx> { 152 | fn new(instance: Instance<'tcx>, tcx: TyCtxt<'tcx>) -> Option { 153 | if skip_detecting(&instance, tcx) { 154 | return None; 155 | } 156 | // Only detect instances in local crate. 157 | if instance.def_id().krate != LOCAL_CRATE { 158 | return None; 159 | } 160 | let body = tcx.instance_mir(instance.def); 161 | Some(Self { 162 | instance, 163 | body, 164 | tcx, 165 | callsites: Default::default(), 166 | }) 167 | } 168 | 169 | fn detect(&mut self) -> HashMap<(DefId, Location), (Span, Span, PanicInstance<'tcx>)> { 170 | self.visit_body(self.body); 171 | self.callsites 172 | .iter() 173 | .map(|(loc, instance)| { 174 | let def_id = self.instance.def_id(); 175 | let span = self.body.source_info(*loc).span; 176 | let outermost_span = self.body.source_scopes[OUTERMOST_SOURCE_SCOPE].span; 177 | ((def_id, *loc), (span, outermost_span, instance.clone())) 178 | }) 179 | .collect::<_>() 180 | } 181 | } 182 | 183 | impl<'tcx> Visitor<'tcx> for PanicFinder<'tcx> { 184 | fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { 185 | if let TerminatorKind::Call { ref func, .. } = terminator.kind { 186 | let func_ty = func.ty(self.body, self.tcx); 187 | let func_ty = self.instance.instantiate_mir_and_normalize_erasing_regions( 188 | self.tcx, 189 | ty::TypingEnv::fully_monomorphized(), 190 | EarlyBinder::bind(func_ty), 191 | ); 192 | if let TyKind::FnDef(def_id, subst_ref) = func_ty.kind() { 193 | if let Some(callee_instance) = Instance::try_resolve( 194 | self.tcx, 195 | ty::TypingEnv::fully_monomorphized(), 196 | *def_id, 197 | subst_ref, 198 | ) 199 | .ok() 200 | .flatten() 201 | { 202 | if let Some(panic_instance) = PanicInstance::new(callee_instance, self.tcx) { 203 | self.callsites.insert(location, panic_instance); 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | fn skip_detecting<'tcx>(instance: &Instance<'tcx>, tcx: TyCtxt<'tcx>) -> bool { 212 | if let InstanceKind::Item(_) = instance.def { 213 | !tcx.is_mir_available(instance.def_id()) 214 | } else { 215 | true 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/detector/report.rs: -------------------------------------------------------------------------------- 1 | //! Reports for different kinds of bugs. 2 | //! ReportContent includes bug kind, possibility, diagnosis, and explanation. 3 | //! The diagnosis for different kinds of bugs may be different. 4 | //! e.g., doublelock diagnosis contains one deadlock diagnosis, 5 | //!while conflictlock diagnosis contanis a vector of deadlock diagnosis. 6 | //! Deadlock diagnosis consists of the first & second locks' type and span (a.k.a. src code location), 7 | //! and **all** possible callchains from first to second lock. 8 | use serde::Serialize; 9 | 10 | use crate::detector::atomic::report::AtomicityViolationDiagnosis; 11 | use crate::detector::lock::report::{CondvarDeadlockDiagnosis, DeadlockDiagnosis}; 12 | 13 | #[allow(dead_code)] 14 | #[derive(Debug, Serialize)] 15 | pub struct ReportContent { 16 | pub bug_kind: String, 17 | pub possibility: String, 18 | pub diagnosis: D, 19 | pub explanation: String, 20 | } 21 | 22 | impl ReportContent { 23 | pub fn new(bug_kind: String, possibility: String, diagnosis: D, explanation: String) -> Self { 24 | Self { 25 | bug_kind, 26 | possibility, 27 | diagnosis, 28 | explanation, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Serialize)] 34 | pub enum Report { 35 | DoubleLock(ReportContent), 36 | ConflictLock(ReportContent>), 37 | CondvarDeadlock(ReportContent), 38 | AtomicityViolation(ReportContent), 39 | InvalidFree(ReportContent), 40 | UseAfterFree(ReportContent), 41 | } 42 | -------------------------------------------------------------------------------- /src/interest/concurrency/atomic.rs: -------------------------------------------------------------------------------- 1 | //! Find atomic functions and classify them into read, write, read-write. 2 | extern crate rustc_hash; 3 | extern crate rustc_hir; 4 | extern crate rustc_middle; 5 | 6 | use once_cell::sync::Lazy; 7 | use regex::Regex; 8 | 9 | use rustc_hash::FxHashMap; 10 | use rustc_hir::def_id::DefId; 11 | use rustc_middle::ty::{GenericArg, Instance, List, TyCtxt}; 12 | 13 | static ATOMIC_API_REGEX: Lazy> = Lazy::new(|| { 14 | macro_rules! atomic_api_prefix { 15 | () => { 16 | r"^(std|core)::sync::atomic[:a-zA-Z0-9]*::" 17 | }; 18 | } 19 | let mut m = FxHashMap::default(); 20 | m.insert( 21 | "AtomicRead", 22 | Regex::new(std::concat!(atomic_api_prefix!(), r"load")).unwrap(), 23 | ); 24 | m.insert( 25 | "AtomicWrite", 26 | Regex::new(std::concat!(atomic_api_prefix!(), r"store")).unwrap(), 27 | ); 28 | m.insert( 29 | "AtomicReadWrite", 30 | Regex::new(std::concat!( 31 | atomic_api_prefix!(), 32 | r"(compare|fetch)_[a-zA-Z0-9]*" 33 | )) 34 | .unwrap(), 35 | ); 36 | m 37 | }); 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::ATOMIC_API_REGEX; 42 | #[test] 43 | fn test_atomic_api_regex() { 44 | assert!(ATOMIC_API_REGEX["AtomicRead"].is_match("std::sync::atomic::AtomicUsize::load")); 45 | assert!(ATOMIC_API_REGEX["AtomicWrite"].is_match("std::sync::atomic::AtomicUsize::store")); 46 | assert!(ATOMIC_API_REGEX["AtomicReadWrite"] 47 | .is_match("std::sync::atomic::AtomicUsize::compare_and_swap")); 48 | } 49 | } 50 | 51 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 52 | pub enum AtomicApi { 53 | Read, 54 | Write, 55 | ReadWrite, 56 | } 57 | 58 | impl AtomicApi { 59 | pub fn from_instance<'tcx>(instance: Instance<'tcx>, tcx: TyCtxt<'tcx>) -> Option { 60 | let path = tcx.def_path_str_with_args(instance.def_id(), instance.args); 61 | if ATOMIC_API_REGEX["AtomicRead"].is_match(&path) { 62 | Some(AtomicApi::Read) 63 | } else if ATOMIC_API_REGEX["AtomicWrite"].is_match(&path) { 64 | Some(AtomicApi::Write) 65 | } else if ATOMIC_API_REGEX["AtomicReadWrite"].is_match(&path) { 66 | Some(AtomicApi::ReadWrite) 67 | } else { 68 | None 69 | } 70 | } 71 | } 72 | 73 | // AtomicPtr::store(&self, ptr: *mut T, order: Ordering) 74 | // Alias: self = ptr 75 | static ATOMIC_PTR_STORE: Lazy = 76 | Lazy::new(|| Regex::new(r"^(std|core)::sync::atomic::AtomicPtr::<.*>::store").unwrap()); 77 | 78 | pub fn is_atomic_ptr_store<'tcx>( 79 | def_id: DefId, 80 | substs: &'tcx List>, 81 | tcx: TyCtxt<'tcx>, 82 | ) -> bool { 83 | let path = tcx.def_path_str_with_args(def_id, substs); 84 | ATOMIC_PTR_STORE.is_match(&path) 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests2 { 89 | use super::*; 90 | 91 | #[test] 92 | fn test_atomic_ptr_store() { 93 | assert!(ATOMIC_PTR_STORE.is_match("std::sync::atomic::AtomicPtr::::store")); 94 | assert!(!ATOMIC_PTR_STORE.is_match("std::sync::atomic::AtomicUsize::store")); 95 | assert!(!ATOMIC_PTR_STORE.is_match("std::sync::atomic::AtomicPtr::::load")); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/interest/concurrency/chan.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurtonQin/lockbud/b2427ff793e89d7c0f0651b84274fd501887d925/src/interest/concurrency/chan.rs -------------------------------------------------------------------------------- /src/interest/concurrency/condvar.rs: -------------------------------------------------------------------------------- 1 | //! Denotes Condvar APIs in std and parking_lot. 2 | //! 3 | //! 1. std::Condvar::wait.*(&Condvar, MutexGuard,.*) -> MutexGuard 4 | //! 2. std::Condvar::notify.*(&Condvar) 5 | //! 3. parking_lot::Condvar::wait.*(&Condvar, &mut MutexGuard,.*) 6 | //! 4. parking_lot::Condvar::notify.*(&Condvar) 7 | use rustc_middle::ty::{Instance, TyCtxt}; 8 | 9 | #[derive(Clone, Copy, Debug)] 10 | pub enum CondvarApi { 11 | Std(StdCondvarApi), 12 | ParkingLot(ParkingLotCondvarApi), 13 | } 14 | 15 | impl CondvarApi { 16 | pub fn from_instance<'tcx>(instance: &Instance<'tcx>, tcx: TyCtxt<'tcx>) -> Option { 17 | let path = tcx.def_path_str_with_args(instance.def_id(), instance.args); 18 | let std_condvar = "std::sync::Condvar::"; 19 | let parking_lot_condvar = "parking_lot::Condvar::"; 20 | if path.starts_with(std_condvar) { 21 | let tail = &path.as_bytes()[std_condvar.len()..]; 22 | let std_condvar_api = if tail.starts_with("wait::".as_bytes()) { 23 | StdCondvarApi::Wait(StdWait::Wait) 24 | } else if tail.starts_with("wait_timeout::".as_bytes()) { 25 | StdCondvarApi::Wait(StdWait::WaitTimeout) 26 | } else if tail.starts_with("wait_timeout_ms::".as_bytes()) { 27 | StdCondvarApi::Wait(StdWait::WaitTimeoutMs) 28 | } else if tail.starts_with("wait_timeout_while::".as_bytes()) { 29 | StdCondvarApi::Wait(StdWait::WaitTimeoutWhile) 30 | } else if tail.starts_with("wait_while::".as_bytes()) { 31 | StdCondvarApi::Wait(StdWait::WaitWhile) 32 | } else if tail == "notify_all".as_bytes() { 33 | StdCondvarApi::Notify(StdNotify::NotifyAll) 34 | } else if tail == "notify_one".as_bytes() { 35 | StdCondvarApi::Notify(StdNotify::NotifyOne) 36 | } else { 37 | return None; 38 | }; 39 | Some(CondvarApi::Std(std_condvar_api)) 40 | } else if path.starts_with(parking_lot_condvar) { 41 | let tail = &path.as_bytes()[parking_lot_condvar.len()..]; 42 | let parking_lot_condvar_api = if tail.starts_with("wait::".as_bytes()) { 43 | ParkingLotCondvarApi::Wait(ParkingLotWait::Wait) 44 | } else if tail.starts_with("wait_for::".as_bytes()) { 45 | ParkingLotCondvarApi::Wait(ParkingLotWait::WaitFor) 46 | } else if tail.starts_with("wait_until::".as_bytes()) { 47 | ParkingLotCondvarApi::Wait(ParkingLotWait::WaitUntil) 48 | } else if tail.starts_with("wait_while::".as_bytes()) { 49 | ParkingLotCondvarApi::Wait(ParkingLotWait::WaitWhile) 50 | } else if tail.starts_with("wait_while_for::".as_bytes()) { 51 | ParkingLotCondvarApi::Wait(ParkingLotWait::WaitWhileFor) 52 | } else if tail.starts_with("wait_while_until::".as_bytes()) { 53 | ParkingLotCondvarApi::Wait(ParkingLotWait::WaitWhileUntil) 54 | } else if tail == "notify_all".as_bytes() { 55 | ParkingLotCondvarApi::Notify(ParkingLotNotify::NotifyAll) 56 | } else if tail == "notify_one".as_bytes() { 57 | ParkingLotCondvarApi::Notify(ParkingLotNotify::NotifyOne) 58 | } else { 59 | return None; 60 | }; 61 | Some(CondvarApi::ParkingLot(parking_lot_condvar_api)) 62 | } else { 63 | None 64 | } 65 | } 66 | } 67 | 68 | #[allow(dead_code)] 69 | #[derive(Clone, Copy, Debug)] 70 | pub enum StdCondvarApi { 71 | Wait(StdWait), 72 | Notify(StdNotify), 73 | } 74 | 75 | #[derive(Clone, Copy, Debug)] 76 | pub enum StdWait { 77 | Wait, 78 | WaitTimeout, 79 | WaitTimeoutMs, 80 | WaitTimeoutWhile, 81 | WaitWhile, 82 | } 83 | 84 | #[derive(Clone, Copy, Debug)] 85 | pub enum StdNotify { 86 | NotifyAll, 87 | NotifyOne, 88 | } 89 | 90 | #[allow(dead_code)] 91 | #[derive(Clone, Copy, Debug)] 92 | pub enum ParkingLotCondvarApi { 93 | Wait(ParkingLotWait), 94 | Notify(ParkingLotNotify), 95 | } 96 | 97 | #[derive(Clone, Copy, Debug)] 98 | pub enum ParkingLotWait { 99 | Wait, 100 | WaitFor, 101 | WaitUntil, 102 | WaitWhile, 103 | WaitWhileFor, 104 | WaitWhileUntil, 105 | } 106 | 107 | #[derive(Clone, Copy, Debug)] 108 | pub enum ParkingLotNotify { 109 | NotifyAll, 110 | NotifyOne, 111 | } 112 | -------------------------------------------------------------------------------- /src/interest/concurrency/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod atomic; 2 | pub mod condvar; 3 | pub mod lock; 4 | -------------------------------------------------------------------------------- /src/interest/memory/cast.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurtonQin/lockbud/b2427ff793e89d7c0f0651b84274fd501887d925/src/interest/memory/cast.rs -------------------------------------------------------------------------------- /src/interest/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ownership; 2 | pub mod uninit; 3 | -------------------------------------------------------------------------------- /src/interest/memory/ownership.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_hir; 2 | extern crate rustc_middle; 3 | 4 | use rustc_hir::def_id::DefId; 5 | use rustc_middle::ty::TyCtxt; 6 | 7 | use rustc_middle::ty::{GenericArg, List}; 8 | 9 | /// y = Arc::clone(x) 10 | pub fn is_arc_or_rc_clone<'tcx>( 11 | def_id: DefId, 12 | substs: &List>, 13 | tcx: TyCtxt<'tcx>, 14 | ) -> bool { 15 | let fn_name = tcx.def_path_str(def_id); 16 | if fn_name != "std::clone::Clone::clone" { 17 | return false; 18 | } 19 | if let &[arg] = substs.as_ref() { 20 | let arg_ty_name = format!("{:?}", arg); 21 | if is_arc(&arg_ty_name) || is_rc(&arg_ty_name) { 22 | return true; 23 | } 24 | } 25 | false 26 | } 27 | 28 | #[inline] 29 | pub fn is_arc(arg_ty_name: &str) -> bool { 30 | arg_ty_name.starts_with("std::sync::Arc<") 31 | } 32 | 33 | #[inline] 34 | pub fn is_rc(arg_ty_name: &str) -> bool { 35 | arg_ty_name.starts_with("std::rc::Rc<") 36 | } 37 | 38 | /// y = std::ptr::read::(x) 39 | #[inline] 40 | pub fn is_ptr_read(def_id: DefId, tcx: TyCtxt<'_>) -> bool { 41 | tcx.def_path_str(def_id).starts_with("std::ptr::read::<") 42 | } 43 | 44 | /// z = <_ as Index<_>>::index(x, y) 45 | #[inline] 46 | pub fn is_index(def_id: DefId, tcx: TyCtxt<'_>) -> bool { 47 | tcx.def_path_str(def_id).ends_with("::index") 48 | } 49 | -------------------------------------------------------------------------------- /src/interest/memory/rawptr.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurtonQin/lockbud/b2427ff793e89d7c0f0651b84274fd501887d925/src/interest/memory/rawptr.rs -------------------------------------------------------------------------------- /src/interest/memory/uninit.rs: -------------------------------------------------------------------------------- 1 | //! uninitialize: 2 | //! 1. _1 = uninitialized::>() -> bb1; 3 | //! 2. _1 = MaybeUninit::>::uninit() -> bb1; 4 | //! 3. _2 = MaybeUninit::>::assume_init(move _1) -> bb4; 5 | //! initialize: 6 | //! 1. _2 = MaybeUninit::>::write(move _3, move _4) -> bb3; 7 | //! 2. _2 = MaybeUninit::::as_mut_ptr(move _3) -> bb2; 8 | //! 3. _5 = &raw mut ((*_2).0: std::vec::Vec); 9 | //! 4. _4 = ptr::mut_ptr::>::write(move _5, move _6) -> bb4; 10 | extern crate rustc_data_structures; 11 | extern crate rustc_middle; 12 | 13 | use once_cell::sync::Lazy; 14 | use regex::Regex; 15 | 16 | use rustc_data_structures::fx::FxHashMap; 17 | use rustc_middle::ty::{Instance, TyCtxt}; 18 | 19 | static UNINIT_API_REGEX: Lazy> = Lazy::new(|| { 20 | use UninitApi::*; 21 | 22 | let mut m = FxHashMap::default(); 23 | m.insert( 24 | MaybeUninit, 25 | Regex::new(r"^(std|core)::mem::MaybeUninit::<.*>::uninit").unwrap(), 26 | ); 27 | m.insert( 28 | Uninitialized, 29 | Regex::new(r"^(std|core)::mem::uninitialized::<.*>").unwrap(), 30 | ); 31 | m.insert( 32 | MaybeUninitWrite, 33 | Regex::new(r"^(std|core)::mem::MaybeUninit::<.*>::write").unwrap(), 34 | ); 35 | m.insert( 36 | PtrWrite, 37 | Regex::new(r"^(std|core)::mem::MaybeUninit::<.*>::as_mut_ptr").unwrap(), 38 | ); 39 | m.insert( 40 | AssumeInit, 41 | Regex::new(r"^(std|core)::mem::MaybeUninit::<.*>::assume_init(_mut)?").unwrap(), 42 | ); 43 | m 44 | }); 45 | 46 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 47 | pub enum UninitApi { 48 | Uninitialized, 49 | MaybeUninit, 50 | AssumeInit, 51 | MaybeUninitWrite, 52 | PtrWrite, 53 | } 54 | 55 | impl UninitApi { 56 | pub fn from_instance<'tcx>(instance: Instance<'tcx>, tcx: TyCtxt<'tcx>) -> Option { 57 | let path = tcx.def_path_str_with_args(instance.def_id(), instance.args); 58 | Self::from_str(&path) 59 | } 60 | 61 | #[inline] 62 | fn from_str(path: &str) -> Option { 63 | for (k, v) in UNINIT_API_REGEX.iter() { 64 | if v.is_match(path) { 65 | return Some(*k); 66 | } 67 | } 68 | None 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn test_uninit_api() { 78 | use UninitApi::*; 79 | assert_eq!( 80 | MaybeUninit, 81 | UninitApi::from_str("std::mem::MaybeUninit::>::uninit").unwrap() 82 | ); 83 | assert_eq!( 84 | Uninitialized, 85 | UninitApi::from_str("std::mem::uninitialized::>").unwrap() 86 | ); 87 | assert_eq!( 88 | MaybeUninitWrite, 89 | UninitApi::from_str("std::mem::MaybeUninit::>::write").unwrap() 90 | ); 91 | assert_eq!( 92 | PtrWrite, 93 | UninitApi::from_str("std::mem::MaybeUninit::>::as_mut_ptr").unwrap() 94 | ); 95 | assert_eq!( 96 | AssumeInit, 97 | UninitApi::from_str("std::mem::MaybeUninit::>::assume_init") 98 | .unwrap() 99 | ); 100 | assert_eq!( 101 | AssumeInit, 102 | UninitApi::from_str("std::mem::MaybeUninit::>::assume_init_mut") 103 | .unwrap() 104 | ); 105 | assert_eq!( 106 | AssumeInit, 107 | UninitApi::from_str("std::mem::MaybeUninit::::assume_init") 108 | .unwrap() 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/interest/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod concurrency; 2 | pub mod memory; 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! The general rustc plugin framework. 2 | //! Inspired by 3 | #![feature(rustc_private)] 4 | #![feature(box_patterns)] 5 | 6 | extern crate rustc_driver; 7 | extern crate rustc_interface; 8 | extern crate rustc_middle; 9 | extern crate rustc_session; 10 | 11 | mod analysis; 12 | mod callbacks; 13 | mod detector; 14 | mod interest; 15 | mod options; 16 | 17 | use log::debug; 18 | use options::Options; 19 | use rustc_session::config::ErrorOutputType; 20 | use rustc_session::EarlyDiagCtxt; 21 | 22 | fn main() { 23 | // Initialize loggers. 24 | let handler = EarlyDiagCtxt::new(ErrorOutputType::default()); 25 | if std::env::var("RUSTC_LOG").is_ok() { 26 | rustc_driver::init_rustc_env_logger(&handler); 27 | } 28 | if std::env::var("LOCKBUD_LOG").is_ok() { 29 | let e = env_logger::Env::new() 30 | .filter("LOCKBUD_LOG") 31 | .write_style("LOCKBUD_LOG_STYLE"); 32 | env_logger::init_from_env(e); 33 | } 34 | // Get any options specified via the LOCKBUD_FLAGS environment variable 35 | let options = Options::parse_from_str(&std::env::var("LOCKBUD_FLAGS").unwrap_or_default()) 36 | .unwrap_or_default(); 37 | debug!("LOCKBUD options from environment: {options:?}"); 38 | let mut args = std::env::args_os() 39 | .enumerate() 40 | .map(|(i, arg)| { 41 | arg.into_string().unwrap_or_else(|arg| { 42 | handler.early_fatal(format!("Argument {i} is not valid Unicode: {arg:?}")) 43 | }) 44 | }) 45 | .collect::>(); 46 | assert!(!args.is_empty()); 47 | 48 | // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. 49 | // We're invoking the compiler programmatically, so we remove it if present. 50 | if args.len() > 1 && std::path::Path::new(&args[1]).file_stem() == Some("rustc".as_ref()) { 51 | args.remove(1); 52 | } 53 | 54 | let mut rustc_command_line_arguments = args; 55 | rustc_driver::install_ice_hook("ice ice ice baby", |_| ()); 56 | let exit_code = rustc_driver::catch_with_exit_code(|| { 57 | let print = "--print="; 58 | if rustc_command_line_arguments 59 | .iter() 60 | .any(|arg| arg.starts_with(print)) 61 | { 62 | // If a --print option is given on the command line we wont get called to analyze 63 | // anything. We also don't want to the caller to know that LOCKBUD adds configuration 64 | // parameters to the command line, lest the caller be cargo and it panics because 65 | // the output from --print=cfg is not what it expects. 66 | } else { 67 | let sysroot = "--sysroot"; 68 | if !rustc_command_line_arguments 69 | .iter() 70 | .any(|arg| arg.starts_with(sysroot)) 71 | { 72 | // Tell compiler where to find the std library and so on. 73 | // The compiler relies on the standard rustc driver to tell it, so we have to do likewise. 74 | rustc_command_line_arguments.push(format!("{sysroot}={}", find_sysroot())); 75 | } 76 | 77 | let always_encode_mir = "always-encode-mir"; 78 | if !rustc_command_line_arguments 79 | .iter() 80 | .any(|arg| arg.ends_with(always_encode_mir)) 81 | { 82 | // Tell compiler to emit MIR into crate for every function with a body. 83 | rustc_command_line_arguments.push(format!("-Z{always_encode_mir}")); 84 | } 85 | } 86 | 87 | let mut callbacks = callbacks::LockBudCallbacks::new(options); 88 | debug!("rustc_command_line_arguments {rustc_command_line_arguments:?}"); 89 | rustc_driver::run_compiler(&rustc_command_line_arguments, &mut callbacks); 90 | Ok(()) 91 | }); 92 | std::process::exit(exit_code); 93 | } 94 | 95 | fn find_sysroot() -> String { 96 | let home = option_env!("RUSTUP_HOME"); 97 | let toolchain = option_env!("RUSTUP_TOOLCHAIN"); 98 | #[allow(clippy::option_env_unwrap)] 99 | match (home, toolchain) { 100 | (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), 101 | _ => option_env!("RUST_SYSROOT") 102 | .expect( 103 | "Could not find sysroot. Specify the RUST_SYSROOT environment variable, \ 104 | or use rustup to set the compiler to use for LOCKBUD", 105 | ) 106 | .to_owned(), 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | //! Parsing Options. 2 | //! `--detector-kind {kind}` or `-k`, currently support only deadlock 3 | //! `--blacklist-mode` or `-b`, sets backlist than the default whitelist. 4 | //! `--crate-name-list [crate1,crate2]` or `-l`, white or black lists of crates decided by `-b`. 5 | //! if `-l` not specified, then do not white-or-black list the crates. 6 | use clap::{Arg, Command}; 7 | use std::error::Error; 8 | 9 | #[derive(Debug)] 10 | pub enum CrateNameList { 11 | White(Vec), 12 | Black(Vec), 13 | } 14 | 15 | impl Default for CrateNameList { 16 | fn default() -> Self { 17 | CrateNameList::White(Vec::new()) 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | #[non_exhaustive] 23 | pub enum DetectorKind { 24 | All, 25 | Deadlock, 26 | AtomicityViolation, 27 | Memory, 28 | Panic, 29 | // More to be supported. 30 | } 31 | 32 | fn make_options_parser<'help>() -> Command<'help> { 33 | let parser = Command::new("LOCKBUD") 34 | .no_binary_name(true) 35 | .version("v0.2.0") 36 | .arg( 37 | Arg::new("kind") 38 | .short('k') 39 | .long("detector-kind") 40 | .possible_values(["deadlock", "atomicity_violation", "memory", "all", "panic"]) 41 | .default_values(&["deadlock"]) 42 | .help("The detector kind"), 43 | ) 44 | .arg( 45 | Arg::new("black") 46 | .short('b') 47 | .long("blacklist-mode") 48 | .takes_value(false) 49 | .help("set `crates` as blacklist than whitelist"), 50 | ) 51 | .arg( 52 | Arg::new("crates") 53 | .short('l') 54 | .long("crate-name-list") 55 | .takes_value(true) 56 | .help("The crate names seperated by ,"), 57 | ); 58 | parser 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct Options { 63 | pub detector_kind: DetectorKind, 64 | pub crate_name_list: CrateNameList, 65 | } 66 | 67 | impl Default for Options { 68 | fn default() -> Self { 69 | Options { 70 | detector_kind: DetectorKind::Deadlock, 71 | crate_name_list: CrateNameList::Black(Vec::new()), 72 | } 73 | } 74 | } 75 | 76 | impl Options { 77 | pub fn parse_from_str(s: &str) -> Result> { 78 | let flags = shellwords::split(s)?; 79 | Self::parse_from_args(&flags) 80 | } 81 | 82 | pub fn parse_from_args(flags: &[String]) -> Result> { 83 | let app = make_options_parser(); 84 | let matches = app.try_get_matches_from(flags.iter())?; 85 | let detector_kind = match matches.value_of("kind") { 86 | Some("deadlock") => DetectorKind::Deadlock, 87 | Some("atomicity_violation") => DetectorKind::AtomicityViolation, 88 | Some("memory") => DetectorKind::Memory, 89 | Some("all") => DetectorKind::All, 90 | Some("panic") => DetectorKind::Panic, 91 | _ => return Err("UnsupportedDetectorKind")?, 92 | }; 93 | let black = matches.is_present("black"); 94 | let crate_name_list = matches 95 | .value_of("crates") 96 | .map(|crates| { 97 | let crates: Vec = crates.split(',').map(|s| s.into()).collect(); 98 | if black { 99 | CrateNameList::Black(crates) 100 | } else { 101 | CrateNameList::White(crates) 102 | } 103 | }) 104 | .unwrap_or_default(); 105 | Ok(Options { 106 | detector_kind, 107 | crate_name_list, 108 | }) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | 116 | #[test] 117 | fn test_parse_from_str_blacklist_ok() { 118 | let options = Options::parse_from_str("-k deadlock -b -l cc,tokio_util,indicatif").unwrap(); 119 | assert!(matches!(options.detector_kind, DetectorKind::Deadlock)); 120 | assert!( 121 | matches!(options.crate_name_list, CrateNameList::Black(v) if v == vec!["cc".to_owned(), "tokio_util".to_owned(), "indicatif".to_owned()]) 122 | ); 123 | } 124 | 125 | #[test] 126 | fn test_parse_from_str_whitelist_ok() { 127 | let options = Options::parse_from_str("-k deadlock -l cc,tokio_util,indicatif").unwrap(); 128 | assert!(matches!(options.detector_kind, DetectorKind::Deadlock)); 129 | assert!( 130 | matches!(options.crate_name_list, CrateNameList::White(v) if v == vec!["cc".to_owned(), "tokio_util".to_owned(), "indicatif".to_owned()]) 131 | ); 132 | } 133 | 134 | #[test] 135 | fn test_parse_from_str_err() { 136 | let options = Options::parse_from_str("-k unknown -b -l cc,tokio_util,indicatif"); 137 | assert!(options.is_err()); 138 | } 139 | 140 | #[test] 141 | fn test_parse_from_args_blacklist_ok() { 142 | let options = Options::parse_from_args(&[ 143 | "-k".to_owned(), 144 | "deadlock".to_owned(), 145 | "-b".to_owned(), 146 | "-l".to_owned(), 147 | "cc,tokio_util,indicatif".to_owned(), 148 | ]) 149 | .unwrap(); 150 | assert!(matches!(options.detector_kind, DetectorKind::Deadlock)); 151 | assert!( 152 | matches!(options.crate_name_list, CrateNameList::Black(v) if v == vec!["cc".to_owned(), "tokio_util".to_owned(), "indicatif".to_owned()]) 153 | ); 154 | } 155 | 156 | #[test] 157 | fn test_parse_from_args_whitelist_ok() { 158 | let options = Options::parse_from_args(&[ 159 | "-k".to_owned(), 160 | "deadlock".to_owned(), 161 | "-l".to_owned(), 162 | "cc,tokio_util,indicatif".to_owned(), 163 | ]) 164 | .unwrap(); 165 | assert!(matches!(options.detector_kind, DetectorKind::Deadlock)); 166 | assert!( 167 | matches!(options.crate_name_list, CrateNameList::White(v) if v == vec!["cc".to_owned(), "tokio_util".to_owned(), "indicatif".to_owned()]) 168 | ); 169 | } 170 | 171 | #[test] 172 | fn test_parse_from_args_err() { 173 | let options = Options::parse_from_args(&[ 174 | "-k".to_owned(), 175 | "unknown".to_owned(), 176 | "-b".to_owned(), 177 | "-l".to_owned(), 178 | "cc,tokio_util,indicatif".to_owned(), 179 | ]); 180 | assert!(options.is_err()); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /toys/atomic-violation/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atomic-violation" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "rand", 10 | ] 11 | 12 | [[package]] 13 | name = "cfg-if" 14 | version = "1.0.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 17 | 18 | [[package]] 19 | name = "getrandom" 20 | version = "0.2.7" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 23 | dependencies = [ 24 | "cfg-if", 25 | "libc", 26 | "wasi", 27 | ] 28 | 29 | [[package]] 30 | name = "libc" 31 | version = "0.2.132" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 34 | 35 | [[package]] 36 | name = "ppv-lite86" 37 | version = "0.2.16" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 40 | 41 | [[package]] 42 | name = "rand" 43 | version = "0.8.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 46 | dependencies = [ 47 | "libc", 48 | "rand_chacha", 49 | "rand_core", 50 | ] 51 | 52 | [[package]] 53 | name = "rand_chacha" 54 | version = "0.3.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 57 | dependencies = [ 58 | "ppv-lite86", 59 | "rand_core", 60 | ] 61 | 62 | [[package]] 63 | name = "rand_core" 64 | version = "0.6.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 67 | dependencies = [ 68 | "getrandom", 69 | ] 70 | 71 | [[package]] 72 | name = "wasi" 73 | version = "0.11.0+wasi-snapshot-preview1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 76 | -------------------------------------------------------------------------------- /toys/atomic-violation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atomic-violation" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | -------------------------------------------------------------------------------- /toys/atomic-violation/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::Ordering; 2 | use std::sync::atomic::{AtomicBool, AtomicI32}; 3 | 4 | fn gen_rand_val_bool() -> bool { 5 | rand::random::() 6 | } 7 | 8 | fn gen_rand_val_i32() -> i32 { 9 | rand::random::() 10 | } 11 | 12 | fn buggy_control_dep_bool() { 13 | let a = AtomicBool::new(gen_rand_val_bool()); 14 | if a.load(Ordering::Relaxed) { 15 | a.store(false, Ordering::Relaxed); 16 | } 17 | println!("{}", a.load(Ordering::Relaxed)); 18 | } 19 | 20 | fn buggy_control_dep_i32() { 21 | let a = AtomicI32::new(gen_rand_val_i32()); 22 | let v = a.load(Ordering::Relaxed); 23 | let v3 = v + 1; 24 | let v4 = if v3 > 10 { v3 + 2 } else { v3 - 1 }; 25 | if v4 > 11 && gen_rand_val_i32() < 12 { 26 | a.store(10, Ordering::Relaxed); 27 | } 28 | println!("{:?}", a); 29 | } 30 | 31 | fn buggy_data_dep_i32() { 32 | let a = AtomicI32::new(gen_rand_val_i32()); 33 | let v = a.load(Ordering::Relaxed); 34 | let v3 = v + 1; 35 | let v4 = if v3 > 10 { v3 + 2 } else { v3 - 1 }; 36 | a.store(v4, Ordering::Relaxed); 37 | println!("{:?}", a); 38 | } 39 | 40 | fn buggy_both_dep_i32() { 41 | let a = AtomicI32::new(gen_rand_val_i32()); 42 | let v = a.load(Ordering::Relaxed); 43 | let v3 = v + 1; 44 | let v4 = if v3 > 10 { v3 + 2 } else { v3 - 1 }; 45 | if v4 > 11 { 46 | a.store(v4, Ordering::Relaxed); 47 | } 48 | println!("{:?}", a); 49 | } 50 | 51 | fn maybe_false_positive() { 52 | let a = AtomicI32::new(gen_rand_val_i32()); 53 | let v = a.load(Ordering::Relaxed); 54 | let v3 = v + 1; 55 | let v4 = if v3 > 10 { v3 + 2 } else { v3 - 1 }; 56 | if v4 > 11 { 57 | if let Ok(_v) = a.compare_exchange(v, v4, Ordering::Relaxed, Ordering::Relaxed) { 58 | if gen_rand_val_i32() < 12 { 59 | a.store(10, Ordering::Relaxed); 60 | } 61 | } 62 | } 63 | println!("{:?}", a); 64 | } 65 | 66 | fn main() { 67 | buggy_control_dep_bool(); 68 | buggy_control_dep_i32(); 69 | buggy_data_dep_i32(); 70 | buggy_both_dep_i32(); 71 | maybe_false_positive(); 72 | } 73 | -------------------------------------------------------------------------------- /toys/call-no-deadlock/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "call-no-deadlock" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/call-no-deadlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "call-no-deadlock" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/call-no-deadlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, MutexGuard}; 2 | 3 | fn func(_g1: MutexGuard<'_, i32>, _g2: MutexGuard<'_, i32>) { 4 | } 5 | 6 | fn main() { 7 | let mu1 = Mutex::new(1); 8 | let mu2 = Mutex::new(1); 9 | loop { 10 | let g1 = mu1.lock().unwrap(); 11 | let g2 = mu2.lock().unwrap(); 12 | if *g1 == 0 { 13 | func(g1, g2); 14 | return; 15 | } 16 | std::mem::drop(g2); 17 | std::mem::drop(g1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /toys/condvar-closure/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "condvar-closure" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "parking_lot", 28 | ] 29 | 30 | [[package]] 31 | name = "libc" 32 | version = "0.2.127" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" 35 | 36 | [[package]] 37 | name = "lock_api" 38 | version = "0.4.7" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 41 | dependencies = [ 42 | "autocfg", 43 | "scopeguard", 44 | ] 45 | 46 | [[package]] 47 | name = "parking_lot" 48 | version = "0.12.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 51 | dependencies = [ 52 | "lock_api", 53 | "parking_lot_core", 54 | ] 55 | 56 | [[package]] 57 | name = "parking_lot_core" 58 | version = "0.9.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 61 | dependencies = [ 62 | "cfg-if", 63 | "libc", 64 | "redox_syscall", 65 | "smallvec", 66 | "windows-sys", 67 | ] 68 | 69 | [[package]] 70 | name = "redox_syscall" 71 | version = "0.2.16" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 74 | dependencies = [ 75 | "bitflags", 76 | ] 77 | 78 | [[package]] 79 | name = "scopeguard" 80 | version = "1.1.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 83 | 84 | [[package]] 85 | name = "smallvec" 86 | version = "1.9.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 89 | 90 | [[package]] 91 | name = "windows-sys" 92 | version = "0.36.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 95 | dependencies = [ 96 | "windows_aarch64_msvc", 97 | "windows_i686_gnu", 98 | "windows_i686_msvc", 99 | "windows_x86_64_gnu", 100 | "windows_x86_64_msvc", 101 | ] 102 | 103 | [[package]] 104 | name = "windows_aarch64_msvc" 105 | version = "0.36.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 108 | 109 | [[package]] 110 | name = "windows_i686_gnu" 111 | version = "0.36.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 114 | 115 | [[package]] 116 | name = "windows_i686_msvc" 117 | version = "0.36.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 120 | 121 | [[package]] 122 | name = "windows_x86_64_gnu" 123 | version = "0.36.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 126 | 127 | [[package]] 128 | name = "windows_x86_64_msvc" 129 | version = "0.36.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 132 | -------------------------------------------------------------------------------- /toys/condvar-closure/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "condvar-closure" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | parking_lot = "0.12.1" 10 | -------------------------------------------------------------------------------- /toys/condvar-closure/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::thread; 3 | 4 | fn std_correct() { 5 | use std::sync::{Condvar, Mutex}; 6 | 7 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 8 | let pair2 = pair1.clone(); 9 | 10 | let th1 = thread::spawn(move || { 11 | let (lock, cvar) = &*pair1; 12 | let mut started = lock.lock().unwrap(); 13 | while !*started { 14 | started = cvar.wait(started).unwrap(); 15 | } 16 | }); 17 | 18 | let th2 = thread::spawn(move || { 19 | let (lock, cvar) = &*pair2; 20 | let mut started = lock.lock().unwrap(); 21 | *started = true; 22 | cvar.notify_one(); 23 | }); 24 | 25 | th1.join().unwrap(); 26 | th2.join().unwrap(); 27 | } 28 | 29 | fn std_deadlock_wait() { 30 | use std::sync::{Condvar, Mutex}; 31 | let mu1 = Arc::new(Mutex::new(1)); 32 | let mu2 = mu1.clone(); 33 | 34 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 35 | let pair2 = pair1.clone(); 36 | 37 | let th1 = thread::spawn(move || { 38 | let _i = mu1.lock().unwrap(); 39 | let (lock, cvar) = &*pair1; 40 | let mut started = lock.lock().unwrap(); 41 | while !*started { 42 | started = cvar.wait(started).unwrap(); 43 | } 44 | }); 45 | 46 | let th2 = thread::spawn(move || { 47 | let _i = mu2.lock().unwrap(); 48 | let (lock, cvar) = &*pair2; 49 | let mut started = lock.lock().unwrap(); 50 | *started = true; 51 | cvar.notify_one(); 52 | }); 53 | 54 | th1.join().unwrap(); 55 | th2.join().unwrap(); 56 | } 57 | 58 | fn std_missing_lock_before_notify() { 59 | use std::sync::{Condvar, Mutex}; 60 | 61 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 62 | let pair2 = pair1.clone(); 63 | 64 | let th1 = thread::spawn(move || { 65 | let (lock, cvar) = &*pair1; 66 | let mut started = lock.lock().unwrap(); 67 | while !*started { 68 | started = cvar.wait(started).unwrap(); 69 | } 70 | }); 71 | 72 | let th2 = thread::spawn(move || { 73 | let (_, cvar) = &*pair2; 74 | cvar.notify_one(); 75 | }); 76 | 77 | th1.join().unwrap(); 78 | th2.join().unwrap(); 79 | } 80 | 81 | fn parking_lot_correct() { 82 | use parking_lot::{Condvar, Mutex}; 83 | 84 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 85 | let pair2 = pair1.clone(); 86 | 87 | let th1 = thread::spawn(move || { 88 | let (lock, cvar) = &*pair1; 89 | let mut started = lock.lock(); 90 | while !*started { 91 | cvar.wait(&mut started); 92 | } 93 | }); 94 | 95 | let th2 = thread::spawn(move || { 96 | let (lock, cvar) = &*pair2; 97 | let mut started = lock.lock(); 98 | *started = true; 99 | cvar.notify_one(); 100 | }); 101 | 102 | th1.join().unwrap(); 103 | th2.join().unwrap(); 104 | } 105 | 106 | fn parking_lot_deadlock_wait() { 107 | use parking_lot::{Condvar, Mutex}; 108 | let mu1 = Arc::new(Mutex::new(1)); 109 | let mu2 = mu1.clone(); 110 | 111 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 112 | let pair2 = pair1.clone(); 113 | 114 | let th1 = thread::spawn(move || { 115 | let _i = mu1.lock(); 116 | let (lock, cvar) = &*pair1; 117 | let mut started = lock.lock(); 118 | while !*started { 119 | cvar.wait(&mut started); 120 | } 121 | }); 122 | 123 | let th2 = thread::spawn(move || { 124 | let _i = mu2.lock(); 125 | let (lock, cvar) = &*pair2; 126 | let mut started = lock.lock(); 127 | *started = true; 128 | cvar.notify_one(); 129 | }); 130 | 131 | th1.join().unwrap(); 132 | th2.join().unwrap(); 133 | } 134 | 135 | fn parking_lot_missing_lock_before_notify() { 136 | use parking_lot::{Condvar, Mutex}; 137 | 138 | let pair1 = Arc::new((Mutex::new(false), Condvar::new())); 139 | let pair2 = pair1.clone(); 140 | 141 | let th1 = thread::spawn(move || { 142 | let (lock, cvar) = &*pair1; 143 | let mut started = lock.lock(); 144 | while !*started { 145 | cvar.wait(&mut started); 146 | } 147 | }); 148 | 149 | let th2 = thread::spawn(move || { 150 | let (_lock, cvar) = &*pair2; 151 | cvar.notify_one(); 152 | }); 153 | 154 | th1.join().unwrap(); 155 | th2.join().unwrap(); 156 | } 157 | 158 | fn main() { 159 | std_correct(); 160 | std_deadlock_wait(); 161 | std_missing_lock_before_notify(); 162 | parking_lot_correct(); 163 | parking_lot_deadlock_wait(); 164 | parking_lot_missing_lock_before_notify(); 165 | } 166 | -------------------------------------------------------------------------------- /toys/condvar-struct/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "condvar-struct" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "parking_lot", 28 | ] 29 | 30 | [[package]] 31 | name = "libc" 32 | version = "0.2.131" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" 35 | 36 | [[package]] 37 | name = "lock_api" 38 | version = "0.4.7" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 41 | dependencies = [ 42 | "autocfg", 43 | "scopeguard", 44 | ] 45 | 46 | [[package]] 47 | name = "parking_lot" 48 | version = "0.12.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 51 | dependencies = [ 52 | "lock_api", 53 | "parking_lot_core", 54 | ] 55 | 56 | [[package]] 57 | name = "parking_lot_core" 58 | version = "0.9.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 61 | dependencies = [ 62 | "cfg-if", 63 | "libc", 64 | "redox_syscall", 65 | "smallvec", 66 | "windows-sys", 67 | ] 68 | 69 | [[package]] 70 | name = "redox_syscall" 71 | version = "0.2.16" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 74 | dependencies = [ 75 | "bitflags", 76 | ] 77 | 78 | [[package]] 79 | name = "scopeguard" 80 | version = "1.1.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 83 | 84 | [[package]] 85 | name = "smallvec" 86 | version = "1.9.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 89 | 90 | [[package]] 91 | name = "windows-sys" 92 | version = "0.36.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 95 | dependencies = [ 96 | "windows_aarch64_msvc", 97 | "windows_i686_gnu", 98 | "windows_i686_msvc", 99 | "windows_x86_64_gnu", 100 | "windows_x86_64_msvc", 101 | ] 102 | 103 | [[package]] 104 | name = "windows_aarch64_msvc" 105 | version = "0.36.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 108 | 109 | [[package]] 110 | name = "windows_i686_gnu" 111 | version = "0.36.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 114 | 115 | [[package]] 116 | name = "windows_i686_msvc" 117 | version = "0.36.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 120 | 121 | [[package]] 122 | name = "windows_x86_64_gnu" 123 | version = "0.36.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 126 | 127 | [[package]] 128 | name = "windows_x86_64_msvc" 129 | version = "0.36.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 132 | -------------------------------------------------------------------------------- /toys/condvar-struct/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "condvar-struct" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | parking_lot = "0.12.1" 10 | -------------------------------------------------------------------------------- /toys/condvar-struct/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::thread; 3 | 4 | fn std_correct() { 5 | use std::sync::{Condvar, Mutex}; 6 | 7 | struct CondPair { 8 | lock: Mutex, 9 | cvar: Condvar, 10 | } 11 | 12 | impl CondPair { 13 | fn new() -> Self { 14 | Self { 15 | lock: Mutex::new(false), 16 | cvar: Condvar::new(), 17 | } 18 | } 19 | fn wait(&self) { 20 | let mut started = self.lock.lock().unwrap(); 21 | println!("start waiting!"); 22 | while !*started { 23 | started = self.cvar.wait(started).unwrap(); 24 | } 25 | println!("end waiting"); 26 | } 27 | fn notify(&self) { 28 | let mut started = self.lock.lock().unwrap(); 29 | println!("start notifying!"); 30 | *started = true; 31 | self.cvar.notify_one(); 32 | println!("end notifying!"); 33 | } 34 | } 35 | 36 | let condvar1 = Arc::new(CondPair::new()); 37 | let condvar2 = condvar1.clone(); 38 | 39 | let th1 = thread::spawn(move || { 40 | condvar1.wait(); 41 | }); 42 | 43 | condvar2.notify(); 44 | th1.join().unwrap(); 45 | } 46 | 47 | fn std_deadlock_wait() { 48 | use std::sync::{Condvar, Mutex}; 49 | 50 | struct CondPair { 51 | lock: Mutex, 52 | cvar: Condvar, 53 | other: Mutex, 54 | } 55 | 56 | impl CondPair { 57 | fn new() -> Self { 58 | Self { 59 | lock: Mutex::new(false), 60 | cvar: Condvar::new(), 61 | other: Mutex::new(1), 62 | } 63 | } 64 | fn wait(&self) { 65 | let _i = self.other.lock().unwrap(); 66 | let mut started = self.lock.lock().unwrap(); 67 | println!("start waiting!"); 68 | while !*started { 69 | started = self.cvar.wait(started).unwrap(); 70 | } 71 | println!("end waiting"); 72 | } 73 | fn notify(&self) { 74 | let _i = self.other.lock().unwrap(); 75 | let mut started = self.lock.lock().unwrap(); 76 | println!("start notifying!"); 77 | *started = true; 78 | self.cvar.notify_one(); 79 | println!("end notifying!"); 80 | } 81 | } 82 | 83 | let condvar1 = Arc::new(CondPair::new()); 84 | let condvar2 = condvar1.clone(); 85 | 86 | let th1 = thread::spawn(move || { 87 | condvar1.wait(); 88 | }); 89 | 90 | condvar2.notify(); 91 | th1.join().unwrap(); 92 | } 93 | 94 | fn std_missing_lock_before_notify() { 95 | use std::sync::{Condvar, Mutex}; 96 | 97 | struct CondPair { 98 | lock: Mutex, 99 | cvar: Condvar, 100 | } 101 | 102 | impl CondPair { 103 | fn new() -> Self { 104 | Self { 105 | lock: Mutex::new(false), 106 | cvar: Condvar::new(), 107 | } 108 | } 109 | fn wait(&self) { 110 | let mut started = self.lock.lock().unwrap(); 111 | println!("start waiting!"); 112 | while !*started { 113 | started = self.cvar.wait(started).unwrap(); 114 | } 115 | println!("end waiting"); 116 | } 117 | fn notify(&self) { 118 | println!("start notifying!"); 119 | self.cvar.notify_one(); 120 | println!("end notifying!"); 121 | } 122 | } 123 | 124 | let condvar1 = Arc::new(CondPair::new()); 125 | let condvar2 = condvar1.clone(); 126 | 127 | let th1 = thread::spawn(move || { 128 | condvar1.wait(); 129 | }); 130 | 131 | condvar2.notify(); 132 | th1.join().unwrap(); 133 | } 134 | 135 | fn parking_lot_correct() { 136 | use parking_lot::{Condvar, Mutex}; 137 | 138 | struct CondPair { 139 | lock: Mutex, 140 | cvar: Condvar, 141 | } 142 | 143 | impl CondPair { 144 | fn new() -> Self { 145 | Self { 146 | lock: Mutex::new(false), 147 | cvar: Condvar::new(), 148 | } 149 | } 150 | fn wait(&self) { 151 | let mut started = self.lock.lock(); 152 | println!("start waiting!"); 153 | while !*started { 154 | self.cvar.wait(&mut started); 155 | } 156 | println!("end waiting"); 157 | } 158 | fn notify(&self) { 159 | let mut started = self.lock.lock(); 160 | println!("start notifying!"); 161 | *started = true; 162 | self.cvar.notify_one(); 163 | println!("end notifying!"); 164 | } 165 | } 166 | 167 | let condvar1 = Arc::new(CondPair::new()); 168 | let condvar2 = condvar1.clone(); 169 | 170 | let th1 = thread::spawn(move || { 171 | condvar1.wait(); 172 | }); 173 | 174 | condvar2.notify(); 175 | th1.join().unwrap(); 176 | } 177 | 178 | fn parking_lot_deadlock_wait() { 179 | use parking_lot::{Condvar, Mutex}; 180 | 181 | struct CondPair { 182 | lock: Mutex, 183 | cvar: Condvar, 184 | other: Mutex, 185 | } 186 | 187 | impl CondPair { 188 | fn new() -> Self { 189 | Self { 190 | lock: Mutex::new(false), 191 | cvar: Condvar::new(), 192 | other: Mutex::new(1), 193 | } 194 | } 195 | fn wait(&self) { 196 | let _i = self.other.lock(); 197 | let mut started = self.lock.lock(); 198 | println!("start waiting!"); 199 | while !*started { 200 | self.cvar.wait(&mut started); 201 | } 202 | println!("end waiting"); 203 | } 204 | fn notify(&self) { 205 | let _i = self.other.lock(); 206 | let mut started = self.lock.lock(); 207 | println!("start notifying!"); 208 | *started = true; 209 | self.cvar.notify_one(); 210 | println!("end notifying!"); 211 | } 212 | } 213 | 214 | let condvar1 = Arc::new(CondPair::new()); 215 | let condvar2 = condvar1.clone(); 216 | 217 | let th1 = thread::spawn(move || { 218 | condvar1.wait(); 219 | }); 220 | 221 | condvar2.notify(); 222 | th1.join().unwrap(); 223 | } 224 | 225 | fn parking_lot_missing_lock_before_notify() { 226 | use parking_lot::{Condvar, Mutex}; 227 | 228 | struct CondPair { 229 | lock: Mutex, 230 | cvar: Condvar, 231 | } 232 | 233 | impl CondPair { 234 | fn new() -> Self { 235 | Self { 236 | lock: Mutex::new(false), 237 | cvar: Condvar::new(), 238 | } 239 | } 240 | fn wait(&self) { 241 | let mut started = self.lock.lock(); 242 | println!("start waiting!"); 243 | while !*started { 244 | self.cvar.wait(&mut started); 245 | } 246 | println!("end waiting"); 247 | } 248 | fn notify(&self) { 249 | println!("start notifying!"); 250 | self.cvar.notify_one(); 251 | println!("end notifying!"); 252 | } 253 | } 254 | 255 | let condvar1 = Arc::new(CondPair::new()); 256 | let condvar2 = condvar1.clone(); 257 | 258 | let th1 = thread::spawn(move || { 259 | condvar1.wait(); 260 | }); 261 | 262 | condvar2.notify(); 263 | th1.join().unwrap(); 264 | } 265 | 266 | fn main() { 267 | std_correct(); 268 | std_deadlock_wait(); 269 | std_missing_lock_before_notify(); 270 | parking_lot_correct(); 271 | parking_lot_deadlock_wait(); 272 | parking_lot_missing_lock_before_notify(); 273 | } 274 | -------------------------------------------------------------------------------- /toys/conflict-inter/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "conflict-inter" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /toys/conflict-inter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conflict-inter" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /toys/conflict-inter/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync; 2 | use std::thread; 3 | 4 | struct Foo { 5 | mu1: sync::Arc>, 6 | rw1: sync::RwLock, 7 | } 8 | 9 | impl Foo { 10 | fn new() -> Self { 11 | Self { 12 | mu1: sync::Arc::new(sync::Mutex::new(1)), 13 | rw1: sync::RwLock::new(1), 14 | } 15 | } 16 | 17 | fn std_mutex_1(&self) { 18 | match *self.mu1.lock().unwrap() { 19 | 1 => {}, 20 | _ => { self.std_rw_2(); }, 21 | }; 22 | } 23 | 24 | fn std_rw_2(&self) { 25 | *self.rw1.write().unwrap() += 1; 26 | } 27 | 28 | fn std_rw_1(&self) { 29 | match *self.rw1.read().unwrap() { 30 | 1 => {}, 31 | _ => { self.std_mutex_2(); }, 32 | } 33 | } 34 | 35 | fn std_mutex_2(&self) { 36 | *self.mu1.lock().unwrap() += 1; 37 | } 38 | } 39 | 40 | fn main() { 41 | let foo = sync::Arc::new(Foo::new()); 42 | let foo1 = foo.clone(); 43 | let th = thread::spawn(move || { 44 | foo1.std_mutex_1(); 45 | }); 46 | foo.std_rw_1(); 47 | th.join().unwrap(); 48 | } 49 | -------------------------------------------------------------------------------- /toys/conflict/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "conflict" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/conflict/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conflict" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/conflict/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex, RwLock}; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | struct MyStruct { 6 | mu: Mutex, 7 | rw1: RwLock, 8 | rw2: RwLock, 9 | } 10 | 11 | impl MyStruct { 12 | fn new() -> Self { 13 | Self { 14 | mu: Mutex::new(true), 15 | rw1: RwLock::new(1), 16 | rw2: RwLock::new(1), 17 | } 18 | } 19 | 20 | fn mu_rw1(&self) -> i32 { 21 | let mu = self.mu.lock().unwrap(); 22 | println!("mu_rw1: mu locked"); 23 | thread::sleep(Duration::from_millis(1)); 24 | let ret = match *mu { 25 | true => { 26 | let ret = self.rw1.read().unwrap(); 27 | println!("mu_rw1: rw1 locked"); 28 | println!("mu_rw1: rw1 unlocked"); 29 | *ret 30 | }, 31 | false => 0, 32 | }; 33 | println!("mu_rw1: mu unlocked"); 34 | ret 35 | } 36 | 37 | fn rw1_rw2(&self) -> u8 { 38 | let mut rw1 = self.rw1.write().unwrap(); 39 | println!("rw1_rw2: rw1 locked"); 40 | thread::sleep(Duration::from_millis(1)); 41 | *rw1 += 1; 42 | let ret = self.rw2.read().unwrap(); 43 | println!("rw1_rw2: rw2 locked"); 44 | println!("rw1_rw2: rw2 unlocked"); 45 | println!("rw1_rw2: rw1 unlocked"); 46 | *ret 47 | } 48 | 49 | fn rw2_mu(&self) -> bool { 50 | let mut rw2 = self.rw2.write().unwrap(); 51 | println!("rw2_mu: rw2 locked"); 52 | thread::sleep(Duration::from_millis(1)); 53 | *rw2 += 1; 54 | let ret = self.mu.lock().unwrap(); 55 | println!("rw2_mu: mu locked"); 56 | println!("rw2_mu: mu unlocked"); 57 | println!("rw2_mu: rw2 unlocked"); 58 | *ret 59 | } 60 | } 61 | 62 | fn main() { 63 | let my_struct = Arc::new(MyStruct::new()); 64 | let clone1 = Arc::clone(&my_struct); 65 | let clone2 = Arc::clone(&my_struct); 66 | let th1 = thread::spawn(move || { 67 | clone1.mu_rw1(); 68 | }); 69 | let th2 = thread::spawn(move || { 70 | clone2.rw1_rw2(); 71 | }); 72 | my_struct.rw2_mu(); 73 | th1.join().unwrap(); 74 | th2.join().unwrap(); 75 | } 76 | -------------------------------------------------------------------------------- /toys/inter/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.2.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "inter" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "parking_lot", 28 | "spin", 29 | ] 30 | 31 | [[package]] 32 | name = "libc" 33 | version = "0.2.126" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 36 | 37 | [[package]] 38 | name = "lock_api" 39 | version = "0.4.7" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 42 | dependencies = [ 43 | "autocfg", 44 | "scopeguard", 45 | ] 46 | 47 | [[package]] 48 | name = "parking_lot" 49 | version = "0.12.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 52 | dependencies = [ 53 | "lock_api", 54 | "parking_lot_core", 55 | ] 56 | 57 | [[package]] 58 | name = "parking_lot_core" 59 | version = "0.9.3" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 62 | dependencies = [ 63 | "cfg-if", 64 | "libc", 65 | "redox_syscall", 66 | "smallvec", 67 | "windows-sys", 68 | ] 69 | 70 | [[package]] 71 | name = "redox_syscall" 72 | version = "0.2.13" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 75 | dependencies = [ 76 | "bitflags", 77 | ] 78 | 79 | [[package]] 80 | name = "scopeguard" 81 | version = "1.1.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 84 | 85 | [[package]] 86 | name = "smallvec" 87 | version = "1.8.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 90 | 91 | [[package]] 92 | name = "spin" 93 | version = "0.5.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 96 | 97 | [[package]] 98 | name = "windows-sys" 99 | version = "0.36.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 102 | dependencies = [ 103 | "windows_aarch64_msvc", 104 | "windows_i686_gnu", 105 | "windows_i686_msvc", 106 | "windows_x86_64_gnu", 107 | "windows_x86_64_msvc", 108 | ] 109 | 110 | [[package]] 111 | name = "windows_aarch64_msvc" 112 | version = "0.36.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 115 | 116 | [[package]] 117 | name = "windows_i686_gnu" 118 | version = "0.36.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 121 | 122 | [[package]] 123 | name = "windows_i686_msvc" 124 | version = "0.36.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 127 | 128 | [[package]] 129 | name = "windows_x86_64_gnu" 130 | version = "0.36.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 133 | 134 | [[package]] 135 | name = "windows_x86_64_msvc" 136 | version = "0.36.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 139 | -------------------------------------------------------------------------------- /toys/inter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inter" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | parking_lot = "0.12" 11 | spin = "0.5.2" 12 | -------------------------------------------------------------------------------- /toys/inter/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync; 2 | 3 | struct Foo { 4 | mu1: sync::Arc>, 5 | rw1: sync::RwLock, 6 | mu2: parking_lot::Mutex, 7 | rw2: parking_lot::RwLock, 8 | mu3: spin::Mutex, 9 | rw3: spin::RwLock, 10 | } 11 | 12 | impl Foo { 13 | fn new() -> Self { 14 | Self { 15 | mu1: sync::Arc::new(sync::Mutex::new(1)), 16 | rw1: sync::RwLock::new(1), 17 | mu2: parking_lot::Mutex::new(1), 18 | rw2: parking_lot::RwLock::new(1), 19 | mu3: spin::Mutex::new(1), 20 | rw3: spin::RwLock::new(1), 21 | } 22 | } 23 | 24 | fn std_mutex_1(&self) { 25 | let guard1 = self.mu1.lock().unwrap(); 26 | match *guard1 { 27 | 1 => {}, 28 | _ => { self.std_mutex_2(); }, 29 | }; 30 | } 31 | 32 | fn std_mutex_2(&self) { 33 | *self.mu1.lock().unwrap() += 1; 34 | } 35 | 36 | fn std_rwlock_read_1(&self) { 37 | match *self.rw1.read().unwrap() { 38 | 1 => { self.std_rwlock_write_2(); }, 39 | _ => { self.std_rwlock_read_2(); }, 40 | }; 41 | } 42 | 43 | fn std_rwlock_write_1(&self) { 44 | match *self.rw1.write().unwrap() { 45 | 1 => { self.std_rwlock_write_2(); }, 46 | _ => { self.std_rwlock_read_2(); }, 47 | }; 48 | } 49 | 50 | fn std_rwlock_read_2(&self) { 51 | let _ = *self.rw1.read().unwrap(); 52 | } 53 | 54 | fn std_rwlock_write_2(&self) { 55 | *self.rw1.write().unwrap() += 1; 56 | } 57 | 58 | fn parking_lot_mutex_1(&self) { 59 | match *self.mu2.lock() { 60 | 1 => {}, 61 | _ => { self.parking_lot_mutex_2(); }, 62 | }; 63 | } 64 | 65 | fn parking_lot_mutex_2(&self) { 66 | *self.mu2.lock() += 1; 67 | } 68 | 69 | fn parking_lot_rwlock_read_1(&self) { 70 | match *self.rw2.read() { 71 | 1 => { self.parking_lot_rwlock_write_2(); }, 72 | _ => { self.parking_lot_rwlock_read_2(); }, 73 | }; 74 | } 75 | 76 | fn parking_lot_rwlock_write_1(&self) { 77 | match *self.rw2.write() { 78 | 1 => { self.parking_lot_rwlock_write_2(); }, 79 | _ => { self.parking_lot_rwlock_read_2(); }, 80 | }; 81 | } 82 | 83 | fn parking_lot_rwlock_read_2(&self) { 84 | let _ = *self.rw2.read(); 85 | } 86 | 87 | fn parking_lot_rwlock_write_2(&self) { 88 | *self.rw2.write() += 1; 89 | } 90 | 91 | fn spin_mutex_1(&self) { 92 | match *self.mu3.lock() { 93 | 1 => { self.recur() }, 94 | _ => { self.spin_mutex_2(); }, 95 | }; 96 | } 97 | 98 | fn recur(&self) { 99 | self.spin_mutex_1(); 100 | } 101 | 102 | fn spin_mutex_2(&self) { 103 | *self.mu3.lock() += 1; 104 | } 105 | 106 | fn spin_rwlock_read_1(&self) { 107 | match *self.rw3.read() { 108 | 1 => { self.spin_rwlock_write_2(); }, 109 | _ => { self.spin_rwlock_read_2(); }, 110 | } 111 | } 112 | 113 | fn spin_rwlock_write_1(&self) { 114 | match *self.rw3.write() { 115 | 1 => { self.spin_rwlock_write_2(); }, 116 | _ => { self.spin_rwlock_read_2(); }, 117 | }; 118 | } 119 | 120 | fn spin_rwlock_read_2(&self) { 121 | let _ = *self.rw3.read(); 122 | } 123 | 124 | fn spin_rwlock_write_2(&self) { 125 | *self.rw3.write() += 1; 126 | } 127 | } 128 | 129 | fn main() { 130 | let foo1 = Foo::new(); 131 | foo1.std_mutex_1(); 132 | foo1.std_mutex_2(); 133 | foo1.std_rwlock_read_1(); 134 | foo1.std_rwlock_write_1(); 135 | foo1.parking_lot_mutex_1(); 136 | foo1.parking_lot_mutex_2(); 137 | foo1.parking_lot_rwlock_read_1(); 138 | foo1.parking_lot_rwlock_read_2(); 139 | foo1.parking_lot_rwlock_write_1(); 140 | foo1.parking_lot_rwlock_write_2(); 141 | foo1.spin_mutex_1(); 142 | foo1.spin_mutex_2(); 143 | foo1.recur(); 144 | foo1.spin_rwlock_read_1(); 145 | foo1.spin_rwlock_read_2(); 146 | foo1.spin_rwlock_write_1(); 147 | foo1.spin_rwlock_write_2(); 148 | } 149 | -------------------------------------------------------------------------------- /toys/intra/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bitflags" 5 | version = "1.2.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "0.1.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 14 | 15 | [[package]] 16 | name = "cloudabi" 17 | version = "0.0.3" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 20 | dependencies = [ 21 | "bitflags", 22 | ] 23 | 24 | [[package]] 25 | name = "intra" 26 | version = "0.1.0" 27 | dependencies = [ 28 | "parking_lot", 29 | ] 30 | 31 | [[package]] 32 | name = "libc" 33 | version = "0.2.70" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" 36 | 37 | [[package]] 38 | name = "lock_api" 39 | version = "0.3.4" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 42 | dependencies = [ 43 | "scopeguard", 44 | ] 45 | 46 | [[package]] 47 | name = "parking_lot" 48 | version = "0.10.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 51 | dependencies = [ 52 | "lock_api", 53 | "parking_lot_core", 54 | ] 55 | 56 | [[package]] 57 | name = "parking_lot_core" 58 | version = "0.7.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 61 | dependencies = [ 62 | "cfg-if", 63 | "cloudabi", 64 | "libc", 65 | "redox_syscall", 66 | "smallvec", 67 | "winapi", 68 | ] 69 | 70 | [[package]] 71 | name = "redox_syscall" 72 | version = "0.1.56" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 75 | 76 | [[package]] 77 | name = "scopeguard" 78 | version = "1.1.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 81 | 82 | [[package]] 83 | name = "smallvec" 84 | version = "1.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 87 | 88 | [[package]] 89 | name = "winapi" 90 | version = "0.3.8" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 93 | dependencies = [ 94 | "winapi-i686-pc-windows-gnu", 95 | "winapi-x86_64-pc-windows-gnu", 96 | ] 97 | 98 | [[package]] 99 | name = "winapi-i686-pc-windows-gnu" 100 | version = "0.4.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 103 | 104 | [[package]] 105 | name = "winapi-x86_64-pc-windows-gnu" 106 | version = "0.4.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 109 | -------------------------------------------------------------------------------- /toys/intra/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intra" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | parking_lot = "0.12" 11 | -------------------------------------------------------------------------------- /toys/intra/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync; 2 | use parking_lot; 3 | 4 | fn std_mutex() { 5 | let mu1 = sync::Mutex::new(1); 6 | match *mu1.lock().ok().unwrap() { 7 | 1 => {}, 8 | _ => { *mu1.lock().unwrap() += 1; }, 9 | }; 10 | } 11 | 12 | fn std_rwlock() -> i32 { 13 | let rw1 = sync::RwLock::new(1); 14 | let mut a = 0; 15 | match *rw1.read().unwrap() { 16 | 1 => { *rw1.write().unwrap() += 1; }, 17 | _ => { a = *rw1.read().unwrap(); }, 18 | }; 19 | a 20 | } 21 | 22 | fn parking_lot_mutex() { 23 | let mu1 = parking_lot::Mutex::new(1); 24 | match *mu1.lock() { 25 | 1 => {}, 26 | _ => { *mu1.lock() += 1; }, 27 | }; 28 | } 29 | 30 | fn parking_lot_rwlock() -> i32 { 31 | let rw1 = parking_lot::RwLock::new(1); 32 | let mut a = 0; 33 | match *rw1.read() { 34 | 1 => { *rw1.write() += 1; }, 35 | _ => { a = *rw1.read(); }, 36 | }; 37 | a 38 | } 39 | 40 | fn main() { 41 | std_mutex(); 42 | std_rwlock(); 43 | parking_lot_mutex(); 44 | parking_lot_rwlock(); 45 | } 46 | -------------------------------------------------------------------------------- /toys/invalid-free/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "invalid-free" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/invalid-free/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "invalid-free" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/invalid-free/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::ptr::addr_of_mut; 3 | 4 | fn uninit() { 5 | unsafe { 6 | #[allow(invalid_value, deprecated)] 7 | let _obj: Vec = mem::uninitialized(); 8 | } 9 | } 10 | 11 | fn assume_write_fp() { 12 | let mut uninit = std::mem::MaybeUninit::>::uninit(); 13 | unsafe { 14 | uninit.write(Vec::new()); 15 | uninit.assume_init(); 16 | } 17 | } 18 | 19 | fn assume_ptr_write_fp() { 20 | #[derive(Debug)] 21 | struct Obj { 22 | a: Vec, 23 | b: bool, 24 | } 25 | 26 | let mut uninit = std::mem::MaybeUninit::::uninit(); 27 | unsafe { 28 | let ptr = uninit.as_mut_ptr(); 29 | addr_of_mut!((*ptr).a).write(Vec::new()); 30 | addr_of_mut!((*ptr).b).write(true); 31 | uninit.assume_init(); 32 | } 33 | } 34 | 35 | fn assume() { 36 | let uninit = std::mem::MaybeUninit::>::uninit(); 37 | unsafe { 38 | uninit.assume_init(); 39 | } 40 | } 41 | 42 | fn main() { 43 | assume_write_fp(); 44 | assume_ptr_write_fp(); 45 | assume(); 46 | uninit(); 47 | } 48 | -------------------------------------------------------------------------------- /toys/issue71/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "issue71" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/issue71/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "issue71" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /toys/issue71/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | fn main() { 4 | let mut_a = Arc::new(Mutex::new(true)); 5 | let mut_b = Arc::new(Mutex::new(true)); 6 | 7 | let mut_a_clone = mut_a.clone(); 8 | let mut_b_clone = mut_b.clone(); 9 | std::thread::spawn(move || loop { 10 | let _b = mut_b_clone.lock().unwrap(); 11 | let _a = mut_a_clone.lock().unwrap(); 12 | dbg!("thread"); 13 | }); 14 | 15 | loop { 16 | let _a = mut_a.lock().unwrap(); 17 | let _b = mut_b.lock().unwrap(); 18 | dbg!("main"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /toys/lock-closure/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "lock-closure" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/lock-closure/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lock-closure" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/lock-closure/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::thread; 3 | 4 | fn one_closure_one_caller() { 5 | let lock_a1 = Arc::new(Mutex::new(1)); 6 | let lock_a2 = lock_a1.clone(); 7 | let lock_b1 = Arc::new(Mutex::new(true)); 8 | let lock_b2 = lock_b1.clone(); 9 | { 10 | let _b = lock_b1.lock().unwrap(); 11 | let _a = lock_a1.lock().unwrap(); 12 | } 13 | let th = thread::spawn(move || { 14 | let _a = lock_a2.lock().unwrap(); 15 | let _b = lock_b2.lock().unwrap(); 16 | }); 17 | th.join().unwrap(); 18 | } 19 | 20 | fn two_closures() { 21 | let lock_a1 = Arc::new(Mutex::new(1)); 22 | let lock_a2 = lock_a1.clone(); 23 | let lock_b1 = Arc::new(Mutex::new(true)); 24 | let lock_b2 = lock_b1.clone(); 25 | let th1 = thread::spawn(move || { 26 | let _b = lock_b1.lock().unwrap(); 27 | let _a = lock_a1.lock().unwrap(); 28 | }); 29 | let th2 = thread::spawn(move || { 30 | let _a = lock_a2.lock().unwrap(); 31 | let _b = lock_b2.lock().unwrap(); 32 | }); 33 | th1.join().unwrap(); 34 | th2.join().unwrap(); 35 | } 36 | 37 | fn main() { 38 | one_closure_one_caller(); 39 | two_closures(); 40 | } 41 | -------------------------------------------------------------------------------- /toys/panic/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "panic" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/panic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/panic/src/main.rs: -------------------------------------------------------------------------------- 1 | fn panic_macro() { 2 | panic!("This is a panic!"); 3 | } 4 | 5 | fn assert_panic() { 6 | let a = 10; 7 | assert_eq!(a, 12); 8 | } 9 | 10 | fn unwrap_panic() { 11 | let a: Option = None; 12 | let _b = a.unwrap(); 13 | } 14 | 15 | fn expect_panic() { 16 | let a: Result = Err(()); 17 | let _b = a.expect("Expect panic!"); 18 | } 19 | 20 | fn no_panic_fp() { 21 | println!("..."); 22 | } 23 | 24 | fn main() { 25 | panic_macro(); 26 | assert_panic(); 27 | unwrap_panic(); 28 | expect_panic(); 29 | no_panic_fp(); 30 | } 31 | -------------------------------------------------------------------------------- /toys/recursive-no-deadlock/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "libc" 25 | version = "0.2.134" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" 28 | 29 | [[package]] 30 | name = "lock_api" 31 | version = "0.4.9" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 34 | dependencies = [ 35 | "autocfg", 36 | "scopeguard", 37 | ] 38 | 39 | [[package]] 40 | name = "parking_lot" 41 | version = "0.12.1" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 44 | dependencies = [ 45 | "lock_api", 46 | "parking_lot_core", 47 | ] 48 | 49 | [[package]] 50 | name = "parking_lot_core" 51 | version = "0.9.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 54 | dependencies = [ 55 | "cfg-if", 56 | "libc", 57 | "redox_syscall", 58 | "smallvec", 59 | "windows-sys", 60 | ] 61 | 62 | [[package]] 63 | name = "recursive-no-deadlock" 64 | version = "0.1.0" 65 | dependencies = [ 66 | "parking_lot", 67 | ] 68 | 69 | [[package]] 70 | name = "redox_syscall" 71 | version = "0.2.16" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 74 | dependencies = [ 75 | "bitflags", 76 | ] 77 | 78 | [[package]] 79 | name = "scopeguard" 80 | version = "1.1.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 83 | 84 | [[package]] 85 | name = "smallvec" 86 | version = "1.10.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 89 | 90 | [[package]] 91 | name = "windows-sys" 92 | version = "0.36.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 95 | dependencies = [ 96 | "windows_aarch64_msvc", 97 | "windows_i686_gnu", 98 | "windows_i686_msvc", 99 | "windows_x86_64_gnu", 100 | "windows_x86_64_msvc", 101 | ] 102 | 103 | [[package]] 104 | name = "windows_aarch64_msvc" 105 | version = "0.36.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 108 | 109 | [[package]] 110 | name = "windows_i686_gnu" 111 | version = "0.36.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 114 | 115 | [[package]] 116 | name = "windows_i686_msvc" 117 | version = "0.36.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 120 | 121 | [[package]] 122 | name = "windows_x86_64_gnu" 123 | version = "0.36.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 126 | 127 | [[package]] 128 | name = "windows_x86_64_msvc" 129 | version = "0.36.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 132 | -------------------------------------------------------------------------------- /toys/recursive-no-deadlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "recursive-no-deadlock" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | parking_lot = "0.12" -------------------------------------------------------------------------------- /toys/recursive-no-deadlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use parking_lot; 2 | 3 | fn parking_lot_rwlock() -> i32 { 4 | let rw1 = parking_lot::RwLock::new(1); 5 | let mut a = 0; 6 | match *rw1.read() { 7 | 1 => { a = *rw1.read_recursive() + a; }, 8 | _ => { a = *rw1.read() + a; }, 9 | }; 10 | a 11 | } 12 | 13 | fn main() { 14 | parking_lot_rwlock(); 15 | } 16 | -------------------------------------------------------------------------------- /toys/static-ref/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "lazy_static" 5 | version = "1.4.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 8 | 9 | [[package]] 10 | name = "static-ref" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "lazy_static", 14 | ] 15 | -------------------------------------------------------------------------------- /toys/static-ref/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static-ref" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | lazy_static = "1.4.0" 11 | -------------------------------------------------------------------------------- /toys/static-ref/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | use std::sync::Arc; 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | lazy_static! { 7 | static ref GRAPH_ACQUIRE_LOCK: Arc> = Arc::new(Mutex::new(false)); 8 | } 9 | 10 | fn main() { 11 | let _tmp = GRAPH_ACQUIRE_LOCK.lock().unwrap(); 12 | let _tmp2 = GRAPH_ACQUIRE_LOCK.lock().unwrap(); 13 | } 14 | -------------------------------------------------------------------------------- /toys/tikv-wrapper/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "tikv-wrapper" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /toys/tikv-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tikv-wrapper" 3 | version = "0.1.0" 4 | authors = ["BurtonQin "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /toys/tikv-wrapper/src/main.rs: -------------------------------------------------------------------------------- 1 | mod util; 2 | use std::sync::{Arc, RwLock}; 3 | use util::HandyRwLock; 4 | 5 | struct Foo { 6 | inner: Arc::>, 7 | data: i32, 8 | } 9 | 10 | impl Foo { 11 | fn new() -> Self { 12 | Self { 13 | inner: Arc::new(RwLock::new(1)), 14 | data: 1, 15 | } 16 | } 17 | fn foo(&self) { 18 | match *self.inner.rl() { 19 | 1 => *self.inner.wl() += 1, 20 | _ => {} 21 | }; 22 | } 23 | fn bar(&self) -> i32 { 24 | self.data 25 | } 26 | } 27 | 28 | fn main() { 29 | let f = Foo::new(); 30 | f.foo(); 31 | f.bar(); 32 | } 33 | -------------------------------------------------------------------------------- /toys/tikv-wrapper/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{RwLock, RwLockWriteGuard, RwLockReadGuard}; 2 | 3 | pub trait HandyRwLock { 4 | fn wl(&self) -> RwLockWriteGuard<'_, T>; 5 | fn rl(&self) -> RwLockReadGuard<'_, T>; 6 | } 7 | 8 | impl HandyRwLock for RwLock { 9 | fn wl(&self) -> RwLockWriteGuard<'_, T> { 10 | self.write().unwrap() 11 | } 12 | 13 | fn rl(&self) -> RwLockReadGuard<'_, T> { 14 | self.read().unwrap() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /toys/use-after-free/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "libc" 7 | version = "0.2.137" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 10 | 11 | [[package]] 12 | name = "use-after-free" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "libc", 16 | ] 17 | -------------------------------------------------------------------------------- /toys/use-after-free/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use-after-free" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | libc = "0.2.137" 10 | -------------------------------------------------------------------------------- /toys/use-after-free/src/main.rs: -------------------------------------------------------------------------------- 1 | fn drop_in_match() { 2 | fn create_obj(i: i32) -> Option> { 3 | if i > 10 { 4 | Some(Vec::new()) 5 | } else { 6 | None 7 | } 8 | } 9 | let ptr = match create_obj(11) { 10 | Some(mut v) => v.as_mut_ptr(), 11 | None => std::ptr::null_mut(), 12 | }; 13 | unsafe { 14 | if !ptr.is_null() { 15 | println!("{}", *ptr); 16 | } 17 | } 18 | } 19 | fn escape_to_param() { 20 | use std::ptr; 21 | use std::sync::atomic::{AtomicPtr, Ordering}; 22 | struct Owned { 23 | data: T, 24 | } 25 | impl Owned { 26 | fn as_raw(&self) -> *mut T { 27 | &self.data as *const _ as *mut _ 28 | } 29 | } 30 | fn opt_owned_as_raw(val: &Option>) -> *mut T { 31 | val.as_ref().map(Owned::as_raw).unwrap_or(ptr::null_mut()) 32 | } 33 | struct Obj { 34 | ptr: AtomicPtr, 35 | } 36 | impl Obj { 37 | fn null() -> Self { 38 | Obj { 39 | ptr: AtomicPtr::new(ptr::null_mut()), 40 | } 41 | } 42 | fn load(&self, ord: Ordering) -> *mut T { 43 | self.ptr.load(ord) 44 | } 45 | fn store(&self, owned: Option>, ord: Ordering) { 46 | self.ptr.store(opt_owned_as_raw(&owned), ord); 47 | } 48 | } 49 | let o = Obj::>::null(); 50 | let owned = Some(Owned { data: Vec::new() }); 51 | o.store(owned, Ordering::Relaxed); 52 | let p = o.load(Ordering::Relaxed); 53 | unsafe { 54 | println!("{:?}", *p); 55 | } 56 | } 57 | 58 | fn escape_to_global() { 59 | use std::os::raw::{c_char, c_int}; 60 | use std::ptr; 61 | #[repr(C)] 62 | pub struct hostent { 63 | h_name: *mut c_char, 64 | h_aliases: *mut *mut c_char, 65 | h_addrtype: c_int, 66 | h_length: c_int, 67 | h_addr_list: *mut *mut c_char, 68 | } 69 | 70 | static mut HOST_ENTRY: hostent = hostent { 71 | h_name: ptr::null_mut(), 72 | h_aliases: ptr::null_mut(), 73 | h_addrtype: 0, 74 | h_length: 0, 75 | h_addr_list: ptr::null_mut(), 76 | }; 77 | 78 | static mut HOST_NAME: Option> = None; 79 | static mut HOST_ALIASES: Option>> = None; 80 | 81 | pub unsafe extern "C" fn gethostent() -> *const hostent { 82 | HOST_ALIASES = Some(vec![vec![0, 1, 2], vec![3, 4, 5]]); 83 | let mut host_aliases: Vec<*mut i8> = HOST_ALIASES 84 | .as_mut() 85 | .unwrap() 86 | .iter_mut() 87 | .map(|x| x.as_mut_ptr() as *mut i8) 88 | .collect(); 89 | host_aliases.push(ptr::null_mut()); 90 | host_aliases.push(ptr::null_mut()); 91 | 92 | HOST_NAME = Some(vec![0, 1, 2]); 93 | 94 | HOST_ENTRY = hostent { 95 | h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char, 96 | h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, 97 | h_addrtype: 0, 98 | h_length: 4, 99 | h_addr_list: ptr::null_mut(), 100 | }; 101 | &HOST_ENTRY as *const hostent 102 | } 103 | 104 | unsafe { 105 | let h = gethostent(); 106 | println!("{:?}", *(&*h).h_aliases); 107 | } 108 | } 109 | 110 | use libc::c_char; 111 | use std::ffi::CStr; 112 | 113 | unsafe fn fmt_time(date: &Date) -> *const c_char { 114 | let days = vec!["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 115 | let months = vec![ 116 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 117 | ]; 118 | let year = 1900 + date.tm_year; 119 | 120 | let time_str = format!( 121 | "{} {} {:2} {:02}:{:02}:{:02} {:4}\n\0\0\0\0\0\0\0\0\0\0\0\0\0", 122 | days[date.tm_wday as usize], 123 | months[date.tm_mon as usize], 124 | date.tm_mday, 125 | date.tm_hour, 126 | date.tm_min, 127 | date.tm_sec, 128 | year 129 | ); 130 | time_str[0..26].as_ptr() as _ 131 | } 132 | 133 | struct Date { 134 | tm_year: usize, 135 | tm_wday: usize, 136 | tm_mon: usize, 137 | tm_mday: usize, 138 | tm_hour: usize, 139 | tm_min: usize, 140 | tm_sec: usize, 141 | } 142 | 143 | fn escape_to_return() { 144 | let date = Date { 145 | tm_year: 1, 146 | tm_wday: 1, 147 | tm_mon: 1, 148 | tm_mday: 1, 149 | tm_hour: 1, 150 | tm_min: 1, 151 | tm_sec: 1, 152 | }; 153 | unsafe { 154 | let ptr = fmt_time(&date); 155 | println!("{:?}", CStr::from_ptr(ptr)); 156 | } 157 | } 158 | 159 | fn main() { 160 | drop_in_match(); 161 | escape_to_param(); 162 | escape_to_global(); 163 | escape_to_return(); 164 | } 165 | -------------------------------------------------------------------------------- /toys/wait-lock-no-deadlock/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "wait-lock-no-deadlock" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /toys/wait-lock-no-deadlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wait-lock-no-deadlock" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /toys/wait-lock-no-deadlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | use std::sync::MutexGuard; 3 | 4 | fn wait(a: MutexGuard) -> MutexGuard { 5 | a 6 | } 7 | 8 | fn main() { 9 | let mu = Mutex::new(0); 10 | let mut lg = mu.lock().unwrap(); 11 | lg = wait(lg); 12 | std::mem::drop(lg); 13 | } 14 | --------------------------------------------------------------------------------