├── docs ├── .unified_annotations.md.swp └── index.md ├── .gitignore ├── examples ├── debug_refs.cpp ├── cmake_project │ ├── .gitignore │ ├── src │ │ ├── safe_string.cpp │ │ ├── utils.cpp │ │ ├── main.cpp │ │ ├── memory_manager.cpp │ │ └── simple_demo.cpp │ ├── include │ │ └── safe_string.h │ └── CMakeLists.txt ├── debug_mixed_refs.cpp ├── debug_const_refs.cpp ├── simple_test.cpp ├── test_debug.cpp ├── simple_lifetime_test.cpp ├── include_test │ ├── external │ │ └── libs │ │ │ └── utils.h │ ├── src │ │ ├── local_header.h │ │ ├── test_env.cpp │ │ └── main.cpp │ ├── compile_commands.json │ └── include │ │ └── math_utils.h ├── test_debug_move.cpp ├── lifetime_demo.h ├── transitive.h ├── simple_header_safety │ ├── math.h │ └── math.cpp ├── test_rc_rejected.cpp ├── simple_header.h ├── lifetime_test.h ├── third_party_check.cpp ├── test_compositional_safety.cpp ├── string_utils.h ├── simple_cross_file.cpp ├── send_trait_error_test.cpp ├── test_simple_refs.cpp ├── lifetime_demo.cpp ├── safe_file_example.cpp ├── safe_unsafe_simple.cpp ├── test_lifetimes.cpp ├── header_safety_example │ ├── safe_math.h │ └── safe_math.cpp ├── unsafe_blocks.cpp ├── simple_safety_demo.cpp ├── submodule_project │ ├── setup.sh │ └── CMakeLists.txt ├── test_simple_move.cpp ├── test_rusty_move.cpp ├── test_each_control_flow.cpp ├── test_dangling.cpp ├── test_unique_ptr.cpp ├── simple_const_ref_example.cpp ├── send_hybrid_debug.cpp ├── test_transitive.cpp ├── test_cross_file.cpp ├── test_loop_simple.cpp ├── scope_fix_demo.cpp ├── safety_annotation_demo.cpp ├── unsafe_comment_example.cpp ├── test_std_move.cpp ├── reference_demo.cpp ├── mpsc_demo.cpp ├── test_references.cpp ├── test_unsafe_propagation.cpp ├── unified_annotations.cpp ├── test_scope_tracking.cpp ├── unsafe_block_example.cpp ├── test_control_flow_bugs.cpp ├── safe_unsafe_demo.cpp ├── test_arc_ffi.cpp ├── undeclared_calling_demo.cpp ├── test_option_as_ref.cpp └── std_safety_demo.cpp ├── test.sh ├── dist └── cpp-borrow-checker-Darwin-arm64 │ ├── cpp-borrow-checker │ ├── cpp-borrow-checker-standalone │ ├── install.sh │ └── README.md ├── src ├── lib.rs ├── debug_macros.rs ├── solver │ └── mod.rs ├── analysis │ ├── ownership.rs │ ├── lifetimes.rs │ └── borrows.rs └── parser │ └── template_context.rs ├── tests ├── run_cpp_tests.sh ├── raii │ ├── partial_move_whole_struct_test.cpp │ ├── partial_move_detailed_test.cpp │ ├── partial_move_test.cpp │ ├── partial_move_nested_fields.cpp │ ├── partial_borrow_test.cpp │ ├── return_ref_to_local.cpp │ ├── member_outlives_object.cpp │ ├── partial_borrow_nested_test.cpp │ ├── temporary_lifetime.cpp │ └── double_free.cpp ├── run_rusty_tests.sh ├── test_scope_tracking.rs ├── test_std_move.rs ├── test_stl_lifetime_enforcement.rs ├── test_this_tracking.rs ├── test_undeclared_can_call_undeclared.rs └── rusty_box_test.cpp ├── run_tests.sh ├── include └── rusty │ ├── weak.hpp │ ├── send_impls.hpp │ ├── send_trait.hpp │ ├── barrier.hpp │ ├── rc │ └── weak.hpp │ ├── rusty.hpp │ ├── cell.hpp │ ├── std_minimal.hpp │ ├── sync │ └── weak.hpp │ ├── once.hpp │ ├── box.hpp │ └── unsafe_cell.hpp ├── LICENSE ├── Cargo.toml ├── .cargo └── config.toml └── CMakeLists.txt /docs/.unified_annotations.md.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuaimu/rusty-cpp/HEAD/docs/.unified_annotations.md.swp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | *.out 4 | *.swp 5 | tests/build/ 6 | build/* 7 | *.lock 8 | 9 | build-tests/ 10 | -------------------------------------------------------------------------------- /examples/debug_refs.cpp: -------------------------------------------------------------------------------- 1 | void test() { 2 | int x = 10; 3 | int& ref1 = x; 4 | int& ref2 = x; // Should fail 5 | } -------------------------------------------------------------------------------- /examples/cmake_project/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.o 3 | *.a 4 | *.so 5 | *.dylib 6 | compile_commands.json 7 | CMakeCache.txt 8 | CMakeFiles/ -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | Z3_SYS_Z3_HEADER=/opt/homebrew/include/z3.h DYLD_LIBRARY_PATH=/opt/homebrew/Cellar/llvm/19.1.7/lib:$DYLD_LIBRARY_PATH cargo test 2 | -------------------------------------------------------------------------------- /dist/cpp-borrow-checker-Darwin-arm64/cpp-borrow-checker: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuaimu/rusty-cpp/HEAD/dist/cpp-borrow-checker-Darwin-arm64/cpp-borrow-checker -------------------------------------------------------------------------------- /examples/debug_mixed_refs.cpp: -------------------------------------------------------------------------------- 1 | void test() { 2 | int x = 10; 3 | const int& ref1 = x; // Immutable borrow 4 | int& mut_ref = x; // Mutable borrow - should fail 5 | } -------------------------------------------------------------------------------- /examples/debug_const_refs.cpp: -------------------------------------------------------------------------------- 1 | void test() { 2 | int x = 10; 3 | const int& ref1 = x; 4 | const int& ref2 = x; // Should be OK 5 | const int& ref3 = x; // Should be OK 6 | } -------------------------------------------------------------------------------- /examples/simple_test.cpp: -------------------------------------------------------------------------------- 1 | void test() { 2 | int value = 42; 3 | int* ptr = &value; 4 | *ptr = 10; 5 | } 6 | 7 | int main() { 8 | test(); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /examples/test_debug.cpp: -------------------------------------------------------------------------------- 1 | void test() { 2 | int x = 10; 3 | const int& ref1 = x; 4 | int& ref2 = x; // Should fail 5 | } 6 | 7 | int main() { 8 | test(); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /examples/simple_lifetime_test.cpp: -------------------------------------------------------------------------------- 1 | void test_simple() { 2 | int x = 10; 3 | int& ref = x; // Mutable borrow 4 | 5 | // This should error - can't have immutable while mutable exists 6 | const int& const_ref = x; 7 | } -------------------------------------------------------------------------------- /examples/include_test/external/libs/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | // @lifetime: (&'a) -> &'a 5 | const char* echo(const char* str); 6 | 7 | // @lifetime: owned 8 | int compute(int x, int y); 9 | 10 | #endif // UTILS_H -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Library crate for rusty-cpp 2 | // Exposes modules for integration testing 3 | 4 | #[macro_use] 5 | pub mod debug_macros; 6 | 7 | pub mod parser; 8 | pub mod ir; 9 | pub mod analysis; 10 | pub mod solver; 11 | pub mod diagnostics; 12 | -------------------------------------------------------------------------------- /examples/include_test/src/local_header.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCAL_HEADER_H 2 | #define LOCAL_HEADER_H 3 | 4 | // @lifetime: &'a 5 | const char* getLocalString(); 6 | 7 | // @lifetime: (&'a) -> &'a 8 | const int& passThrough(const int& val); 9 | 10 | #endif // LOCAL_HEADER_H -------------------------------------------------------------------------------- /src/debug_macros.rs: -------------------------------------------------------------------------------- 1 | // Macro for debug logging - enabled via RUSTY_CPP_DEBUG env var 2 | #[macro_export] 3 | macro_rules! debug_println { 4 | ($($arg:tt)*) => { 5 | if std::env::var("RUSTY_CPP_DEBUG").is_ok() { 6 | eprintln!($($arg)*); 7 | } 8 | }; 9 | } -------------------------------------------------------------------------------- /examples/include_test/compile_commands.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "directory": "/Users/shuai/workspace/rusty/examples/include_test", 4 | "file": "src/main.cpp", 5 | "command": "clang++ -std=c++17 -I/Users/shuai/workspace/rusty/examples/include_test/include -Iexternal/libs -c src/main.cpp" 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/run_cpp_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 5 | BUILD_DIR="$ROOT_DIR/build-tests" 6 | 7 | cmake -S "$ROOT_DIR" -B "$BUILD_DIR" 8 | cmake --build "$BUILD_DIR" 9 | ctest --test-dir "$BUILD_DIR" --output-on-failure 10 | -------------------------------------------------------------------------------- /examples/include_test/include/math_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_UTILS_H 2 | #define MATH_UTILS_H 3 | 4 | // @lifetime: (&'a, &'b) -> &'a 5 | const int& max(const int& a, const int& b); 6 | 7 | // @lifetime: owned 8 | int square(int x); 9 | 10 | // @lifetime: &'a mut 11 | int& increment(int& value); 12 | 13 | #endif // MATH_UTILS_H -------------------------------------------------------------------------------- /examples/test_debug_move.cpp: -------------------------------------------------------------------------------- 1 | // Simple test to debug std::move detection 2 | namespace std { 3 | template 4 | T&& move(T& t); 5 | } 6 | 7 | void test() { 8 | int x = 42; 9 | int y = std::move(x); // Should be detected as a move 10 | 11 | // Use after move 12 | int z = x; // Should error 13 | } -------------------------------------------------------------------------------- /examples/lifetime_demo.h: -------------------------------------------------------------------------------- 1 | #ifndef LIFETIME_DEMO_H 2 | #define LIFETIME_DEMO_H 3 | 4 | // @lifetime: owned 5 | int getValue(); 6 | 7 | // @lifetime: &'static 8 | const int& getStaticRef(); 9 | 10 | // @lifetime: (&'a) -> &'a 11 | const int& identity(const int& x); 12 | 13 | // @lifetime: owned 14 | int* createInt(); 15 | 16 | #endif // LIFETIME_DEMO_H -------------------------------------------------------------------------------- /examples/transitive.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSITIVE_H 2 | #define TRANSITIVE_H 3 | 4 | // @lifetime: (&'a, &'b) -> &'a where 'a: 'b 5 | const int& requires_outlives(const int& longer, const int& shorter); 6 | 7 | // @lifetime: (&'a, &'b, &'c) -> &'a where 'a: 'b, 'b: 'c 8 | const int& requires_transitive(const int& a, const int& b, const int& c); 9 | 10 | #endif // TRANSITIVE_H -------------------------------------------------------------------------------- /examples/simple_header_safety/math.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_H 2 | #define MATH_H 3 | 4 | // Example demonstrating header safety annotations propagating to implementations 5 | 6 | // @safe 7 | int safe_add(int a, int b); 8 | 9 | // @unsafe 10 | int unsafe_divide(int a, int b); 11 | 12 | // No annotation - defaults to unsafe 13 | int regular_multiply(int a, int b); 14 | 15 | #endif -------------------------------------------------------------------------------- /examples/test_rc_rejected.cpp: -------------------------------------------------------------------------------- 1 | // This file should FAIL to compile 2 | // Demonstrates that Rc is correctly rejected 3 | 4 | #include "rusty/sync/mpsc.hpp" 5 | #include "rusty/rc.hpp" 6 | 7 | int main() { 8 | // This should cause a compile error: 9 | // "Channel type T must be Send (marked explicitly)" 10 | auto [tx, rx] = rusty::sync::mpsc::channel>(); 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script to run tests with required environment variables 3 | 4 | # Set Z3 header path for macOS (Homebrew installation) 5 | export Z3_SYS_Z3_HEADER=/opt/homebrew/include/z3.h 6 | 7 | # Set LLVM/Clang library path for macOS 8 | export DYLD_LIBRARY_PATH=/opt/homebrew/Cellar/llvm/19.1.7/lib:$DYLD_LIBRARY_PATH 9 | 10 | # Run cargo test with all required environment variables 11 | cargo test "$@" -------------------------------------------------------------------------------- /examples/simple_header.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_HEADER_H 2 | #define SIMPLE_HEADER_H 3 | 4 | // @lifetime: &'a 5 | const int& getGlobalRef(); 6 | 7 | // @lifetime: (&'a) -> &'a 8 | const int& identity(const int& x); 9 | 10 | // @lifetime: owned 11 | int getValue(); 12 | 13 | // @lifetime: &'a mut 14 | int& getMutableRef(); 15 | 16 | // @lifetime: (&'a, &'b) -> &'a where 'a: 'b 17 | const int& selectFirst(const int& a, const int& b); 18 | 19 | #endif // SIMPLE_HEADER_H -------------------------------------------------------------------------------- /examples/lifetime_test.h: -------------------------------------------------------------------------------- 1 | #ifndef LIFETIME_TEST_H 2 | #define LIFETIME_TEST_H 3 | 4 | // @lifetime: &'static 5 | const int& getRef(); 6 | 7 | // @lifetime: (&'a) -> &'a 8 | const int& identity(const int& x); 9 | 10 | // @lifetime: (&'a, &'b) -> &'a where 'a: 'b 11 | const int& selectFirst(const int& first, const int& second); 12 | 13 | // @lifetime: owned 14 | int getValue(); 15 | 16 | // @lifetime: &'local 17 | const int& returnLocal(); 18 | 19 | #endif // LIFETIME_TEST_H -------------------------------------------------------------------------------- /dist/cpp-borrow-checker-Darwin-arm64/cpp-borrow-checker-standalone: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Standalone wrapper for cpp-borrow-checker 3 | 4 | # Get the directory where this script is located 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | # Set library paths 8 | export DYLD_LIBRARY_PATH="/opt/homebrew/Cellar/llvm/19.1.7/lib:/opt/homebrew/lib:/usr/local/lib:$DYLD_LIBRARY_PATH" 9 | 10 | # Run the actual binary 11 | exec "$SCRIPT_DIR/cpp-borrow-checker" "$@" 12 | -------------------------------------------------------------------------------- /examples/third_party_check.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class ThirdPartyType { 5 | public: 6 | ThirdPartyType() = default; 7 | ThirdPartyType(ThirdPartyType&&) = default; 8 | }; 9 | 10 | int main() { 11 | std::cout << "is_move_constructible: " << std::is_move_constructible_v << "\n"; 12 | std::cout << "is_move_assignable: " << std::is_move_assignable_v << "\n"; 13 | std::cout << "is_destructible: " << std::is_destructible_v << "\n"; 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /examples/test_compositional_safety.cpp: -------------------------------------------------------------------------------- 1 | // Demonstrates compositional safety: 2 | // struct { Rc } is automatically rejected (no marker) 3 | 4 | #include "rusty/sync/mpsc.hpp" 5 | #include "rusty/rc.hpp" 6 | 7 | struct ContainsRc { 8 | rusty::Rc data; 9 | }; 10 | 11 | int main() { 12 | // This should FAIL to compile: 13 | // ContainsRc is NOT marked as Send (lacks static constexpr bool is_send = true) 14 | // Even though it's technically movable 15 | auto [tx, rx] = rusty::sync::mpsc::channel(); 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /dist/cpp-borrow-checker-Darwin-arm64/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Installer for cpp-borrow-checker 3 | 4 | INSTALL_DIR="/usr/local/bin" 5 | 6 | echo "Installing cpp-borrow-checker to $INSTALL_DIR..." 7 | 8 | # Check for required dependencies 9 | if ! command -v clang &> /dev/null; then 10 | echo "Warning: clang not found. Please install LLVM/Clang:" 11 | echo " brew install llvm" 12 | fi 13 | 14 | # Copy the binary 15 | sudo cp cpp-borrow-checker-standalone "$INSTALL_DIR/cpp-borrow-checker" 16 | sudo chmod +x "$INSTALL_DIR/cpp-borrow-checker" 17 | 18 | echo "Installation complete!" 19 | echo "You can now run: cpp-borrow-checker " 20 | -------------------------------------------------------------------------------- /include/rusty/weak.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_WEAK_HPP 2 | #define RUSTY_WEAK_HPP 3 | 4 | #include "rc/weak.hpp" 5 | #include "sync/weak.hpp" 6 | 7 | namespace rusty { 8 | 9 | // Back-compat alias matching older API defaults 10 | template 11 | using Weak = rc::Weak; 12 | 13 | // Convenience downgrade helpers in root namespace 14 | template 15 | auto downgrade(const Rc& rc) -> rc::Weak { 16 | return rc::downgrade(rc); 17 | } 18 | 19 | template 20 | auto downgrade(const Arc& arc) -> sync::Weak { 21 | return sync::downgrade(arc); 22 | } 23 | 24 | } // namespace rusty 25 | 26 | #endif // RUSTY_WEAK_HPP 27 | -------------------------------------------------------------------------------- /examples/string_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef STRING_UTILS_H 2 | #define STRING_UTILS_H 3 | 4 | #include 5 | 6 | // @lifetime: &'a 7 | const std::string& getStaticString(); 8 | 9 | // @lifetime: (&'a) -> &'a 10 | const std::string& identity(const std::string& str); 11 | 12 | // @lifetime: (&'a, &'b) -> &'a where 'a: 'b 13 | const std::string& selectFirst(const std::string& first, const std::string& second); 14 | 15 | // @lifetime: owned 16 | std::string copyString(const std::string& str); 17 | 18 | // @lifetime: &'a mut 19 | std::string& getMutableString(); 20 | 21 | // @lifetime: (&'a mut) -> &'a mut 22 | std::string& modifyString(std::string& str); 23 | 24 | #endif // STRING_UTILS_H -------------------------------------------------------------------------------- /examples/include_test/src/test_env.cpp: -------------------------------------------------------------------------------- 1 | // Local header - found relative to source 2 | #include "local_header.h" 3 | 4 | // These should be found via environment variables 5 | #include 6 | #include 7 | 8 | void test_env_includes() { 9 | int x = 100; 10 | 11 | // From math_utils.h (first env path) 12 | int& ref = increment(x); 13 | 14 | // From utils.h (second env path) 15 | const char* msg = "hello"; 16 | const char* echoed = echo(msg); 17 | 18 | // Test borrow checking 19 | const int& const_ref = x; 20 | int& mut_ref = x; // ERROR: x already has immutable borrow 21 | } 22 | 23 | int main() { 24 | test_env_includes(); 25 | return 0; 26 | } -------------------------------------------------------------------------------- /examples/simple_cross_file.cpp: -------------------------------------------------------------------------------- 1 | #include "simple_header.h" 2 | 3 | void test_with_header() { 4 | int value = 42; 5 | 6 | // Multiple immutable borrows - OK 7 | const int& ref1 = value; 8 | const int& ref2 = value; 9 | 10 | // This would be an error - can't have mutable while immutable exists 11 | // int& mut_ref = value; 12 | } 13 | 14 | void test_mutable_conflicts() { 15 | int data = 100; 16 | 17 | // Mutable borrow 18 | int& mut_ref = data; 19 | 20 | // This is an error - can't have immutable while mutable exists 21 | const int& const_ref = data; 22 | } 23 | 24 | int main() { 25 | test_with_header(); 26 | test_mutable_conflicts(); 27 | return 0; 28 | } -------------------------------------------------------------------------------- /tests/raii/partial_move_whole_struct_test.cpp: -------------------------------------------------------------------------------- 1 | // Test: Moving whole struct after partial move 2 | // This should be detected as an error 3 | 4 | #include 5 | #include 6 | 7 | struct Pair { 8 | std::string first; 9 | std::string second; 10 | }; 11 | 12 | // TEST: Move whole struct after moving a field - should ERROR 13 | // @safe 14 | void test_whole_struct_move_after_partial() { 15 | Pair p; 16 | p.first = "hello"; 17 | p.second = "world"; 18 | 19 | std::string x = std::move(p.first); // Move just p.first 20 | 21 | // This should be an error - p is partially moved 22 | Pair p2 = std::move(p); // ERROR: Cannot move p because p.first already moved 23 | } 24 | 25 | int main() { return 0; } 26 | -------------------------------------------------------------------------------- /examples/send_trait_error_test.cpp: -------------------------------------------------------------------------------- 1 | // This file demonstrates compile-time error when violating Send constraint 2 | // Expected to FAIL compilation with clear error message 3 | 4 | #include "rusty/sync/mpsc.hpp" 5 | 6 | // Type that is NOT Send (not move-constructible) 7 | struct NotSend { 8 | int value; 9 | 10 | NotSend(int v) : value(v) {} 11 | 12 | // Deleted move operations - NOT Send! 13 | NotSend(NotSend&&) = delete; 14 | NotSend& operator=(NotSend&&) = delete; 15 | }; 16 | 17 | int main() { 18 | // This should cause a compile error with message: 19 | // "Channel type T must be move-constructible (like Rust's Send trait)" 20 | auto [tx, rx] = rusty::sync::mpsc::channel(); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /examples/simple_header_safety/math.cpp: -------------------------------------------------------------------------------- 1 | #include "math.h" 2 | 3 | // Implementation of safe_add - inherits @safe from header 4 | int safe_add(int a, int b) { 5 | // This would be an error: pointer operations in safe function 6 | int* ptr = &a; 7 | return a + b; 8 | } 9 | 10 | // Implementation of unsafe_divide - inherits @unsafe from header 11 | int unsafe_divide(int a, int b) { 12 | if (b == 0) { 13 | // Pointer operations allowed in unsafe function 14 | int* error = nullptr; 15 | return *error; 16 | } 17 | return a / b; 18 | } 19 | 20 | // Implementation of regular_multiply - no annotation means unsafe 21 | int regular_multiply(int a, int b) { 22 | // Pointer operations allowed - unsafe by default 23 | int result = a * b; 24 | int* ptr = &result; 25 | return *ptr; 26 | } -------------------------------------------------------------------------------- /examples/include_test/src/main.cpp: -------------------------------------------------------------------------------- 1 | // Test quoted include (searches relative to source first) 2 | #include "local_header.h" 3 | 4 | // Test angle bracket include (searches -I paths) 5 | #include 6 | 7 | void test_includes() { 8 | int a = 10; 9 | int b = 20; 10 | 11 | // Test function from math_utils.h (found via -I) 12 | const int& max_val = max(a, b); 13 | 14 | // Test function from local_header.h (found relative to source) 15 | const int& passed = passThrough(a); 16 | 17 | // Test borrow checking 18 | const int& ref1 = a; 19 | const int& ref2 = a; // OK - multiple immutable borrows 20 | 21 | // This would be an error 22 | int& mut_ref = a; // ERROR: can't have mutable while immutable exists 23 | } 24 | 25 | int main() { 26 | test_includes(); 27 | return 0; 28 | } -------------------------------------------------------------------------------- /examples/cmake_project/src/safe_string.cpp: -------------------------------------------------------------------------------- 1 | #include "safe_string.h" 2 | #include 3 | 4 | // @safe 5 | namespace safe { 6 | 7 | SafeString::SafeString(const char* str) 8 | : data(std::make_unique(str)) { 9 | } 10 | 11 | SafeString::~SafeString() = default; 12 | 13 | // @safe 14 | const std::string& SafeString::get() const { 15 | return *data; 16 | } 17 | 18 | // @safe 19 | std::unique_ptr SafeString::release() { 20 | return std::move(data); // Transfer ownership 21 | } 22 | 23 | // @safe 24 | SafeString::SafeString(SafeString&& other) noexcept 25 | : data(std::move(other.data)) { 26 | } 27 | 28 | // @safe 29 | SafeString& SafeString::operator=(SafeString&& other) noexcept { 30 | if (this != &other) { 31 | data = std::move(other.data); 32 | } 33 | return *this; 34 | } 35 | 36 | } // namespace safe -------------------------------------------------------------------------------- /examples/test_simple_refs.cpp: -------------------------------------------------------------------------------- 1 | // Simple test for reference borrow checking 2 | 3 | void test_multiple_const_refs() { 4 | int value = 42; 5 | const int& ref1 = value; // First immutable borrow 6 | const int& ref2 = value; // Second immutable borrow - should be OK 7 | } 8 | 9 | void test_mutable_and_const_conflict() { 10 | int value = 42; 11 | int& mut_ref = value; // Mutable borrow 12 | const int& const_ref = value; // Immutable borrow while mutable exists - ERROR 13 | } 14 | 15 | void test_multiple_mutable_refs() { 16 | int value = 42; 17 | int& mut_ref1 = value; // First mutable borrow 18 | int& mut_ref2 = value; // Second mutable borrow - ERROR 19 | } 20 | 21 | int main() { 22 | test_multiple_const_refs(); 23 | test_mutable_and_const_conflict(); 24 | test_multiple_mutable_refs(); 25 | return 0; 26 | } -------------------------------------------------------------------------------- /examples/lifetime_demo.cpp: -------------------------------------------------------------------------------- 1 | #include "lifetime_demo.h" 2 | 3 | void test_lifetime_rules() { 4 | int local = 42; 5 | 6 | // Test 1: Function returning owned value - OK 7 | int owned = getValue(); 8 | 9 | // Test 2: Function returning reference - OK 10 | const int& ref = getStaticRef(); 11 | 12 | // Test 3: Identity function preserves lifetime - OK 13 | const int& ref2 = identity(ref); 14 | 15 | // Test 4: Function that transfers ownership - OK 16 | int* ptr = createInt(); 17 | delete ptr; 18 | 19 | // Test 5: Multiple borrows - should error 20 | int value = 100; 21 | const int& r1 = value; // Immutable borrow 22 | const int& r2 = value; // Another immutable - OK 23 | int& mut_ref = value; // ERROR: Can't have mutable while immutable exists 24 | } 25 | 26 | int main() { 27 | test_lifetime_rules(); 28 | return 0; 29 | } -------------------------------------------------------------------------------- /dist/cpp-borrow-checker-Darwin-arm64/README.md: -------------------------------------------------------------------------------- 1 | # C++ Borrow Checker - Standalone Release 2 | 3 | This is a standalone release of the C++ Borrow Checker. 4 | 5 | ## Installation 6 | 7 | ### Quick Install 8 | Run: `./install.sh` 9 | 10 | ### Manual Install 11 | Copy `cpp-borrow-checker-standalone` to your PATH: 12 | ```bash 13 | cp cpp-borrow-checker-standalone /usr/local/bin/cpp-borrow-checker 14 | ``` 15 | 16 | ## Requirements 17 | 18 | - LLVM/Clang libraries (usually already installed) 19 | - For macOS: `brew install llvm` 20 | - For Linux: `apt-get install llvm` or `yum install llvm` 21 | 22 | ## Usage 23 | 24 | ```bash 25 | cpp-borrow-checker 26 | ``` 27 | 28 | ## Troubleshooting 29 | 30 | If you get library not found errors: 31 | 1. Install LLVM: `brew install llvm` (macOS) or `apt-get install llvm` (Linux) 32 | 2. Use the wrapper script: `cpp-borrow-checker-standalone` instead of the raw binary 33 | 34 | -------------------------------------------------------------------------------- /examples/cmake_project/include/safe_string.h: -------------------------------------------------------------------------------- 1 | #ifndef SAFE_STRING_H 2 | #define SAFE_STRING_H 3 | 4 | #include 5 | #include 6 | 7 | // @safe 8 | namespace safe { 9 | 10 | // A string wrapper with explicit ownership 11 | class SafeString { 12 | private: 13 | std::unique_ptr data; 14 | 15 | public: 16 | SafeString(const char* str); 17 | ~SafeString(); 18 | 19 | // @lifetime: (&'a) -> &'a 20 | const std::string& get() const; 21 | 22 | // Transfer ownership 23 | std::unique_ptr release(); 24 | 25 | // No copy constructor (explicit ownership) 26 | SafeString(const SafeString&) = delete; 27 | SafeString& operator=(const SafeString&) = delete; 28 | 29 | // Move constructor 30 | SafeString(SafeString&& other) noexcept; 31 | SafeString& operator=(SafeString&& other) noexcept; 32 | }; 33 | 34 | } // namespace safe 35 | 36 | #endif // SAFE_STRING_H -------------------------------------------------------------------------------- /examples/safe_file_example.cpp: -------------------------------------------------------------------------------- 1 | // @safe 2 | // This entire file is checked for memory safety 3 | 4 | // All functions in this file are checked by default 5 | void checked_by_default() { 6 | int value = 42; 7 | int& ref1 = value; 8 | // int& ref2 = value; // ERROR: double mutable borrow 9 | ref1 = 100; 10 | } 11 | 12 | // Another checked function 13 | void also_checked() { 14 | int* ptr = new int(42); 15 | // delete ptr; 16 | // *ptr = 100; // Would be caught as use-after-free 17 | } 18 | 19 | // @unsafe 20 | // This function opts out of checking even in a safe file 21 | void performance_critical() { 22 | int* ptr = new int(42); 23 | delete ptr; 24 | // *ptr = 100; // Would be use-after-free but not caught (@unsafe) 25 | } 26 | 27 | // Back to safe by default 28 | void safe_again() { 29 | int value = 10; 30 | const int& cref = value; 31 | // int& mref = value; // ERROR: can't have mutable ref with const ref 32 | } -------------------------------------------------------------------------------- /examples/cmake_project/src/utils.cpp: -------------------------------------------------------------------------------- 1 | // Utility functions - mix of safe and unsafe code 2 | 3 | #include 4 | #include 5 | 6 | // Unsafe by default (no annotation) 7 | void sort_raw_array(int* arr, size_t size) { 8 | // Uses raw pointer - OK because function is unsafe by default 9 | std::sort(arr, arr + size); 10 | } 11 | 12 | // @safe 13 | namespace utils { 14 | 15 | // @safe 16 | void sort_vector(std::vector& vec) { 17 | std::sort(vec.begin(), vec.end()); 18 | // Safe - no raw pointers 19 | } 20 | 21 | // @safe 22 | int sum_vector(const std::vector& vec) { 23 | int total = 0; 24 | for (int val : vec) { 25 | total += val; 26 | } 27 | return total; 28 | } 29 | 30 | // @safe 31 | void unsafe_caller() { 32 | int arr[5] = {5, 2, 8, 1, 9}; 33 | // This should error - calling unsafe function from safe context 34 | sort_raw_array(arr, 5); // Error: requires unsafe annotation 35 | } 36 | } -------------------------------------------------------------------------------- /examples/safe_unsafe_simple.cpp: -------------------------------------------------------------------------------- 1 | // Example demonstrating safe/unsafe annotations 2 | // By default, C++ code is unsafe (no checking) 3 | // To make entire file safe, add: // @safe 4 | 5 | // Legacy function - not checked by default 6 | void legacy_code() { 7 | int* ptr = new int(42); 8 | int* alias = ptr; 9 | delete ptr; 10 | *alias = 100; // Use-after-free but NOT caught (unsafe by default) 11 | } 12 | 13 | // @safe 14 | // Modern function with safety checking 15 | void safe_code() { 16 | int value = 42; 17 | int& ref1 = value; 18 | // Uncomment to see error: 19 | // int& ref2 = value; // ERROR: double mutable borrow 20 | ref1 = 100; 21 | } 22 | 23 | // @unsafe 24 | // Explicitly unsafe even with --safe flag 25 | void performance_critical() { 26 | int* ptr = new int(42); 27 | delete ptr; 28 | *ptr = 100; // Use-after-free NOT caught (@unsafe) 29 | } 30 | 31 | /* 32 | Usage: 33 | Default: cargo run -- examples/safe_unsafe_simple.cpp 34 | Safe mode: cargo run -- --safe examples/safe_unsafe_simple.cpp 35 | */ -------------------------------------------------------------------------------- /examples/test_lifetimes.cpp: -------------------------------------------------------------------------------- 1 | #include "lifetime_test.h" 2 | 3 | void test_lifetime_violations() { 4 | int local = 42; 5 | 6 | // This should be OK - returning owned value 7 | int owned = getValue(); 8 | 9 | // This should be OK - lifetime 'a preserved 10 | const int& ref1 = getRef(); 11 | const int& ref2 = identity(ref1); 12 | 13 | // This would violate lifetime constraint 'a: 'b 14 | // selectFirst requires first argument to outlive second 15 | const int& short_lived = local; 16 | const int& long_lived = getRef(); 17 | // const int& bad = selectFirst(short_lived, long_lived); // ERROR: lifetime violation 18 | 19 | // Returning reference to local - should be caught 20 | const int& dangling = returnLocal(); // This should error 21 | } 22 | 23 | // Function that returns reference to local variable 24 | const int& returnLocal() { 25 | int local = 100; 26 | return local; // ERROR: returning reference to local 27 | } 28 | 29 | int main() { 30 | test_lifetime_violations(); 31 | return 0; 32 | } -------------------------------------------------------------------------------- /examples/header_safety_example/safe_math.h: -------------------------------------------------------------------------------- 1 | #ifndef SAFE_MATH_H 2 | #define SAFE_MATH_H 3 | 4 | // This header demonstrates how safety annotations propagate from headers to implementations 5 | 6 | // @safe 7 | namespace SafeMath { 8 | // All functions in this namespace are safe by default 9 | int add(int a, int b); 10 | int subtract(int a, int b); 11 | int multiply(int a, int b); 12 | 13 | // @unsafe 14 | int divide(int a, int b); // Unsafe due to potential division by zero 15 | } 16 | 17 | // Functions outside the safe namespace 18 | 19 | // @safe 20 | class SafeCalculator { 21 | public: 22 | void set_value(int val); 23 | int get_value() const; 24 | void increment(); 25 | 26 | // @unsafe 27 | void raw_pointer_operation(); 28 | 29 | private: 30 | int value; 31 | }; 32 | 33 | // Individual function annotations 34 | 35 | // @safe 36 | int safe_factorial(int n); 37 | 38 | // @unsafe 39 | void unsafe_memory_operation(void* ptr); 40 | 41 | // No annotation - defaults to unsafe 42 | void regular_function(); 43 | 44 | #endif // SAFE_MATH_H -------------------------------------------------------------------------------- /examples/unsafe_blocks.cpp: -------------------------------------------------------------------------------- 1 | // @safe 2 | // Example showing unsafe blocks within safe functions 3 | 4 | // Define UNSAFE macro for marking unsafe blocks 5 | #define UNSAFE if(true) 6 | 7 | void safe_function_with_unsafe_block() { 8 | int value = 42; 9 | int& ref = value; 10 | ref = 100; // This is checked 11 | 12 | // Unsafe block - checks are disabled here 13 | UNSAFE { 14 | int* ptr = &value; 15 | int* alias = ptr; // Multiple aliases allowed in unsafe 16 | *alias = 200; 17 | 18 | // Even use-after-free is not caught in unsafe block 19 | int* temp = new int(42); 20 | delete temp; 21 | *temp = 300; // Not caught 22 | } 23 | 24 | // Back to safe code 25 | // int& ref2 = value; // ERROR: Would be caught (double borrow) 26 | } 27 | 28 | // Another approach: using comments (future enhancement) 29 | void future_syntax() { 30 | int value = 42; 31 | 32 | // @unsafe { 33 | // int* ptr = &value; 34 | // // unchecked code 35 | // } 36 | 37 | int& ref = value; // checked 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Shuai Mu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /include/rusty/send_impls.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "send_trait.hpp" 4 | 5 | // Send implementations for rusty types 6 | // Mark which rusty types are thread-safe to send 7 | 8 | namespace rusty { 9 | 10 | // Forward declarations 11 | template class Box; 12 | template class Arc; 13 | template class Rc; // NOT Send! 14 | template class Vec; 15 | template class Option; 16 | template class Result; 17 | 18 | // Note: Most rusty types (Box, Arc, Rc, Mutex, Cell, RefCell) are already 19 | // handled in traits.hpp. This file provides additional specializations 20 | // for container types. 21 | 22 | // Vec is Send if T is Send 23 | template 24 | struct is_send> : is_send {}; 25 | 26 | // Option is Send if T is Send 27 | template 28 | struct is_send> : is_send {}; 29 | 30 | // Result is Send if both T and E are Send 31 | template 32 | struct is_send> : std::bool_constant< 33 | is_send::value && is_send::value 34 | > {}; 35 | 36 | } // namespace rusty 37 | -------------------------------------------------------------------------------- /examples/simple_safety_demo.cpp: -------------------------------------------------------------------------------- 1 | // Simple demonstration of unified @safe/@unsafe annotations 2 | // No standard library dependencies 3 | 4 | // Example 1: Namespace-level @safe 5 | // @safe 6 | namespace app { 7 | void func1() { 8 | int value = 42; 9 | int& ref1 = value; 10 | // int& ref2 = value; // ERROR: would be caught 11 | } 12 | 13 | // @unsafe 14 | void unsafe_func() { 15 | int value = 42; 16 | int& ref1 = value; 17 | int& ref2 = value; // OK - not checked 18 | } 19 | } 20 | 21 | // Example 2: Function-level (default unsafe) 22 | void default_unsafe() { 23 | int value = 42; 24 | int& ref1 = value; 25 | int& ref2 = value; // OK - not checked 26 | } 27 | 28 | // @safe 29 | void safe_function() { 30 | int value = 42; 31 | int& ref1 = value; 32 | // int& ref2 = value; // ERROR: would be caught 33 | } 34 | 35 | // Example 3: First element makes file safe 36 | /* 37 | // @safe 38 | int global = 42; // Would make entire file safe 39 | 40 | void all_functions_checked() { 41 | int value = 42; 42 | int& ref1 = value; 43 | // int& ref2 = value; // ERROR: would be caught 44 | } 45 | */ -------------------------------------------------------------------------------- /include/rusty/send_trait.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This file now just includes traits.hpp which provides the unified 4 | // is_send, is_sync, and is_explicitly_send definitions. 5 | // This file exists for backward compatibility. 6 | 7 | #include "traits.hpp" 8 | 9 | namespace rusty { 10 | 11 | // ================================================================== 12 | // HELPER MACRO FOR USER TYPES 13 | // ================================================================== 14 | 15 | // Convenience macro to mark types as Send 16 | // Usage: RUSTY_MARK_SEND(MyType) 17 | #define RUSTY_MARK_SEND(Type) \ 18 | namespace rusty { \ 19 | template<> struct is_explicitly_send : std::true_type {}; \ 20 | template<> struct is_send : std::true_type {}; \ 21 | } 22 | 23 | // Template version - marks Template as Send if T is Send 24 | // Usage: RUSTY_MARK_SEND_TEMPLATE(MyContainer, T) 25 | #define RUSTY_MARK_SEND_TEMPLATE(Template, T) \ 26 | namespace rusty { \ 27 | template \ 28 | struct is_explicitly_send> : is_send {}; \ 29 | template \ 30 | struct is_send> : is_send {}; \ 31 | } 32 | 33 | } // namespace rusty 34 | -------------------------------------------------------------------------------- /examples/submodule_project/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Setup script for using rustycpp as a submodule 4 | 5 | echo "Setting up rustycpp as a submodule..." 6 | 7 | # Create the external directory 8 | mkdir -p external 9 | 10 | # Add rustycpp as a submodule (if not already added) 11 | if [ ! -d "external/rustycpp" ]; then 12 | echo "Adding rustycpp as a git submodule..." 13 | git submodule add https://github.com/shuaimu/rustycpp external/rustycpp 14 | git submodule update --init --recursive 15 | else 16 | echo "rustycpp submodule already exists, updating..." 17 | git submodule update --init --recursive 18 | fi 19 | 20 | # Create build directory 21 | mkdir -p build 22 | 23 | echo "Configuring CMake project..." 24 | cd build 25 | cmake .. -DENABLE_BORROW_CHECKING=ON -DRUSTYCPP_BUILD_TYPE=release 26 | 27 | echo "Building the project (this will also build rusty-cpp-checker)..." 28 | cmake --build . 29 | 30 | echo "" 31 | echo "Setup complete! The rusty-cpp-checker will be built automatically as part of your project." 32 | echo "" 33 | echo "To run borrow checks:" 34 | echo " cd build" 35 | echo " cmake --build . --target borrow_check_all" 36 | echo "" 37 | echo "To build and check in one command:" 38 | echo " cmake --build . --target all" -------------------------------------------------------------------------------- /examples/test_simple_move.cpp: -------------------------------------------------------------------------------- 1 | // Simulate std::move without including headers 2 | namespace std { 3 | template 4 | T&& move(T& t) { return static_cast(t); } 5 | } 6 | 7 | class UniquePtr { 8 | public: 9 | int* ptr; 10 | UniquePtr(int* p) : ptr(p) {} 11 | }; 12 | 13 | void test_basic_move() { 14 | UniquePtr ptr1(new int(42)); 15 | 16 | // Move ptr1 to ptr2 using std::move 17 | UniquePtr ptr2 = std::move(ptr1); 18 | 19 | // This should be flagged as use-after-move 20 | int* p = ptr1.ptr; // ERROR: ptr1 has been moved 21 | } 22 | 23 | void consume(UniquePtr p) { 24 | // Function that takes ownership 25 | } 26 | 27 | void test_move_in_call() { 28 | UniquePtr ptr(new int(42)); 29 | 30 | // Move ptr into function 31 | consume(std::move(ptr)); 32 | 33 | // This should be flagged as use-after-move 34 | int* p = ptr.ptr; // ERROR: ptr has been moved 35 | } 36 | 37 | void test_multiple_moves() { 38 | UniquePtr ptr1(new int(42)); 39 | UniquePtr ptr2 = std::move(ptr1); 40 | 41 | // Try to move again - should error 42 | UniquePtr ptr3 = std::move(ptr1); // ERROR: ptr1 already moved 43 | } 44 | 45 | int main() { 46 | test_basic_move(); 47 | test_move_in_call(); 48 | test_multiple_moves(); 49 | return 0; 50 | } -------------------------------------------------------------------------------- /examples/test_rusty_move.cpp: -------------------------------------------------------------------------------- 1 | // Test move semantics with rusty types 2 | 3 | extern "C" int printf(const char*, ...); 4 | 5 | // Our move function 6 | template 7 | T&& move(T& x) { 8 | return static_cast(x); 9 | } 10 | 11 | // Simple unique pointer 12 | class UniqueInt { 13 | int* ptr; 14 | public: 15 | UniqueInt(int val) : ptr(new int(val)) {} 16 | ~UniqueInt() { delete ptr; } 17 | 18 | // Delete copy 19 | UniqueInt(const UniqueInt&) = delete; 20 | UniqueInt& operator=(const UniqueInt&) = delete; 21 | 22 | // Move constructor 23 | UniqueInt(UniqueInt&& other) : ptr(other.ptr) { 24 | other.ptr = nullptr; 25 | } 26 | 27 | int get() const { return ptr ? *ptr : 0; } 28 | }; 29 | 30 | // @safe 31 | void test_unique_move() { 32 | UniqueInt p1(42); 33 | UniqueInt p2 = move(p1); 34 | 35 | // Use after move - should be caught 36 | int val = p1.get(); // ERROR: use after move 37 | printf("Value: %d\n", val); 38 | } 39 | 40 | // @safe 41 | void test_correct_move() { 42 | UniqueInt p1(100); 43 | printf("Before move: %d\n", p1.get()); 44 | 45 | UniqueInt p2 = move(p1); 46 | printf("After move: %d\n", p2.get()); 47 | // Don't use p1 after move - correct 48 | } 49 | 50 | int main() { 51 | test_correct_move(); 52 | test_unique_move(); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /examples/test_each_control_flow.cpp: -------------------------------------------------------------------------------- 1 | // Test each control flow issue separately 2 | 3 | namespace std { 4 | template 5 | T&& move(T& t) { return static_cast(t); } 6 | } 7 | 8 | // Test 1: Double mutable borrow (SHOULD ERROR) 9 | void test1_double_borrow() { 10 | int value = 42; 11 | int& ref1 = value; 12 | int& ref2 = value; // ERROR expected 13 | } 14 | 15 | // Test 2: Loop with move (SHOULD ERROR on 2nd iteration) 16 | void test2_loop_move() { 17 | int x = 42; 18 | for (int i = 0; i < 2; i++) { 19 | int y = std::move(x); // ERROR on second iteration 20 | } 21 | } 22 | 23 | // Test 3: Move in always-true condition (SHOULD ERROR on use) 24 | void test3_conditional_move() { 25 | int x = 42; 26 | if (true) { 27 | int y = std::move(x); 28 | } 29 | int z = x; // ERROR: use after move 30 | } 31 | 32 | // Test 4: Nested scopes (SHOULD BE OK) 33 | void test4_nested_scopes() { 34 | int value = 42; 35 | { 36 | int& ref1 = value; 37 | } 38 | { 39 | int& ref2 = value; // Should be OK 40 | } 41 | } 42 | 43 | // Test 5: Move in maybe condition (AMBIGUOUS) 44 | void test5_maybe_move() { 45 | int x = 42; 46 | bool unknown = true; // Checker doesn't know the value 47 | if (unknown) { 48 | int y = std::move(x); 49 | } 50 | int z = x; // Might or might not be moved 51 | } -------------------------------------------------------------------------------- /examples/test_dangling.cpp: -------------------------------------------------------------------------------- 1 | // Test for dangling reference detection 2 | 3 | int& create_dangling_ref() { 4 | int local = 42; 5 | return local; // ERROR: returning reference to local variable 6 | } 7 | 8 | const int& another_dangling() { 9 | int temp = 100; 10 | const int& ref = temp; 11 | return ref; // ERROR: ref points to local 'temp' 12 | } 13 | 14 | int* create_dangling_ptr() { 15 | int local = 42; 16 | return &local; // ERROR: returning address of local variable 17 | } 18 | 19 | // This should be OK - returning reference to static 20 | const int& get_static_ref() { 21 | static int static_val = 42; 22 | return static_val; 23 | } 24 | 25 | // Test scope-based lifetime issues 26 | void test_scope_lifetimes() { 27 | int* ptr = nullptr; 28 | 29 | { // Inner scope 30 | int scoped_val = 100; 31 | ptr = &scoped_val; // Taking address of scoped variable 32 | } // scoped_val dies here 33 | 34 | // ERROR: Using ptr here would access destroyed variable 35 | // *ptr = 200; 36 | } 37 | 38 | int main() { 39 | // These calls would all create dangling references 40 | // int& bad1 = create_dangling_ref(); 41 | // const int& bad2 = another_dangling(); 42 | // int* bad3 = create_dangling_ptr(); 43 | 44 | // This is OK 45 | const int& good = get_static_ref(); 46 | 47 | test_scope_lifetimes(); 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /examples/test_unique_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void use_after_move_example() { 5 | std::unique_ptr ptr1 = std::make_unique(42); 6 | std::unique_ptr ptr2 = std::move(ptr1); 7 | 8 | // This should trigger a use-after-move error 9 | *ptr1 = 10; // ERROR: ptr1 has been moved 10 | } 11 | 12 | void valid_transfer() { 13 | std::unique_ptr ptr1 = std::make_unique(42); 14 | std::unique_ptr ptr2 = std::move(ptr1); 15 | 16 | // This is fine - using ptr2, not ptr1 17 | *ptr2 = 10; 18 | } 19 | 20 | void borrow_checking_example() { 21 | int value = 42; 22 | int& ref1 = value; // Immutable borrow 23 | int& ref2 = value; // Another immutable borrow - OK 24 | 25 | // This would be an error if we had mutable borrow checking 26 | // int& mut_ref = value; // ERROR: Cannot have mutable borrow while immutable borrows exist 27 | } 28 | 29 | // Example with annotations (parsed from comments for MVP) 30 | // @owns 31 | std::unique_ptr create_value() { 32 | return std::make_unique(42); 33 | } 34 | 35 | // @borrows 36 | void use_value(const int& value) { 37 | std::cout << value << std::endl; 38 | } 39 | 40 | int main() { 41 | valid_transfer(); 42 | use_after_move_example(); 43 | borrow_checking_example(); 44 | 45 | auto ptr = create_value(); 46 | use_value(*ptr); 47 | 48 | return 0; 49 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty-cpp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Shuai Mu "] 6 | description = "A Rust-based static analyzer that applies Rust's ownership and borrowing rules to C++ code" 7 | documentation = "https://docs.rs/rusty-cpp" 8 | homepage = "https://github.com/shuaimu/rusty-cpp" 9 | repository = "https://github.com/shuaimu/rusty-cpp" 10 | readme = "README.md" 11 | keywords = ["cpp", "static-analysis", "borrow-checker", "memory-safety", "rust"] 12 | categories = ["development-tools", "development-tools::build-utils"] 13 | license = "MIT OR Apache-2.0" 14 | exclude = ["tests/*", "examples/*", ".git/*", ".github/*", "*.bak"] 15 | 16 | [lib] 17 | name = "rusty_cpp" 18 | path = "src/lib.rs" 19 | 20 | [[bin]] 21 | name = "rusty-cpp-checker" 22 | path = "src/main.rs" 23 | 24 | [dependencies] 25 | clang = { version = "2.0.0", features = ["clang_3_6"] } 26 | clang-sys = "1.8.1" 27 | clap = { version = "4.5.44", features = ["derive"] } 28 | colored = "3.0.0" 29 | miette = "7.6.0" 30 | once_cell = "1.21.3" 31 | petgraph = "0.8.2" 32 | rayon = "1.10.0" 33 | regex = "1.10.2" 34 | serde = "1.0.219" 35 | serde_json = "1.0.142" 36 | z3 = "0.12" 37 | 38 | [dev-dependencies] 39 | tempfile = "3.20.0" 40 | assert_cmd = "2.0" 41 | rand = "0.8" 42 | 43 | [package.metadata.docs.rs] 44 | # This tells docs.rs to skip building the documentation since it requires Z3 and LLVM 45 | no-default-features = true 46 | -------------------------------------------------------------------------------- /examples/cmake_project/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "safe_string.h" 2 | #include 3 | #include 4 | 5 | extern "C" int printf(const char*, ...); 6 | 7 | // @safe 8 | void demonstrate_borrow_checking() { 9 | int value = 42; 10 | const int& ref1 = value; // Immutable borrow 11 | const int& ref2 = value; // Multiple immutable borrows OK 12 | 13 | // int& mut_ref = value; // Would error: can't have mutable with immutable 14 | 15 | printf("Values: %d %d\n", ref1, ref2); 16 | } 17 | 18 | // @safe 19 | void demonstrate_move_semantics() { 20 | auto ptr1 = std::make_unique(42); 21 | auto ptr2 = std::move(ptr1); // Move ownership 22 | 23 | // This would be caught as use-after-move: 24 | // int val = *ptr1; // Error: use after move 25 | 26 | int val2 = *ptr2; // OK: ptr2 owns the value 27 | printf("Value: %d\n", val2); 28 | } 29 | 30 | // @safe 31 | void demonstrate_safe_string() { 32 | safe::SafeString str1("Hello"); 33 | const std::string& ref = str1.get(); // Borrow 34 | 35 | printf("String: %s\n", ref.c_str()); 36 | 37 | // Transfer ownership 38 | safe::SafeString str2(std::move(str1)); 39 | 40 | // This would error: 41 | // const std::string& bad_ref = str1.get(); // Use after move 42 | } 43 | 44 | int main() { 45 | demonstrate_borrow_checking(); 46 | demonstrate_move_semantics(); 47 | demonstrate_safe_string(); 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /examples/simple_const_ref_example.cpp: -------------------------------------------------------------------------------- 1 | // Simple example demonstrating const reference borrowing rules 2 | // This shows how Rust's borrow checker rules apply to C++ const references 3 | 4 | #include 5 | 6 | // @safe 7 | void demonstrate_const_ref_borrowing() { 8 | int value = 42; 9 | 10 | // Multiple const references are allowed (immutable borrows) 11 | const int& ref1 = value; // First immutable borrow - OK 12 | const int& ref2 = value; // Second immutable borrow - OK 13 | const int& ref3 = value; // Third immutable borrow - OK 14 | 15 | // All can be used simultaneously to read the value 16 | std::cout << "ref1: " << ref1 << std::endl; // OK 17 | std::cout << "ref2: " << ref2 << std::endl; // OK 18 | std::cout << "ref3: " << ref3 << std::endl; // OK 19 | 20 | int sum = ref1 + ref2 + ref3; // OK - reading through const refs 21 | std::cout << "Sum: " << sum << std::endl; 22 | } 23 | 24 | // @safe 25 | void demonstrate_const_ref_violation() { 26 | int value = 42; 27 | 28 | const int& const_ref = value; // Immutable borrow - OK 29 | int& mut_ref = value; // ERROR: Cannot have mutable borrow when immutable exists 30 | 31 | // This would violate the guarantee that const_ref won't see unexpected changes 32 | mut_ref = 100; // If allowed, const_ref would suddenly see value 100 33 | } 34 | 35 | int main() { 36 | demonstrate_const_ref_borrowing(); 37 | demonstrate_const_ref_violation(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /examples/send_hybrid_debug.cpp: -------------------------------------------------------------------------------- 1 | // Debug the hybrid Send trait 2 | 3 | #include 4 | #include 5 | 6 | namespace rusty { 7 | 8 | template 9 | struct is_explicitly_not_send : std::false_type {}; 10 | 11 | template 12 | struct has_send_marker : std::false_type {}; 13 | 14 | template 15 | struct has_send_marker> : std::true_type {}; 16 | 17 | template 18 | struct is_send { 19 | static constexpr bool value = []() { 20 | if constexpr (is_explicitly_not_send::value) { 21 | return false; 22 | } 23 | else if constexpr (has_send_marker::value) { 24 | return T::is_send; 25 | } 26 | else { 27 | return std::is_move_constructible_v; 28 | } 29 | }(); 30 | }; 31 | 32 | class ThreadSafeQueue { 33 | public: 34 | static constexpr bool is_send = true; 35 | ThreadSafeQueue() = default; 36 | ThreadSafeQueue(ThreadSafeQueue&&) = default; 37 | }; 38 | 39 | } // namespace rusty 40 | 41 | int main() { 42 | std::cout << "has_send_marker: " 43 | << rusty::has_send_marker::value << "\n"; 44 | 45 | std::cout << "ThreadSafeQueue::is_send: " 46 | << rusty::ThreadSafeQueue::is_send << "\n"; 47 | 48 | std::cout << "is_send: " 49 | << rusty::is_send::value << "\n"; 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /tests/raii/partial_move_detailed_test.cpp: -------------------------------------------------------------------------------- 1 | // Detailed test: What partial move features does RustyCpp support? 2 | 3 | #include 4 | #include 5 | 6 | struct Pair { 7 | std::string first; 8 | std::string second; 9 | }; 10 | 11 | // TEST 1: Double move of same field - should ERROR 12 | // @safe 13 | void test_double_move_same_field() { 14 | Pair p; 15 | p.first = "hello"; 16 | std::string x = std::move(p.first); 17 | std::string y = std::move(p.first); // ERROR expected: p.first already moved 18 | } 19 | 20 | // TEST 2: Move different fields - should be OK 21 | // @safe 22 | void test_move_different_fields() { 23 | Pair p; 24 | p.first = "hello"; 25 | p.second = "world"; 26 | std::string x = std::move(p.first); // Move p.first 27 | std::string y = std::move(p.second); // Should be OK: p.second not moved yet 28 | } 29 | 30 | // TEST 3: Use field after moving different field - should be OK 31 | // @safe 32 | void test_use_unmoved_field() { 33 | Pair p; 34 | p.first = "hello"; 35 | p.second = "world"; 36 | std::string x = std::move(p.first); // Move p.first 37 | int len = p.second.length(); // Should be OK: p.second not moved 38 | } 39 | 40 | // TEST 4: Use same field after move - should ERROR 41 | // @safe 42 | void test_use_moved_field() { 43 | Pair p; 44 | p.first = "hello"; 45 | std::string x = std::move(p.first); // Move p.first 46 | int len = p.first.length(); // ERROR expected: p.first was moved 47 | } 48 | 49 | int main() { return 0; } 50 | -------------------------------------------------------------------------------- /examples/cmake_project/src/memory_manager.cpp: -------------------------------------------------------------------------------- 1 | // Example file with both safe and unsafe code 2 | 3 | #include 4 | #include 5 | 6 | // Unsafe by default - uses raw pointers 7 | void* allocate_raw(size_t size) { 8 | return malloc(size); 9 | } 10 | 11 | void deallocate_raw(void* ptr) { 12 | free(ptr); 13 | } 14 | 15 | // @safe 16 | namespace memory { 17 | 18 | // @safe 19 | std::unique_ptr allocate_array(size_t count) { 20 | return std::make_unique(count); 21 | } 22 | 23 | // @safe 24 | void process_array(const int* arr, size_t size) { 25 | // This should trigger an error - dereferencing raw pointer in safe code 26 | for (size_t i = 0; i < size; ++i) { 27 | int value = arr[i]; // Error: pointer dereference requires unsafe 28 | // Process value... 29 | } 30 | } 31 | 32 | // @unsafe 33 | void unsafe_process(int* ptr) { 34 | // This is OK - we're in unsafe context 35 | *ptr = 42; 36 | } 37 | 38 | // @safe 39 | void safe_wrapper() { 40 | int x = 10; 41 | // This should error - calling unsafe function from safe context 42 | unsafe_process(&x); // Error: requires unsafe annotation 43 | } 44 | } 45 | 46 | // Example of gradual adoption - this function remains unchecked 47 | void legacy_function() { 48 | int* ptr = (int*)malloc(sizeof(int)); 49 | *ptr = 100; 50 | free(ptr); 51 | // Use after free not detected in unchecked code 52 | *ptr = 200; // Bug, but not checked by default 53 | } -------------------------------------------------------------------------------- /examples/test_transitive.cpp: -------------------------------------------------------------------------------- 1 | #include "transitive.h" 2 | 3 | // Test transitive lifetime relationships 4 | // If 'a: 'b and 'b: 'c, then 'a: 'c 5 | 6 | void test_transitive_lifetimes() { 7 | int long_lived = 42; 8 | int medium_lived = 100; 9 | int short_lived = 200; 10 | 11 | // Create references with different lifetimes 12 | const int& ref_long = long_lived; // lifetime 'a 13 | const int& ref_medium = medium_lived; // lifetime 'b where 'a: 'b 14 | const int& ref_short = short_lived; // lifetime 'c where 'b: 'c 15 | 16 | // Test function that requires 'a: 'b 17 | const int& result1 = requires_outlives(ref_long, ref_medium); // OK 18 | 19 | // Test function that requires 'a: 'b 20 | // const int& result2 = requires_outlives(ref_medium, ref_long); // ERROR: 'b doesn't outlive 'a 21 | 22 | // Test transitive: needs 'a: 'c (satisfied via 'a: 'b: 'c) 23 | const int& result3 = requires_transitive(ref_long, ref_medium, ref_short); // OK if transitive 24 | } 25 | 26 | // Test lifetime inference 27 | void test_inference() { 28 | int x = 10; 29 | 30 | // Lifetime of ref1 starts here 31 | int& ref1 = x; 32 | 33 | // Some operations 34 | ref1 += 5; 35 | 36 | { 37 | // Inner scope - ref2 has shorter lifetime 38 | int& ref2 = ref1; 39 | ref2 += 10; 40 | } // ref2 lifetime ends here 41 | 42 | // ref1 is still valid 43 | ref1 += 20; 44 | 45 | } // ref1 lifetime ends here 46 | 47 | int main() { 48 | test_transitive_lifetimes(); 49 | test_inference(); 50 | return 0; 51 | } -------------------------------------------------------------------------------- /examples/cmake_project/src/simple_demo.cpp: -------------------------------------------------------------------------------- 1 | // Simple demo without standard library headers 2 | // This demonstrates the borrow checker with CMake 3 | 4 | extern "C" int printf(const char*, ...); 5 | 6 | // @safe 7 | namespace demo { 8 | 9 | // @safe 10 | void test_borrow_checking() { 11 | int value = 42; 12 | const int& ref1 = value; // First immutable borrow 13 | const int& ref2 = value; // Second immutable borrow - OK 14 | 15 | // This would error if uncommented: 16 | // int& mut_ref = value; // Can't have mutable with immutable 17 | 18 | printf("Values: %d %d\n", ref1, ref2); 19 | } 20 | 21 | // @safe 22 | void test_move_semantics() { 23 | int* ptr1 = new int(42); 24 | int* ptr2 = ptr1; // Copy pointer 25 | ptr1 = nullptr; // "Move" by nulling 26 | 27 | // Raw pointer operations should error in safe code: 28 | int val = *ptr2; // Error: dereference requires unsafe 29 | 30 | delete ptr2; 31 | } 32 | 33 | // Unmarked function (unsafe by default) 34 | void unsafe_operation(int* ptr) { 35 | *ptr = 100; // OK in unsafe code 36 | } 37 | 38 | // @safe 39 | void test_unsafe_propagation() { 40 | int x = 10; 41 | // This should error - calling unsafe function from safe 42 | unsafe_operation(&x); // Error: requires unsafe context 43 | } 44 | } 45 | 46 | int main() { 47 | demo::test_borrow_checking(); 48 | demo::test_move_semantics(); 49 | demo::test_unsafe_propagation(); 50 | return 0; 51 | } -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | # Z3 header location - automatically determined based on OS 3 | # For macOS with Homebrew: 4 | # Z3_SYS_Z3_HEADER = "/opt/homebrew/include/z3.h" 5 | 6 | # For Linux (standard location): 7 | Z3_SYS_Z3_HEADER = "/usr/include/z3.h" 8 | 9 | [target.aarch64-apple-darwin] 10 | # macOS ARM64 (Apple Silicon) specific settings 11 | rustflags = [ 12 | "-C", "link-arg=-Wl,-rpath,@executable_path/../lib", 13 | "-C", "link-arg=-Wl,-rpath,@loader_path/../lib", 14 | "-C", "link-arg=-Wl,-rpath,/opt/homebrew/lib", 15 | "-C", "link-arg=-Wl,-rpath,/opt/homebrew/Cellar/llvm/19.1.7/lib" 16 | ] 17 | 18 | [target.x86_64-apple-darwin] 19 | # macOS Intel specific settings 20 | rustflags = [ 21 | "-C", "link-arg=-Wl,-rpath,@executable_path/../lib", 22 | "-C", "link-arg=-Wl,-rpath,@loader_path/../lib", 23 | "-C", "link-arg=-Wl,-rpath,/usr/local/lib", 24 | "-C", "link-arg=-Wl,-rpath,/opt/homebrew/Cellar/llvm/19.1.7/lib" 25 | ] 26 | 27 | [target.x86_64-unknown-linux-gnu] 28 | # Linux x86_64 settings 29 | rustflags = [ 30 | "-C", "link-arg=-Wl,-rpath,$ORIGIN/../lib", 31 | "-C", "link-arg=-Wl,-rpath,/usr/lib/llvm-14/lib", 32 | "-C", "link-arg=-Wl,-rpath,/usr/local/lib" 33 | ] 34 | 35 | [target.aarch64-unknown-linux-gnu] 36 | # Linux ARM64 settings 37 | rustflags = [ 38 | "-C", "link-arg=-Wl,-rpath,$ORIGIN/../lib", 39 | "-C", "link-arg=-Wl,-rpath,/usr/lib/llvm-14/lib", 40 | "-C", "link-arg=-Wl,-rpath,/usr/local/lib" 41 | ] 42 | 43 | # Profile for release builds - optimize for portability 44 | [profile.release] 45 | lto = true 46 | codegen-units = 1 47 | strip = true 48 | opt-level = 3 -------------------------------------------------------------------------------- /tests/run_rusty_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to compile and run all rusty type tests 4 | 5 | echo "Building and running Rusty types tests..." 6 | echo "=========================================" 7 | 8 | # Colors for output 9 | GREEN='\033[0;32m' 10 | RED='\033[0;31m' 11 | NC='\033[0m' # No Color 12 | 13 | # Track failures 14 | FAILED=0 15 | TOTAL=0 16 | 17 | # Compile flags 18 | CXX="clang++" 19 | CXXFLAGS="-std=c++17 -Wall -Wextra -I../include" 20 | 21 | # List of test files 22 | TESTS=( 23 | "rusty_box_test" 24 | "rusty_arc_test" 25 | "rusty_rc_test" 26 | "rusty_vec_test" 27 | "rusty_option_test" 28 | "rusty_result_test" 29 | "rusty_rwlock_test" 30 | "rusty_condvar_test" 31 | "rusty_barrier_test" 32 | "rusty_once_test" 33 | ) 34 | 35 | # Create build directory if it doesn't exist 36 | mkdir -p build 37 | 38 | # Compile and run each test 39 | for test in "${TESTS[@]}"; do 40 | echo "" 41 | echo "Building $test..." 42 | 43 | if $CXX $CXXFLAGS "$test.cpp" -o "build/$test" -pthread 2>/dev/null; then 44 | echo "Running $test..." 45 | if "./build/$test"; then 46 | echo -e "${GREEN}✓ $test passed${NC}" 47 | else 48 | echo -e "${RED}✗ $test failed${NC}" 49 | ((FAILED++)) 50 | fi 51 | else 52 | echo -e "${RED}✗ $test compilation failed${NC}" 53 | ((FAILED++)) 54 | fi 55 | ((TOTAL++)) 56 | done 57 | 58 | echo "" 59 | echo "=========================================" 60 | if [ $FAILED -eq 0 ]; then 61 | echo -e "${GREEN}All $TOTAL tests passed!${NC}" 62 | exit 0 63 | else 64 | echo -e "${RED}$FAILED out of $TOTAL tests failed${NC}" 65 | exit 1 66 | fi -------------------------------------------------------------------------------- /tests/raii/partial_move_test.cpp: -------------------------------------------------------------------------------- 1 | // Test: Partial moves - moving individual struct fields 2 | // Expected: Rust supports this, RustyCpp may not 3 | 4 | #include 5 | #include 6 | 7 | struct Pair { 8 | std::string first; 9 | std::string second; 10 | }; 11 | 12 | // @safe 13 | void test_partial_move_basic() { 14 | Pair p; 15 | p.first = "hello"; 16 | p.second = "world"; 17 | 18 | // Move only p.first 19 | std::string x = std::move(p.first); 20 | 21 | // In Rust: p.second is still valid, p.first is moved 22 | // Question: Does RustyCpp track this at field level? 23 | std::string y = std::move(p.second); // Should be OK - p.second wasn't moved 24 | } 25 | 26 | // @safe 27 | void test_partial_move_use_after_field_move() { 28 | Pair p; 29 | p.first = "hello"; 30 | p.second = "world"; 31 | 32 | std::string x = std::move(p.first); 33 | 34 | // This should be an error - p.first was moved 35 | std::string z = std::move(p.first); // ERROR: use after move of p.first 36 | } 37 | 38 | // @safe 39 | void test_use_unmoved_field_after_partial_move() { 40 | Pair p; 41 | p.first = "hello"; 42 | p.second = "world"; 43 | 44 | std::string x = std::move(p.first); 45 | 46 | // This should be OK - only p.first was moved, not p.second 47 | int len = p.second.length(); // Should be OK 48 | } 49 | 50 | // @safe 51 | void test_whole_struct_move_after_partial() { 52 | Pair p; 53 | p.first = "hello"; 54 | p.second = "world"; 55 | 56 | std::string x = std::move(p.first); 57 | 58 | // In Rust: Cannot move whole struct after partial move 59 | // Pair p2 = std::move(p); // Should be ERROR in Rust 60 | } 61 | 62 | int main() { 63 | test_partial_move_basic(); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /examples/test_cross_file.cpp: -------------------------------------------------------------------------------- 1 | #include "string_utils.h" 2 | #include 3 | 4 | void test_lifetime_annotations() { 5 | // Test 1: Basic immutable borrow 6 | const std::string& static_str = getStaticString(); 7 | std::cout << static_str << std::endl; 8 | 9 | // Test 2: Identity function preserves lifetime 10 | std::string local = "hello"; 11 | const std::string& ref = identity(local); 12 | // ref has same lifetime as local 13 | 14 | // Test 3: Selecting first with lifetime bounds 15 | std::string str1 = "first"; 16 | std::string str2 = "second"; 17 | const std::string& selected = selectFirst(str1, str2); 18 | // selected borrows from str1 19 | 20 | // Test 4: Ownership transfer 21 | std::string owned = copyString(str1); 22 | // owned is a new string, doesn't borrow from str1 23 | 24 | // Test 5: Mutable borrow 25 | std::string& mut_ref = getMutableString(); 26 | mut_ref = "modified"; 27 | 28 | // Test 6: Mutable parameter and return 29 | std::string mut_str = "original"; 30 | std::string& modified = modifyString(mut_str); 31 | modified += " changed"; 32 | } 33 | 34 | void test_borrow_violations() { 35 | std::string value = "test"; 36 | 37 | // Multiple immutable borrows - OK 38 | const std::string& ref1 = value; 39 | const std::string& ref2 = value; 40 | 41 | // Can't have mutable borrow while immutable borrows exist 42 | // std::string& mut_ref = value; // This should be an error 43 | 44 | // Can't have multiple mutable borrows 45 | std::string& mut_ref1 = value; 46 | // std::string& mut_ref2 = value; // This should be an error 47 | } 48 | 49 | int main() { 50 | test_lifetime_annotations(); 51 | test_borrow_violations(); 52 | return 0; 53 | } -------------------------------------------------------------------------------- /examples/test_loop_simple.cpp: -------------------------------------------------------------------------------- 1 | // Simple test to see how loops are handled 2 | 3 | // Mock std::move 4 | namespace std { 5 | template 6 | T&& move(T& t) { return static_cast(t); } 7 | } 8 | 9 | void test_loop_borrow() { 10 | int value = 42; 11 | 12 | // Take a reference in a loop 13 | for (int i = 0; i < 3; i++) { 14 | int& ref = value; // Borrow in each iteration 15 | ref += i; 16 | } 17 | // ref should be out of scope here 18 | 19 | // This should be OK - previous borrow ended 20 | int& ref2 = value; 21 | ref2 = 100; 22 | } 23 | 24 | void test_if_else_borrow() { 25 | int value = 42; 26 | bool condition = true; 27 | 28 | if (condition) { 29 | int& ref1 = value; // Borrow in true branch 30 | ref1 = 100; 31 | } else { 32 | int& ref2 = value; // Borrow in false branch 33 | ref2 = 200; 34 | } 35 | // Both refs out of scope 36 | 37 | // This should be OK 38 | int& ref3 = value; 39 | ref3 = 300; 40 | } 41 | 42 | void test_moved_in_condition() { 43 | int x = 42; 44 | bool condition = false; 45 | 46 | if (condition) { 47 | int y = std::move(x); // Move only if condition is true 48 | } 49 | 50 | // x might or might not be moved 51 | int z = x; // What does checker say? 52 | } 53 | 54 | void test_nested_scopes() { 55 | int value = 42; 56 | 57 | { 58 | int& ref1 = value; // Borrow in inner scope 59 | ref1 = 100; 60 | } // ref1 goes out of scope 61 | 62 | // This should be OK 63 | int& ref2 = value; 64 | ref2 = 200; 65 | } 66 | 67 | int main() { 68 | test_loop_borrow(); 69 | test_if_else_borrow(); 70 | test_moved_in_condition(); 71 | test_nested_scopes(); 72 | return 0; 73 | } -------------------------------------------------------------------------------- /examples/scope_fix_demo.cpp: -------------------------------------------------------------------------------- 1 | // Demonstration of scope tracking fix 2 | // This file should have NO errors with scope tracking 3 | 4 | void before_fix_false_positive() { 5 | int value = 42; 6 | 7 | // These are in separate scopes and should NOT conflict 8 | { 9 | int& ref1 = value; 10 | ref1 = 100; 11 | } // ref1 destroyed here 12 | 13 | { 14 | int& ref2 = value; // Should be OK! 15 | ref2 = 200; 16 | } // ref2 destroyed here 17 | } 18 | 19 | void multiple_const_refs_in_scopes() { 20 | int value = 42; 21 | 22 | { 23 | const int& cref1 = value; 24 | { 25 | const int& cref2 = value; // Nested, both const - OK 26 | { 27 | const int& cref3 = value; // Triple nested - OK 28 | int sum = cref1 + cref2 + cref3; 29 | } 30 | } 31 | } 32 | 33 | // All const refs gone, can take mutable 34 | int& mref = value; 35 | mref = 100; 36 | } 37 | 38 | void sequential_scopes() { 39 | int data = 42; 40 | 41 | // Ten sequential scopes - should all be OK 42 | { int& r = data; r = 1; } 43 | { int& r = data; r = 2; } 44 | { int& r = data; r = 3; } 45 | { int& r = data; r = 4; } 46 | { int& r = data; r = 5; } 47 | { int& r = data; r = 6; } 48 | { int& r = data; r = 7; } 49 | { int& r = data; r = 8; } 50 | { int& r = data; r = 9; } 51 | { int& r = data; r = 10; } 52 | } 53 | 54 | // This function SHOULD have an error (for comparison) 55 | void actual_error() { 56 | int value = 42; 57 | int& ref1 = value; 58 | int& ref2 = value; // ERROR: Double mutable borrow 59 | ref1 = 100; 60 | ref2 = 200; 61 | } 62 | 63 | int main() { 64 | before_fix_false_positive(); 65 | multiple_const_refs_in_scopes(); 66 | sequential_scopes(); 67 | actual_error(); 68 | return 0; 69 | } -------------------------------------------------------------------------------- /examples/safety_annotation_demo.cpp: -------------------------------------------------------------------------------- 1 | // This file demonstrates the safety annotation rules: 2 | // 1. @safe/@unsafe only applies to the NEXT element 3 | // 2. Functions are unsafe by default 4 | // 3. To make a whole file safe, wrap in a namespace 5 | 6 | // Example 1: Single annotation affects only next element 7 | // @safe 8 | int move(int x); // This declaration is marked safe 9 | 10 | void func1() { 11 | // This function is unsafe (default) 12 | int x = 1; 13 | int& ref1 = x; 14 | int& ref2 = x; // Not checked 15 | } 16 | 17 | // Example 2: Explicit function annotations 18 | // @safe 19 | void func2() { 20 | // This function is safe 21 | int x = 1; 22 | int& ref1 = x; 23 | int& ref2 = x; // ERROR: multiple mutable borrows 24 | } 25 | 26 | // Example 3: Namespace makes all contents follow the annotation 27 | // @safe 28 | namespace safe_code { 29 | void func3() { 30 | // Safe by namespace default 31 | int value = 42; 32 | int value2 = move(value); 33 | int x = value; // ERROR: use after move 34 | } 35 | 36 | void func4() { 37 | // Also safe by namespace default 38 | int a = 1; 39 | const int& ref1 = a; 40 | const int& ref2 = a; // OK: multiple immutable borrows 41 | } 42 | 43 | // @unsafe 44 | void func5() { 45 | // Explicitly unsafe within safe namespace 46 | int x = 1; 47 | int& ref1 = x; 48 | int& ref2 = x; // Not checked 49 | } 50 | } 51 | 52 | // Example 4: Blocks within safe functions 53 | // @safe 54 | void func6() { 55 | int x = 42; 56 | int& ref = x; // OK 57 | 58 | // Can't have inline unsafe blocks anymore - @unsafe only applies to next element 59 | // If you need unsafe code, put it in a separate function marked @unsafe 60 | 61 | const int& ref2 = x; // ERROR: mixing mutable and immutable borrows 62 | } -------------------------------------------------------------------------------- /src/solver/mod.rs: -------------------------------------------------------------------------------- 1 | use z3::{Context, Solver}; 2 | use std::collections::HashMap; 3 | 4 | #[allow(dead_code)] 5 | pub struct ConstraintSolver<'ctx> { 6 | solver: Solver<'ctx>, 7 | } 8 | 9 | impl<'ctx> ConstraintSolver<'ctx> { 10 | #[allow(dead_code)] 11 | pub fn new(context: &'ctx Context) -> Self { 12 | let solver = Solver::new(context); 13 | Self { solver } 14 | } 15 | 16 | #[allow(dead_code)] 17 | pub fn add_lifetime_constraint(&mut self, constraint: LifetimeConstraint) { 18 | // Convert lifetime constraints to Z3 assertions 19 | match constraint { 20 | LifetimeConstraint::Outlives { longer: _, shorter: _ } => { 21 | // Add SMT constraint: longer >= shorter 22 | } 23 | LifetimeConstraint::MustBeValid { lifetime: _, point: _ } => { 24 | // Add SMT constraint: lifetime.start <= point <= lifetime.end 25 | } 26 | } 27 | } 28 | 29 | #[allow(dead_code)] 30 | pub fn solve(&self) -> Result { 31 | match self.solver.check() { 32 | z3::SatResult::Sat => { 33 | // Extract model and return solution 34 | Ok(Solution { 35 | lifetimes: HashMap::new(), 36 | }) 37 | } 38 | z3::SatResult::Unsat => { 39 | Err("Lifetime constraints are unsatisfiable".to_string()) 40 | } 41 | z3::SatResult::Unknown => { 42 | Err("Could not determine satisfiability".to_string()) 43 | } 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug)] 49 | #[allow(dead_code)] 50 | pub enum LifetimeConstraint { 51 | Outlives { longer: String, shorter: String }, 52 | MustBeValid { lifetime: String, point: usize }, 53 | } 54 | 55 | #[derive(Debug)] 56 | #[allow(dead_code)] 57 | pub struct Solution { 58 | pub lifetimes: HashMap, 59 | } -------------------------------------------------------------------------------- /src/analysis/ownership.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::{OwnershipState, IrStatement, IrExpression}; 2 | use std::collections::HashMap; 3 | 4 | #[allow(dead_code)] 5 | pub struct OwnershipAnalyzer { 6 | states: HashMap, 7 | } 8 | 9 | impl OwnershipAnalyzer { 10 | #[allow(dead_code)] 11 | pub fn new() -> Self { 12 | Self { 13 | states: HashMap::new(), 14 | } 15 | } 16 | 17 | #[allow(dead_code)] 18 | pub fn analyze_statement(&mut self, stmt: &IrStatement) -> Result<(), String> { 19 | match stmt { 20 | IrStatement::Assign { lhs, rhs } => { 21 | match rhs { 22 | IrExpression::Move(from) => { 23 | // Check if source is available 24 | match self.states.get(from) { 25 | Some(OwnershipState::Owned) => { 26 | self.states.insert(from.clone(), OwnershipState::Moved); 27 | self.states.insert(lhs.clone(), OwnershipState::Owned); 28 | } 29 | Some(OwnershipState::Moved) => { 30 | return Err(format!("Use after move: '{}'", from)); 31 | } 32 | _ => {} 33 | } 34 | } 35 | IrExpression::New(_) => { 36 | self.states.insert(lhs.clone(), OwnershipState::Owned); 37 | } 38 | _ => {} 39 | } 40 | } 41 | IrStatement::Drop(var) => { 42 | self.states.insert(var.clone(), OwnershipState::Moved); 43 | } 44 | _ => {} 45 | } 46 | Ok(()) 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub fn get_state(&self, var: &str) -> Option<&OwnershipState> { 51 | self.states.get(var) 52 | } 53 | } -------------------------------------------------------------------------------- /examples/unsafe_comment_example.cpp: -------------------------------------------------------------------------------- 1 | // @safe 2 | // Example demonstrating unsafe blocks using comment annotations 3 | 4 | // @safe 5 | void mixed_safety_function() { 6 | int value = 42; 7 | 8 | // Safe code - checked for borrow rules 9 | int& ref1 = value; 10 | ref1 = 100; 11 | 12 | // @unsafe 13 | // Unsafe code - no checking 14 | int* ptr = &value; 15 | int* alias = ptr; // Multiple aliases OK in unsafe 16 | *alias = 200; 17 | 18 | // Even dangerous operations not caught 19 | int* heap = new int(42); 20 | delete heap; 21 | // @endunsafe 22 | 23 | // Back to safe code 24 | // int& ref2 = value; // ERROR: would be caught (already borrowed) 25 | } 26 | 27 | void single_unsafe_statement() { 28 | int value = 42; 29 | int& ref = value; 30 | 31 | // @unsafe 32 | int* ptr = &value; // Not checked 33 | // @endunsafe 34 | 35 | // This is checked again 36 | // int& ref2 = value; // ERROR: already borrowed 37 | } 38 | 39 | // Example with performance-critical code 40 | void performance_critical_with_unsafe() { 41 | int array[1000]; 42 | 43 | // Safe initialization 44 | for (int i = 0; i < 1000; i++) { 45 | array[i] = i; 46 | } 47 | 48 | // @unsafe 49 | // Fast, unchecked pointer arithmetic 50 | int* ptr = array; 51 | for (int i = 0; i < 1000; i++) { 52 | *(ptr++) *= 2; // No bounds checking 53 | } 54 | // @endunsafe 55 | } 56 | 57 | // Example: interfacing with C code 58 | extern "C" { 59 | void* legacy_c_function(void* data); 60 | } 61 | 62 | void interface_with_c() { 63 | int value = 42; 64 | 65 | // @unsafe 66 | // Need unsafe to work with raw pointers for C interop 67 | void* raw_ptr = &value; 68 | void* result = legacy_c_function(raw_ptr); 69 | // Cast back - unsafe but necessary for C interop 70 | int* typed = static_cast(result); 71 | // @endunsafe 72 | 73 | // Safe code continues 74 | int& ref = value; 75 | } -------------------------------------------------------------------------------- /examples/test_std_move.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void test_std_move_basic() { 6 | std::unique_ptr ptr = std::make_unique(42); 7 | 8 | // Move the unique_ptr using std::move 9 | std::unique_ptr ptr2 = std::move(ptr); 10 | 11 | // This should be flagged as use-after-move 12 | if (ptr) { // ERROR: ptr has been moved 13 | *ptr = 100; 14 | } 15 | } 16 | 17 | void test_std_move_with_strings() { 18 | std::string s1 = "hello"; 19 | std::string s2 = std::move(s1); 20 | 21 | // Use after move - should be an error 22 | s1.length(); // ERROR: s1 has been moved 23 | } 24 | 25 | void test_move_in_function_call() { 26 | std::unique_ptr ptr = std::make_unique(42); 27 | 28 | // Function that takes ownership 29 | void consume(std::unique_ptr p); 30 | 31 | consume(std::move(ptr)); 32 | 33 | // Use after move 34 | if (ptr) { // ERROR: ptr has been moved 35 | *ptr = 100; 36 | } 37 | } 38 | 39 | void test_conditional_move() { 40 | std::unique_ptr ptr = std::make_unique(42); 41 | bool condition = true; 42 | 43 | if (condition) { 44 | std::unique_ptr ptr2 = std::move(ptr); 45 | } 46 | 47 | // This may or may not be valid depending on control flow 48 | // For now, we should flag it as potentially problematic 49 | if (ptr) { // WARNING: ptr may have been moved 50 | *ptr = 100; 51 | } 52 | } 53 | 54 | void test_move_and_reassign() { 55 | std::unique_ptr ptr = std::make_unique(42); 56 | std::unique_ptr ptr2 = std::move(ptr); 57 | 58 | // Reassign after move - this is OK 59 | ptr = std::make_unique(100); 60 | 61 | // Now ptr is valid again 62 | if (ptr) { // OK: ptr has been reassigned 63 | *ptr = 200; 64 | } 65 | } 66 | 67 | int main() { 68 | test_std_move_basic(); 69 | test_std_move_with_strings(); 70 | test_move_in_function_call(); 71 | test_conditional_move(); 72 | test_move_and_reassign(); 73 | return 0; 74 | } -------------------------------------------------------------------------------- /examples/reference_demo.cpp: -------------------------------------------------------------------------------- 1 | // Demonstration of Rust-like borrow checking for C++ references 2 | 3 | // This function shows multiple const references are allowed 4 | void valid_const_refs() { 5 | int value = 42; 6 | const int& ref1 = value; // First immutable borrow - OK 7 | const int& ref2 = value; // Second immutable borrow - OK 8 | const int& ref3 = value; // Third immutable borrow - OK 9 | 10 | // All three references can be used simultaneously 11 | int sum = ref1 + ref2 + ref3; // OK - reading through const refs 12 | } 13 | 14 | // This function would violate Rust's borrow rules 15 | void invalid_mutable_refs() { 16 | int value = 42; 17 | int& mut_ref1 = value; // First mutable borrow - OK 18 | int& mut_ref2 = value; // Second mutable borrow - ERROR! 19 | 20 | // This would lead to aliasing issues 21 | mut_ref1 = 10; 22 | mut_ref2 = 20; // Which value does 'value' have? 23 | } 24 | 25 | // This function shows the conflict between mutable and immutable refs 26 | void invalid_mixed_refs() { 27 | int value = 42; 28 | const int& const_ref = value; // Immutable borrow - OK 29 | int& mut_ref = value; // Mutable borrow - ERROR! Already immutably borrowed 30 | 31 | // This would violate the guarantee that const_ref won't see changes 32 | mut_ref = 100; 33 | } 34 | 35 | // Another violation: mutable first, then const 36 | void invalid_mixed_refs2() { 37 | int value = 42; 38 | int& mut_ref = value; // Mutable borrow - OK 39 | const int& const_ref = value; // Immutable borrow - ERROR! Already mutably borrowed 40 | } 41 | 42 | // Valid pattern: scoped borrows (not yet tracked by our analyzer) 43 | void valid_scoped_borrows() { 44 | int value = 42; 45 | 46 | { 47 | const int& ref1 = value; // Immutable borrow in inner scope 48 | int x = ref1; // Use the reference 49 | } // ref1 goes out of scope 50 | 51 | int& mut_ref = value; // Mutable borrow - Would be OK if we tracked scopes 52 | mut_ref = 100; 53 | } 54 | 55 | int main() { 56 | valid_const_refs(); 57 | invalid_mutable_refs(); 58 | invalid_mixed_refs(); 59 | invalid_mixed_refs2(); 60 | valid_scoped_borrows(); 61 | 62 | return 0; 63 | } -------------------------------------------------------------------------------- /examples/mpsc_demo.cpp: -------------------------------------------------------------------------------- 1 | // Demo showing Rust-like API path: rusty::sync::mpsc::channel 2 | // Exactly mirrors Rust's std::sync::mpsc::channel 3 | 4 | #include "rusty/sync/mpsc.hpp" 5 | #include 6 | #include 7 | 8 | int main() { 9 | std::cout << "Rust-style MPSC Channel Demo\n"; 10 | std::cout << "=============================\n\n"; 11 | 12 | // Create a channel - exactly like Rust's std::sync::mpsc::channel() 13 | auto [sender, receiver] = rusty::sync::mpsc::channel(); 14 | 15 | std::cout << "Created channel with rusty::sync::mpsc::channel()\n"; 16 | std::cout << "This mirrors Rust's std::sync::mpsc::channel::()\n\n"; 17 | 18 | // Clone sender for multi-producer - like Rust's sender.clone() 19 | auto sender2 = sender.clone(); 20 | std::cout << "Cloned sender for multi-producer setup\n\n"; 21 | 22 | // Producer thread 1 23 | std::thread producer1([sender = std::move(sender)]() mutable { 24 | for (int i = 0; i < 5; i++) { 25 | sender.send(i); 26 | std::cout << "Producer 1 sent: " << i << "\n"; 27 | } 28 | }); 29 | 30 | // Producer thread 2 31 | std::thread producer2([sender2 = std::move(sender2)]() mutable { 32 | for (int i = 100; i < 105; i++) { 33 | sender2.send(i); 34 | std::cout << "Producer 2 sent: " << i << "\n"; 35 | } 36 | }); 37 | 38 | // Consumer receives from both producers 39 | std::thread consumer([receiver = std::move(receiver)]() mutable { 40 | std::cout << "\nConsumer receiving messages:\n"; 41 | for (int i = 0; i < 10; i++) { 42 | auto result = receiver.recv(); 43 | if (result.is_ok()) { 44 | std::cout << " Received: " << result.unwrap() << "\n"; 45 | } 46 | } 47 | }); 48 | 49 | producer1.join(); 50 | producer2.join(); 51 | consumer.join(); 52 | 53 | std::cout << "\n✓ Demo complete!\n"; 54 | std::cout << "\nAPI Comparison:\n"; 55 | std::cout << " Rust: use std::sync::mpsc;\n"; 56 | std::cout << " let (tx, rx) = mpsc::channel();\n"; 57 | std::cout << " C++: #include \n"; 58 | std::cout << " auto [tx, rx] = rusty::sync::mpsc::channel();\n"; 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /examples/test_references.cpp: -------------------------------------------------------------------------------- 1 | // Test file for reference borrow checking 2 | 3 | void test_const_reference() { 4 | int value = 42; 5 | const int& const_ref = value; // Immutable borrow 6 | const int& another_const = value; // Multiple immutable borrows OK 7 | 8 | // This would be an error if we could detect it: 9 | // const_ref = 10; // ERROR: Cannot modify through const reference 10 | } 11 | 12 | void test_mutable_reference() { 13 | int value = 42; 14 | int& mut_ref = value; // Mutable borrow 15 | 16 | // This would be an error: 17 | // const int& const_ref = value; // ERROR: Cannot have immutable borrow while mutable exists 18 | // int& another_mut = value; // ERROR: Cannot have multiple mutable borrows 19 | 20 | mut_ref = 100; // OK: Can modify through mutable reference 21 | } 22 | 23 | void test_reference_aliasing() { 24 | int x = 10; 25 | int y = 20; 26 | 27 | const int& ref1 = x; 28 | const int& ref2 = x; // Multiple const refs OK 29 | const int& ref3 = y; // Ref to different value OK 30 | } 31 | 32 | // These would be errors but nested functions aren't allowed in C++ 33 | // void test_dangling_reference() { 34 | // int* get_local() { 35 | // int local = 42; 36 | // return &local; // ERROR: Returning address of local 37 | // } 38 | // 39 | // int& get_ref() { 40 | // int local = 42; 41 | // return local; // ERROR: Returning reference to local 42 | // } 43 | // } 44 | 45 | int* get_local() { 46 | int local = 42; 47 | return &local; // ERROR: Returning address of local 48 | } 49 | 50 | int& get_ref() { 51 | int local = 42; 52 | return local; // ERROR: Returning reference to local 53 | } 54 | 55 | void test_move_semantics() { 56 | // Simplified example without std::unique_ptr 57 | int* ptr1 = new int(42); 58 | int* ptr2 = ptr1; // This is a copy in C++, not a move 59 | 60 | // Both pointers are valid in C++, but we might want to track this 61 | delete ptr1; 62 | // *ptr2 = 10; // ERROR: Use after free (but hard to detect without ownership) 63 | } 64 | 65 | int main() { 66 | test_const_reference(); 67 | test_mutable_reference(); 68 | test_reference_aliasing(); 69 | test_move_semantics(); 70 | 71 | return 0; 72 | } -------------------------------------------------------------------------------- /examples/test_unsafe_propagation.cpp: -------------------------------------------------------------------------------- 1 | // Test unsafe propagation - safe functions cannot call unmarked/unsafe functions 2 | 3 | // External functions (simulate system calls) 4 | extern "C" int printf(const char*, ...); 5 | void process_data(int x); // No safety annotation - unsafe by default 6 | 7 | // @unsafe 8 | void explicitly_unsafe_operation(int* ptr) { 9 | *ptr = 42; // Pointer dereference allowed in unsafe 10 | } 11 | 12 | // @safe 13 | namespace safe_zone { 14 | 15 | void helper_unmarked() { 16 | // This function has no safety annotation 17 | printf("Helper\n"); // printf is whitelisted 18 | } 19 | 20 | // @safe 21 | void safe_function_bad() { 22 | // This should trigger errors: 23 | process_data(10); // Error: calling unmarked function 24 | explicitly_unsafe_operation(nullptr); // Error: calling unsafe function 25 | helper_unmarked(); // Error: calling unmarked function (even in same namespace) 26 | } 27 | 28 | // @unsafe 29 | void unsafe_wrapper() { 30 | // Unsafe functions can call anything: 31 | process_data(10); // OK in unsafe context 32 | explicitly_unsafe_operation(nullptr); // OK in unsafe context 33 | helper_unmarked(); // OK in unsafe context 34 | } 35 | 36 | // @safe 37 | void safe_function_good() { 38 | // Only safe operations: 39 | printf("Safe operation\n"); // OK: printf is whitelisted 40 | int x = 10; 41 | int y = x; // OK: normal assignment 42 | // Note: Cannot call unsafe_wrapper() here either 43 | } 44 | } 45 | 46 | // Default namespace (unsafe by default) 47 | namespace default_ns { 48 | void unmarked_can_call_anything() { 49 | // No safety annotation = unsafe by default 50 | process_data(10); // OK: unsafe calling unsafe 51 | explicitly_unsafe_operation(nullptr); // OK: unsafe calling unsafe 52 | } 53 | } 54 | 55 | int main() { 56 | // Main is unmarked, so it's unsafe by default 57 | safe_zone::safe_function_bad(); // Will check this function 58 | safe_zone::unsafe_wrapper(); // Won't check this (it's unsafe) 59 | safe_zone::safe_function_good(); // Will check this function 60 | 61 | return 0; 62 | } -------------------------------------------------------------------------------- /tests/raii/partial_move_nested_fields.cpp: -------------------------------------------------------------------------------- 1 | // Test: Nested field tracking for partial moves 2 | // Goal: Track moves of nested struct fields like p.inner.field 3 | 4 | #include 5 | #include 6 | 7 | struct Inner { 8 | std::string data; 9 | int value; 10 | }; 11 | 12 | struct Outer { 13 | Inner inner; 14 | std::string name; 15 | }; 16 | 17 | // TEST 1: Double move of nested field - should ERROR 18 | // @safe 19 | void test_nested_double_move() { 20 | Outer o; 21 | o.inner.data = "hello"; 22 | 23 | std::string x = std::move(o.inner.data); // Move o.inner.data 24 | std::string y = std::move(o.inner.data); // ERROR: o.inner.data already moved 25 | } 26 | 27 | // TEST 2: Move different nested fields - should be OK 28 | // @safe 29 | void test_nested_different_fields() { 30 | Outer o; 31 | o.inner.data = "hello"; 32 | o.name = "world"; 33 | 34 | std::string x = std::move(o.inner.data); // Move o.inner.data 35 | std::string y = std::move(o.name); // OK: o.name not moved 36 | } 37 | 38 | // TEST 3: Use nested field after move - should ERROR 39 | // @safe 40 | void test_nested_use_after_move() { 41 | Outer o; 42 | o.inner.data = "hello"; 43 | 44 | std::string x = std::move(o.inner.data); // Move o.inner.data 45 | int len = o.inner.data.length(); // ERROR: o.inner.data was moved 46 | } 47 | 48 | // TEST 4: Move parent after moving nested field - should ERROR 49 | // @safe 50 | void test_move_parent_after_nested_move() { 51 | Outer o; 52 | o.inner.data = "hello"; 53 | 54 | std::string x = std::move(o.inner.data); // Move o.inner.data 55 | Inner i = std::move(o.inner); // ERROR: o.inner partially moved 56 | } 57 | 58 | // TEST 5: Move whole struct after nested field move - should ERROR 59 | // @safe 60 | void test_move_whole_after_nested_move() { 61 | Outer o; 62 | o.inner.data = "hello"; 63 | 64 | std::string x = std::move(o.inner.data); // Move o.inner.data 65 | Outer o2 = std::move(o); // ERROR: o partially moved (via o.inner.data) 66 | } 67 | 68 | // TEST 6: Use sibling nested field after move - should be OK 69 | // @safe 70 | void test_sibling_nested_field_ok() { 71 | Outer o; 72 | o.inner.data = "hello"; 73 | o.inner.value = 42; 74 | 75 | std::string x = std::move(o.inner.data); // Move o.inner.data 76 | int v = o.inner.value; // OK: o.inner.value not moved 77 | } 78 | 79 | int main() { return 0; } 80 | -------------------------------------------------------------------------------- /examples/unified_annotations.cpp: -------------------------------------------------------------------------------- 1 | // Example demonstrating unified @safe/@unsafe annotation rules 2 | // Annotations attach to the next statement/block/function/namespace 3 | 4 | // Example 1: Namespace-level safety (whole file) 5 | // @safe 6 | namespace myapp { 7 | 8 | void func1() { 9 | int value = 42; 10 | int& ref1 = value; 11 | // int& ref2 = value; // ERROR: would be caught 12 | } 13 | 14 | // @unsafe 15 | void unsafe_func() { 16 | // This function is explicitly unsafe, no checking 17 | int value = 42; 18 | int& ref1 = value; 19 | int& ref2 = value; // OK - not checked 20 | } 21 | 22 | void func2() { 23 | // Inherits @safe from namespace 24 | int value = 42; 25 | const int& ref1 = value; 26 | const int& ref2 = value; // OK - multiple const refs 27 | } 28 | } 29 | 30 | // Example 2: Function-level annotations 31 | namespace example2 { 32 | 33 | // Default is unsafe (no annotation) 34 | void unchecked_func() { 35 | int value = 42; 36 | int& ref1 = value; 37 | int& ref2 = value; // Not checked 38 | } 39 | 40 | // @safe 41 | void checked_func() { 42 | int value = 42; 43 | int& ref1 = value; 44 | // int& ref2 = value; // ERROR: would be caught 45 | 46 | // Unsafe block within safe function 47 | // @unsafe 48 | { 49 | int& ref2 = value; // OK in unsafe block 50 | int& ref3 = value; // OK in unsafe block 51 | } 52 | // @endunsafe 53 | 54 | // Back to safe 55 | // int& ref4 = value; // ERROR: would be caught 56 | } 57 | } 58 | 59 | // Example 3: First code element rule 60 | // @safe 61 | int global_var = 42; // Makes whole file safe 62 | 63 | void global_func() { 64 | int value = 42; 65 | int& ref1 = value; 66 | // int& ref2 = value; // ERROR: would be caught 67 | } 68 | 69 | // Example 4: Class-level (future enhancement) 70 | // @safe 71 | class SafeClass { 72 | public: 73 | void method1() { 74 | // Inherits @safe from class 75 | int value = 42; 76 | int& ref1 = value; 77 | // int& ref2 = value; // ERROR: would be caught 78 | } 79 | 80 | // @unsafe 81 | void unsafe_method() { 82 | // Explicitly unsafe method 83 | int value = 42; 84 | int& ref1 = value; 85 | int& ref2 = value; // OK - not checked 86 | } 87 | }; -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(rusty_cpp_tests LANGUAGES CXX) 3 | 4 | include(CTest) 5 | 6 | set(CMAKE_CXX_STANDARD 20) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | set(CMAKE_CXX_EXTENSIONS OFF) 9 | 10 | set(TEST_SOURCES 11 | # Basic Rusty types - ALL WORKING 12 | tests/rusty_arc_test.cpp 13 | tests/rusty_arc_mt_test.cpp 14 | tests/rusty_box_test.cpp 15 | tests/rusty_cell_test.cpp 16 | tests/rusty_option_test.cpp 17 | tests/rusty_rc_test.cpp 18 | tests/rusty_refcell_test.cpp 19 | tests/rusty_result_test.cpp 20 | tests/rusty_vec_test.cpp 21 | tests/rusty_function_test.cpp 22 | tests/test_traits.cpp 23 | tests/test_mutex.cpp 24 | tests/test_thread.cpp 25 | tests/test_channel.cpp 26 | 27 | # Annotation system tests - WORKING (fixed missing includes) 28 | tests/test_external_annotations.cpp 29 | tests/test_simplified_external.cpp 30 | tests/test_stl_lifetimes.cpp 31 | tests/test_unified_annotations.cpp 32 | 33 | # BTreeMap tests - RE-ENABLED (all methods implemented) 34 | tests/rusty_btreemap_test.cpp 35 | 36 | # HashMap/HashSet tests - RE-ENABLED (APIs complete) 37 | tests/rusty_hashmap_test.cpp 38 | tests/rusty_hashset_test.cpp 39 | 40 | # BTreeSet tests - RE-ENABLED (fixed range and iterator) 41 | tests/rusty_btreeset_test.cpp 42 | 43 | # TODO: Re-enable when String API is complete 44 | # tests/rusty_string_test.cpp 45 | # tests/test_all_types.cpp 46 | 47 | # TODO: Re-enable when performance/analysis features are complete 48 | # tests/btreemap_analysis.cpp 49 | # tests/final_btree_test.cpp 50 | # tests/final_performance_test.cpp 51 | ) 52 | 53 | find_package(Threads REQUIRED) 54 | 55 | set(TEST_TARGETS) 56 | foreach(test_source IN LISTS TEST_SOURCES) 57 | get_filename_component(test_name "${test_source}" NAME_WE) 58 | set(test_executable "${test_name}.out") 59 | add_executable(${test_executable} ${test_source}) 60 | target_include_directories(${test_executable} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) 61 | target_compile_options(${test_executable} PRIVATE -Wall -Wextra -Wpedantic) 62 | target_link_libraries(${test_executable} PRIVATE Threads::Threads) 63 | add_test(NAME ${test_name} COMMAND ${test_executable}) 64 | list(APPEND TEST_TARGETS ${test_executable}) 65 | endforeach() 66 | 67 | add_custom_target(run-tests 68 | COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure 69 | DEPENDS ${TEST_TARGETS} 70 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 71 | ) 72 | -------------------------------------------------------------------------------- /examples/header_safety_example/safe_math.cpp: -------------------------------------------------------------------------------- 1 | #include "safe_math.h" 2 | #include 3 | 4 | // Implementation of SafeMath namespace functions 5 | // These inherit the @safe annotation from the namespace in the header 6 | 7 | namespace SafeMath { 8 | int add(int a, int b) { 9 | // This function is safe - no pointer operations allowed 10 | return a + b; 11 | } 12 | 13 | int subtract(int a, int b) { 14 | // Also safe 15 | return a - b; 16 | } 17 | 18 | int multiply(int a, int b) { 19 | // Safe function 20 | return a * b; 21 | } 22 | 23 | int divide(int a, int b) { 24 | // This is marked @unsafe in the header, so pointer operations are allowed 25 | if (b == 0) { 26 | int* error_ptr = nullptr; 27 | *error_ptr = -1; // OK - unsafe function 28 | return 0; 29 | } 30 | return a / b; 31 | } 32 | } 33 | 34 | // SafeCalculator methods inherit safety from the class declaration 35 | 36 | void SafeCalculator::set_value(int val) { 37 | // This is a safe method - inherited from header 38 | value = val; 39 | // int* ptr = &value; // This would be an error - raw pointer in safe function 40 | } 41 | 42 | int SafeCalculator::get_value() const { 43 | // Safe method 44 | return value; 45 | } 46 | 47 | void SafeCalculator::increment() { 48 | // Safe method 49 | value++; 50 | } 51 | 52 | void SafeCalculator::raw_pointer_operation() { 53 | // This is marked unsafe in the header, so pointer operations are allowed 54 | int* ptr = &value; 55 | *ptr = *ptr + 1; 56 | } 57 | 58 | // Individual function implementations 59 | 60 | int safe_factorial(int n) { 61 | // This function is marked @safe in the header 62 | if (n <= 1) return 1; 63 | return n * safe_factorial(n - 1); 64 | // int* ptr = &n; // This would be an error - raw pointer in safe function 65 | } 66 | 67 | void unsafe_memory_operation(void* ptr) { 68 | // This is marked unsafe in the header, pointer operations allowed 69 | int* int_ptr = static_cast(ptr); 70 | if (int_ptr) { 71 | *int_ptr = 42; 72 | } 73 | } 74 | 75 | void regular_function() { 76 | // No annotation in header means unsafe by default 77 | int x = 10; 78 | int* ptr = &x; 79 | *ptr = 20; 80 | } 81 | 82 | // This function is not declared in the header - it's only in the implementation 83 | // It can have its own safety annotation 84 | // @safe 85 | void implementation_only_safe_function() { 86 | int a = 5, b = 10; 87 | int result = SafeMath::add(a, b); 88 | } -------------------------------------------------------------------------------- /examples/test_scope_tracking.cpp: -------------------------------------------------------------------------------- 1 | // Test that scope tracking eliminates false positives 2 | 3 | void test_nested_scopes() { 4 | int value = 42; 5 | 6 | // First scope with a reference 7 | { 8 | int& ref1 = value; 9 | ref1 = 100; 10 | } // ref1 goes out of scope here 11 | 12 | // Second scope with another reference 13 | // This should NOT error - ref1 is gone 14 | { 15 | int& ref2 = value; 16 | ref2 = 200; 17 | } // ref2 goes out of scope here 18 | 19 | // Third reference outside any nested scope 20 | // This should also be OK 21 | int& ref3 = value; 22 | ref3 = 300; 23 | } 24 | 25 | void test_multiple_scopes() { 26 | int x = 10; 27 | int y = 20; 28 | 29 | { 30 | int& rx = x; // Borrow x in this scope 31 | rx = 15; 32 | } // rx out of scope 33 | 34 | { 35 | int& ry = y; // Borrow y in this scope 36 | ry = 25; 37 | } // ry out of scope 38 | 39 | // Should be able to borrow both again 40 | int& rx2 = x; 41 | int& ry2 = y; 42 | rx2 = 30; 43 | ry2 = 40; 44 | } 45 | 46 | void test_nested_blocks() { 47 | int value = 100; 48 | 49 | { 50 | int& ref1 = value; 51 | { 52 | // Nested block - ref1 still exists 53 | // This SHOULD error (double borrow) 54 | int& ref2 = value; 55 | ref2 = 200; 56 | } 57 | ref1 = 150; 58 | } 59 | } 60 | 61 | void test_const_ref_scopes() { 62 | int value = 42; 63 | 64 | { 65 | const int& cref1 = value; 66 | const int& cref2 = value; // Multiple const refs OK 67 | int sum = cref1 + cref2; 68 | } // Both const refs out of scope 69 | 70 | // Now can take mutable ref 71 | int& mref = value; 72 | mref = 100; 73 | } 74 | 75 | // Mock std::move for testing 76 | namespace std { 77 | template 78 | T&& move(T& t) { return static_cast(t); } 79 | } 80 | 81 | void test_move_in_scope() { 82 | int x = 42; 83 | 84 | { 85 | int y = std::move(x); // x is moved 86 | // x is moved but y is only in this scope 87 | } // y goes out of scope 88 | 89 | // x is still moved though! 90 | // int z = x; // This should still error (use after move) 91 | } 92 | 93 | int main() { 94 | test_nested_scopes(); 95 | test_multiple_scopes(); 96 | test_nested_blocks(); 97 | test_const_ref_scopes(); 98 | test_move_in_scope(); 99 | return 0; 100 | } -------------------------------------------------------------------------------- /examples/unsafe_block_example.cpp: -------------------------------------------------------------------------------- 1 | // @safe 2 | // Example demonstrating unsafe blocks within safe functions 3 | 4 | // Functions for marking unsafe regions 5 | // These are just markers for the analyzer, not real functions 6 | void unsafe_begin(); 7 | void unsafe_end(); 8 | 9 | // Alternative: use extern "C" to avoid name mangling 10 | extern "C" { 11 | void UNSAFE_BEGIN(); 12 | void UNSAFE_END(); 13 | } 14 | 15 | // @safe 16 | void mixed_safety_function() { 17 | int value = 42; 18 | 19 | // Safe code - checked for borrow rules 20 | int& ref1 = value; 21 | ref1 = 100; 22 | 23 | // Begin unsafe block 24 | unsafe_begin(); 25 | { 26 | // Unsafe code - no checking 27 | int* ptr = &value; 28 | int* alias = ptr; // Multiple aliases OK in unsafe 29 | *alias = 200; 30 | 31 | // Even dangerous operations not caught 32 | int* heap = new int(42); 33 | delete heap; 34 | // *heap = 300; // Use-after-free not caught in unsafe 35 | } 36 | unsafe_end(); 37 | 38 | // Back to safe code 39 | // int& ref2 = value; // ERROR: would be caught (already borrowed) 40 | } 41 | 42 | void single_unsafe_statement() { 43 | int value = 42; 44 | int& ref = value; 45 | 46 | // Unsafe for a single statement 47 | unsafe_begin(); 48 | int* ptr = &value; // Not checked 49 | unsafe_end(); 50 | 51 | // This is checked again 52 | // int& ref2 = value; // ERROR: already borrowed 53 | } 54 | 55 | // Example with performance-critical code 56 | void performance_critical_with_unsafe() { 57 | int array[1000]; 58 | 59 | // Safe initialization 60 | for (int i = 0; i < 1000; i++) { 61 | array[i] = i; 62 | } 63 | 64 | // Unsafe optimization - skip bounds checking 65 | unsafe_begin(); 66 | { 67 | int* ptr = array; 68 | // Fast, unchecked pointer arithmetic 69 | for (int i = 0; i < 1000; i++) { 70 | *(ptr++) *= 2; // No bounds checking 71 | } 72 | } 73 | unsafe_end(); 74 | } 75 | 76 | // Example: interfacing with C code 77 | extern "C" { 78 | void* legacy_c_function(void* data); 79 | } 80 | 81 | void interface_with_c() { 82 | int value = 42; 83 | 84 | // Need unsafe to work with raw pointers for C interop 85 | unsafe_begin(); 86 | { 87 | void* raw_ptr = &value; 88 | void* result = legacy_c_function(raw_ptr); 89 | // Cast back - unsafe but necessary for C interop 90 | int* typed = static_cast(result); 91 | } 92 | unsafe_end(); 93 | 94 | // Safe code continues 95 | int& ref = value; 96 | } -------------------------------------------------------------------------------- /include/rusty/barrier.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace rusty { 8 | 9 | // Barrier - Synchronization point for multiple threads 10 | // Matches Rust's std::sync::Barrier behavior 11 | // 12 | // Usage: 13 | // Barrier barrier(3); // Wait for 3 threads 14 | // 15 | // // In each thread: 16 | // BarrierWaitResult result = barrier.wait(); 17 | // if (result.is_leader()) { 18 | // // One thread (the leader) will see is_leader() == true 19 | // // This thread can perform cleanup or coordination tasks 20 | // } 21 | // 22 | class Barrier { 23 | private: 24 | std::mutex mtx_; 25 | std::condition_variable cv_; 26 | std::size_t threshold_; 27 | std::size_t count_; 28 | std::size_t generation_; 29 | 30 | public: 31 | // Result of a barrier wait operation 32 | class BarrierWaitResult { 33 | private: 34 | bool is_leader_; 35 | 36 | friend class Barrier; 37 | 38 | explicit BarrierWaitResult(bool leader) : is_leader_(leader) {} 39 | 40 | public: 41 | // Returns true if this thread is the "leader" (last to arrive) 42 | // The leader thread is chosen arbitrarily from the waiting threads 43 | bool is_leader() const { return is_leader_; } 44 | }; 45 | 46 | // Constructor - specify number of threads that must call wait() 47 | explicit Barrier(std::size_t count) 48 | : threshold_(count), count_(count), generation_(0) { 49 | if (count == 0) { 50 | throw std::invalid_argument("Barrier count must be greater than 0"); 51 | } 52 | } 53 | 54 | // Wait for all threads to arrive at the barrier 55 | // Returns a BarrierWaitResult indicating if this thread is the leader 56 | BarrierWaitResult wait() { 57 | std::unique_lock lock(mtx_); 58 | std::size_t gen = generation_; 59 | 60 | if (--count_ == 0) { 61 | // Last thread to arrive - this is the leader 62 | generation_++; 63 | count_ = threshold_; 64 | cv_.notify_all(); 65 | return BarrierWaitResult(true); 66 | } 67 | 68 | // Not the last thread - wait for the leader to release everyone 69 | cv_.wait(lock, [this, gen] { return gen != generation_; }); 70 | return BarrierWaitResult(false); 71 | } 72 | 73 | // Non-copyable, non-movable 74 | Barrier(const Barrier&) = delete; 75 | Barrier& operator=(const Barrier&) = delete; 76 | Barrier(Barrier&&) = delete; 77 | Barrier& operator=(Barrier&&) = delete; 78 | 79 | ~Barrier() = default; 80 | }; 81 | 82 | } // namespace rusty 83 | -------------------------------------------------------------------------------- /examples/cmake_project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(BorrowCheckedProject CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | # Include the borrow checker module 8 | # In a real project, you would install this module or use find_package 9 | include(${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/CppBorrowChecker.cmake) 10 | 11 | # Option 1: Enable borrow checking globally for all targets 12 | # enable_borrow_checking() 13 | 14 | # Option 2: Enable selectively (recommended for gradual adoption) 15 | set(ENABLE_BORROW_CHECKING ON) 16 | 17 | # Optional: Make borrow check failures stop the build 18 | # set(BORROW_CHECK_FATAL ON) 19 | 20 | # Set up compile_commands.json generation for better include path handling 21 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 22 | setup_borrow_checker_compile_commands() 23 | 24 | # Add include directories that the borrow checker should know about 25 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 26 | 27 | # Example library with borrow checking (commented out due to std header issues) 28 | # add_library(safe_lib 29 | # src/safe_string.cpp 30 | # src/memory_manager.cpp 31 | # ) 32 | # 33 | # # Enable borrow checking for the library 34 | # add_borrow_check_target(safe_lib) 35 | 36 | # Simple demo without std headers 37 | add_executable(simple_demo 38 | src/simple_demo.cpp 39 | ) 40 | 41 | # Enable borrow checking for the demo 42 | add_borrow_check(src/simple_demo.cpp) 43 | 44 | # Example executable (commented out due to std header issues) 45 | # add_executable(main_app 46 | # src/main.cpp 47 | # src/utils.cpp 48 | # ) 49 | # 50 | # target_link_libraries(main_app safe_lib) 51 | # 52 | # # Enable borrow checking for specific files only 53 | # add_borrow_check(src/main.cpp) 54 | # add_borrow_check(src/utils.cpp) # Uncomment to check this file too 55 | 56 | # Custom target to check all files at once 57 | add_custom_target(check_all 58 | COMMAND ${CPP_BORROW_CHECKER} 59 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp 60 | -I${CMAKE_CURRENT_SOURCE_DIR}/include 61 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 62 | COMMENT "Running borrow checker on all source files" 63 | VERBATIM 64 | ) 65 | 66 | # Install target (optional) 67 | install(TARGETS simple_demo 68 | RUNTIME DESTINATION bin 69 | ) 70 | 71 | # Install the borrow checker binary (optional) 72 | # install_borrow_checker() 73 | 74 | # Print status 75 | message(STATUS "Borrow checking is ${ENABLE_BORROW_CHECKING}") 76 | if(CPP_BORROW_CHECKER) 77 | message(STATUS "Borrow checker found at: ${CPP_BORROW_CHECKER}") 78 | else() 79 | message(WARNING "Borrow checker not found. Build it with: cargo build --release") 80 | endif() -------------------------------------------------------------------------------- /examples/test_control_flow_bugs.cpp: -------------------------------------------------------------------------------- 1 | // Examples that SHOULD be errors but might not be caught due to control flow limitations 2 | 3 | // Mock std::move 4 | namespace std { 5 | template 6 | T&& move(T& t) { return static_cast(t); } 7 | } 8 | 9 | // CASE 1: Double borrow should be caught but might not be due to scope issues 10 | void false_negative_double_borrow() { 11 | int value = 42; 12 | 13 | // Create two mutable references to the same value 14 | // This SHOULD be an error (violates Rust's borrowing rules) 15 | int& ref1 = value; 16 | int& ref2 = value; // ERROR: Second mutable borrow while first is active! 17 | 18 | ref1 = 100; 19 | ref2 = 200; // Both references used - definitely a problem 20 | } 21 | 22 | // CASE 2: Move in a loop - should error on second iteration 23 | void false_negative_loop_move() { 24 | int x = 42; 25 | 26 | // This loop runs twice 27 | for (int i = 0; i < 2; i++) { 28 | int y = std::move(x); // First iteration OK, second iteration is use-after-move! 29 | // But checker probably doesn't understand loop iterations 30 | } 31 | } 32 | 33 | // CASE 3: Conditional move that definitely happens 34 | void false_negative_conditional_move() { 35 | int x = 42; 36 | bool always_true = true; // This is always true 37 | 38 | if (always_true) { 39 | int y = std::move(x); // x is definitely moved 40 | } 41 | 42 | int z = x; // ERROR: Use after move (since condition is always true) 43 | // But checker can't do path analysis 44 | } 45 | 46 | // CASE 4: False positive - mutually exclusive paths 47 | void false_positive_exclusive_paths() { 48 | int value = 42; 49 | bool condition = true; 50 | 51 | if (condition) { 52 | int& ref1 = value; 53 | ref1 = 100; 54 | // ref1 scope ends here 55 | } 56 | 57 | if (!condition) { 58 | // This branch never executes when above branch does 59 | int& ref2 = value; // Should be OK (mutually exclusive) 60 | ref2 = 200; 61 | // But checker might think both refs exist simultaneously 62 | } 63 | } 64 | 65 | // CASE 5: Scope confusion with blocks 66 | void scope_confusion() { 67 | int value = 42; 68 | 69 | { 70 | int& ref1 = value; 71 | ref1 = 100; 72 | } // ref1 definitely out of scope 73 | 74 | { 75 | int& ref2 = value; // Should be OK - ref1 is gone 76 | ref2 = 200; 77 | } 78 | 79 | // But if checker doesn't understand block scopes, it sees both borrows 80 | } 81 | 82 | int main() { 83 | false_negative_double_borrow(); 84 | false_negative_loop_move(); 85 | false_negative_conditional_move(); 86 | false_positive_exclusive_paths(); 87 | scope_confusion(); 88 | return 0; 89 | } -------------------------------------------------------------------------------- /examples/submodule_project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(MyProjectWithBorrowChecker CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | # Assuming rustycpp is added as a submodule at external/rustycpp 8 | # You would run: git submodule add https://github.com/shuaimu/rustycpp external/rustycpp 9 | 10 | # Include the submodule-friendly CMake integration 11 | include(${CMAKE_CURRENT_SOURCE_DIR}/external/rustycpp/cmake/RustyCppSubmodule.cmake) 12 | 13 | # Configure the borrow checker 14 | set(RUSTYCPP_BUILD_TYPE "release") # or "debug" for faster builds during development 15 | set(ENABLE_BORROW_CHECKING ON) 16 | 17 | # Optional: Make borrow check failures stop the build 18 | # set(BORROW_CHECK_FATAL ON) 19 | 20 | # Enable borrow checking for this project 21 | enable_borrow_checking() 22 | 23 | # Set up compile_commands.json generation for better include path handling 24 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 25 | setup_borrow_checker_compile_commands() 26 | 27 | # Your project's include directories 28 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 29 | 30 | # Example library with borrow checking 31 | add_library(my_safe_lib 32 | src/safe_container.cpp 33 | src/memory_pool.cpp 34 | ) 35 | 36 | # Enable borrow checking for the entire library 37 | add_borrow_check_target(my_safe_lib) 38 | 39 | # Example executable 40 | add_executable(my_app 41 | src/main.cpp 42 | src/application.cpp 43 | ) 44 | 45 | target_link_libraries(my_app my_safe_lib) 46 | 47 | # Option 1: Enable borrow checking for the entire executable 48 | add_borrow_check_target(my_app) 49 | 50 | # Option 2: Enable borrow checking for specific files only 51 | # add_borrow_check(src/main.cpp) 52 | # add_borrow_check(src/application.cpp) 53 | 54 | # Custom target to manually run checks 55 | add_custom_target(check_safety 56 | COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target borrow_check_all 57 | COMMENT "Running all borrow checks" 58 | ) 59 | 60 | # Tests (if you have them) 61 | enable_testing() 62 | add_executable(my_tests 63 | tests/test_main.cpp 64 | tests/test_container.cpp 65 | ) 66 | target_link_libraries(my_tests my_safe_lib) 67 | 68 | # Enable borrow checking for tests 69 | add_borrow_check_target(my_tests) 70 | 71 | add_test(NAME safety_tests COMMAND my_tests) 72 | 73 | # Installation 74 | install(TARGETS my_app 75 | RUNTIME DESTINATION bin 76 | ) 77 | 78 | install(TARGETS my_safe_lib 79 | LIBRARY DESTINATION lib 80 | ARCHIVE DESTINATION lib 81 | ) 82 | 83 | # Print configuration status 84 | message(STATUS "=== Borrow Checker Configuration ===") 85 | message(STATUS "Borrow checking enabled: ${ENABLE_BORROW_CHECKING}") 86 | message(STATUS "Checker build type: ${RUSTYCPP_BUILD_TYPE}") 87 | message(STATUS "Fatal on check failure: ${BORROW_CHECK_FATAL}") 88 | message(STATUS "=====================================") -------------------------------------------------------------------------------- /tests/raii/partial_borrow_test.cpp: -------------------------------------------------------------------------------- 1 | // Test: Partial borrow tracking 2 | // Goal: Track borrows of individual fields separately from whole-struct borrows 3 | 4 | #include 5 | 6 | struct Pair { 7 | std::string first; 8 | std::string second; 9 | }; 10 | 11 | // TEST 1: Borrow different fields mutably - should be OK (Rust allows this) 12 | // @safe 13 | void test_different_fields_mutable_borrow() { 14 | Pair p; 15 | p.first = "hello"; 16 | p.second = "world"; 17 | 18 | std::string& r1 = p.first; // Mutable borrow of p.first 19 | std::string& r2 = p.second; // OK: p.second is separate field 20 | 21 | r1 = "modified"; 22 | r2 = "also modified"; 23 | } 24 | 25 | // TEST 2: Double mutable borrow of same field - should ERROR 26 | // @safe 27 | void test_same_field_double_mutable_borrow() { 28 | Pair p; 29 | p.first = "hello"; 30 | 31 | std::string& r1 = p.first; // Mutable borrow of p.first 32 | std::string& r2 = p.first; // ERROR: p.first already mutably borrowed 33 | 34 | r1 = "modified"; 35 | } 36 | 37 | // TEST 3: Mutable and immutable borrow of same field - should ERROR 38 | // @safe 39 | void test_same_field_mixed_borrow() { 40 | Pair p; 41 | p.first = "hello"; 42 | 43 | const std::string& r1 = p.first; // Immutable borrow of p.first 44 | std::string& r2 = p.first; // ERROR: p.first already borrowed 45 | 46 | r2 = "modified"; 47 | } 48 | 49 | // TEST 4: Immutable borrow field, mutable borrow different field - should be OK 50 | // @safe 51 | void test_mixed_borrows_different_fields() { 52 | Pair p; 53 | p.first = "hello"; 54 | p.second = "world"; 55 | 56 | const std::string& r1 = p.first; // Immutable borrow of p.first 57 | std::string& r2 = p.second; // OK: p.second is separate 58 | 59 | r2 = "modified"; 60 | // r1 still valid for reading 61 | } 62 | 63 | // TEST 5: Multiple immutable borrows of same field - should be OK 64 | // @safe 65 | void test_multiple_immutable_same_field() { 66 | Pair p; 67 | p.first = "hello"; 68 | 69 | const std::string& r1 = p.first; // Immutable borrow of p.first 70 | const std::string& r2 = p.first; // OK: multiple immutable borrows allowed 71 | 72 | // Both r1 and r2 valid for reading 73 | } 74 | 75 | // TEST 6: Whole struct borrow conflicts with field borrow 76 | // @safe 77 | void test_whole_struct_vs_field_borrow() { 78 | Pair p; 79 | p.first = "hello"; 80 | 81 | std::string& r1 = p.first; // Mutable borrow of p.first 82 | Pair& r2 = p; // ERROR: Cannot borrow whole p while p.first borrowed 83 | } 84 | 85 | // TEST 7: Field borrow after whole struct borrow - should ERROR 86 | // @safe 87 | void test_field_borrow_after_whole() { 88 | Pair p; 89 | p.first = "hello"; 90 | 91 | Pair& r1 = p; // Mutable borrow of whole p 92 | std::string& r2 = p.first; // ERROR: Cannot borrow p.first while p borrowed 93 | } 94 | 95 | int main() { return 0; } 96 | -------------------------------------------------------------------------------- /examples/safe_unsafe_demo.cpp: -------------------------------------------------------------------------------- 1 | // Example demonstrating safe/unsafe annotations for gradual adoption 2 | // By default, C++ files are unsafe (no checking) for compatibility 3 | 4 | #include 5 | 6 | // Legacy code - not checked by default 7 | void legacy_function() { 8 | int* ptr = new int(42); 9 | int* alias = ptr; 10 | delete ptr; 11 | // *alias = 100; // Use-after-free but not caught (unsafe by default) 12 | std::cout << "Legacy code runs without checks\n"; 13 | } 14 | 15 | // @safe 16 | // New code with safety checking enabled 17 | void modern_safe_function() { 18 | int value = 42; 19 | int& ref1 = value; 20 | // int& ref2 = value; // ERROR: Would be caught - double mutable borrow 21 | ref1 = 100; 22 | std::cout << "Safe function with borrow checking\n"; 23 | } 24 | 25 | // @safe 26 | // Function that needs to do something unsafe 27 | void mixed_function() { 28 | int value = 42; 29 | int& safe_ref = value; 30 | safe_ref = 100; 31 | 32 | // Sometimes you need to bypass checks for performance or FFI 33 | // @unsafe { 34 | // int* raw = &value; 35 | // int* alias = raw; // Multiple aliases allowed in unsafe block 36 | // *alias = 200; 37 | // } 38 | 39 | std::cout << "Mixed safe/unsafe code\n"; 40 | } 41 | 42 | // @unsafe 43 | // Explicitly unsafe function even if file is compiled with --safe 44 | void explicitly_unsafe() { 45 | int* ptr = new int(42); 46 | delete ptr; 47 | // *ptr = 100; // Use-after-free not caught in @unsafe function 48 | std::cout << "Explicitly unsafe function\n"; 49 | } 50 | 51 | int main() { 52 | std::cout << "Safe/Unsafe Demo\n"; 53 | std::cout << "================\n\n"; 54 | 55 | std::cout << "1. Legacy code (unsafe by default):\n"; 56 | legacy_function(); 57 | 58 | std::cout << "\n2. Modern safe code (with @safe):\n"; 59 | modern_safe_function(); 60 | 61 | std::cout << "\n3. Mixed safe/unsafe code:\n"; 62 | mixed_function(); 63 | 64 | std::cout << "\n4. Explicitly unsafe function:\n"; 65 | explicitly_unsafe(); 66 | 67 | std::cout << "\nTo enable checking for entire file, compile with: --safe flag\n"; 68 | 69 | return 0; 70 | } 71 | 72 | /* 73 | Usage: 74 | ------ 75 | 1. Default (unsafe, no checking): 76 | cargo run -- examples/safe_unsafe_demo.cpp 77 | 78 | 2. Safe mode (check entire file except @unsafe functions): 79 | cargo run -- --safe examples/safe_unsafe_demo.cpp 80 | 81 | 3. Annotations: 82 | - @safe on function: Check this function even in unsafe file 83 | - @unsafe on function: Don't check even in safe file 84 | - Future: @unsafe { ... } blocks within safe functions 85 | 86 | Benefits: 87 | --------- 88 | - Gradual adoption: Start with unsafe, add @safe to new code 89 | - Compatibility: Existing C++ projects work without modification 90 | - Flexibility: Mix safe and unsafe as needed 91 | - Performance: Skip checks in performance-critical sections 92 | */ -------------------------------------------------------------------------------- /src/analysis/lifetimes.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::Lifetime; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug)] 5 | #[allow(dead_code)] 6 | pub struct LifetimeAnalyzer { 7 | lifetimes: HashMap, 8 | constraints: Vec, 9 | } 10 | 11 | #[derive(Debug, Clone)] 12 | #[allow(dead_code)] 13 | pub enum LifetimeConstraint { 14 | Outlives(String, String), // 'a: 'b (a outlives b) 15 | Equal(String, String), // 'a = 'b 16 | } 17 | 18 | impl LifetimeAnalyzer { 19 | #[allow(dead_code)] 20 | pub fn new() -> Self { 21 | Self { 22 | lifetimes: HashMap::new(), 23 | constraints: Vec::new(), 24 | } 25 | } 26 | 27 | #[allow(dead_code)] 28 | pub fn add_lifetime(&mut self, name: String, scope_start: usize, scope_end: usize) { 29 | self.lifetimes.insert( 30 | name.clone(), 31 | Lifetime { 32 | name, 33 | scope_start, 34 | scope_end, 35 | }, 36 | ); 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub fn add_constraint(&mut self, constraint: LifetimeConstraint) { 41 | self.constraints.push(constraint); 42 | } 43 | 44 | #[allow(dead_code)] 45 | pub fn check_constraints(&self) -> Result<(), Vec> { 46 | let mut errors = Vec::new(); 47 | 48 | for constraint in &self.constraints { 49 | match constraint { 50 | LifetimeConstraint::Outlives(longer, shorter) => { 51 | if let (Some(l1), Some(l2)) = (self.lifetimes.get(longer), self.lifetimes.get(shorter)) { 52 | if l1.scope_end < l2.scope_end { 53 | errors.push(format!( 54 | "Lifetime '{}' does not outlive '{}'", 55 | longer, shorter 56 | )); 57 | } 58 | } 59 | } 60 | LifetimeConstraint::Equal(a, b) => { 61 | if let (Some(l1), Some(l2)) = (self.lifetimes.get(a), self.lifetimes.get(b)) { 62 | if l1.scope_start != l2.scope_start || l1.scope_end != l2.scope_end { 63 | errors.push(format!( 64 | "Lifetimes '{}' and '{}' are not equal", 65 | a, b 66 | )); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | if errors.is_empty() { 74 | Ok(()) 75 | } else { 76 | Err(errors) 77 | } 78 | } 79 | 80 | #[allow(dead_code)] 81 | pub fn infer_lifetimes(&mut self) -> HashMap { 82 | // Simple lifetime inference algorithm 83 | // In a real implementation, this would use a constraint solver 84 | self.lifetimes.clone() 85 | } 86 | } -------------------------------------------------------------------------------- /src/analysis/borrows.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::BorrowKind; 2 | use std::collections::{HashMap, HashSet}; 3 | 4 | #[derive(Debug, Clone)] 5 | #[allow(dead_code)] 6 | pub struct BorrowChecker { 7 | active_borrows: HashMap, 8 | } 9 | 10 | #[derive(Debug, Clone, Default)] 11 | #[allow(dead_code)] 12 | struct ActiveBorrows { 13 | immutable: HashSet, 14 | mutable: Option, 15 | } 16 | 17 | impl BorrowChecker { 18 | #[allow(dead_code)] 19 | pub fn new() -> Self { 20 | Self { 21 | active_borrows: HashMap::new(), 22 | } 23 | } 24 | 25 | #[allow(dead_code)] 26 | pub fn check_borrow(&self, target: &str, kind: &BorrowKind) -> Result<(), String> { 27 | if let Some(borrows) = self.active_borrows.get(target) { 28 | match kind { 29 | BorrowKind::Immutable => { 30 | if borrows.mutable.is_some() { 31 | return Err(format!( 32 | "Cannot borrow '{}' as immutable while it's mutably borrowed", 33 | target 34 | )); 35 | } 36 | } 37 | BorrowKind::Mutable => { 38 | if !borrows.immutable.is_empty() { 39 | return Err(format!( 40 | "Cannot borrow '{}' as mutable while it has {} immutable borrow(s)", 41 | target, 42 | borrows.immutable.len() 43 | )); 44 | } 45 | if borrows.mutable.is_some() { 46 | return Err(format!( 47 | "Cannot borrow '{}' as mutable while it's already mutably borrowed", 48 | target 49 | )); 50 | } 51 | } 52 | } 53 | } 54 | Ok(()) 55 | } 56 | 57 | #[allow(dead_code)] 58 | pub fn add_borrow(&mut self, target: String, borrower: String, kind: BorrowKind) { 59 | let borrows = self.active_borrows.entry(target).or_default(); 60 | 61 | match kind { 62 | BorrowKind::Immutable => { 63 | borrows.immutable.insert(borrower); 64 | } 65 | BorrowKind::Mutable => { 66 | borrows.mutable = Some(borrower); 67 | } 68 | } 69 | } 70 | 71 | #[allow(dead_code)] 72 | pub fn release_borrow(&mut self, target: &str, borrower: &str) { 73 | if let Some(borrows) = self.active_borrows.get_mut(target) { 74 | borrows.immutable.remove(borrower); 75 | if borrows.mutable.as_ref() == Some(&borrower.to_string()) { 76 | borrows.mutable = None; 77 | } 78 | 79 | // Remove entry if no active borrows 80 | if borrows.immutable.is_empty() && borrows.mutable.is_none() { 81 | self.active_borrows.remove(target); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /include/rusty/rc/weak.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_RC_WEAK_HPP 2 | #define RUSTY_RC_WEAK_HPP 3 | 4 | #include 5 | 6 | #include "../option.hpp" 7 | #include "../rc.hpp" 8 | 9 | namespace rusty { 10 | namespace rc { 11 | 12 | template 13 | class Weak { 14 | private: 15 | friend class rusty::Rc; 16 | 17 | typename rusty::Rc::ControlBlock* ptr; 18 | 19 | Weak(typename rusty::Rc::ControlBlock* p, bool add_ref) 20 | : ptr(p) { 21 | if (ptr && add_ref) { 22 | ++ptr->weak_count; 23 | } 24 | } 25 | 26 | public: 27 | Weak() : ptr(nullptr) {} 28 | 29 | explicit Weak(const rusty::Rc& rc) 30 | : ptr(rc.control_) { 31 | if (ptr) { 32 | ++ptr->weak_count; 33 | } 34 | } 35 | 36 | Weak(const Weak& other) 37 | : ptr(other.ptr) { 38 | if (ptr) { 39 | ++ptr->weak_count; 40 | } 41 | } 42 | 43 | Weak(Weak&& other) noexcept 44 | : ptr(other.ptr) { 45 | other.ptr = nullptr; 46 | } 47 | 48 | ~Weak() { 49 | reset(); 50 | } 51 | 52 | Weak& operator=(const Weak& other) { 53 | if (this != &other) { 54 | reset(); 55 | ptr = other.ptr; 56 | if (ptr) { 57 | ++ptr->weak_count; 58 | } 59 | } 60 | return *this; 61 | } 62 | 63 | Weak& operator=(Weak&& other) noexcept { 64 | if (this != &other) { 65 | reset(); 66 | ptr = other.ptr; 67 | other.ptr = nullptr; 68 | } 69 | return *this; 70 | } 71 | 72 | Weak& operator=(const rusty::Rc& rc) { 73 | reset(); 74 | ptr = rc.control_; 75 | if (ptr) { 76 | ++ptr->weak_count; 77 | } 78 | return *this; 79 | } 80 | 81 | void reset() { 82 | if (ptr) { 83 | rusty::Rc::release_weak(ptr); 84 | ptr = nullptr; 85 | } 86 | } 87 | 88 | Option> upgrade() const { 89 | if (!ptr || ptr->strong_count == 0) { 90 | return ::rusty::None; 91 | } 92 | T* value_ptr = static_cast(ptr->get_value_ptr()); 93 | return ::rusty::Some(rusty::Rc(value_ptr, ptr, true)); 94 | } 95 | 96 | bool expired() const { 97 | return !ptr || ptr->strong_count == 0; 98 | } 99 | 100 | size_t strong_count() const { 101 | return ptr ? ptr->strong_count : 0; 102 | } 103 | 104 | size_t weak_count() const { 105 | if (!ptr) { 106 | return 0; 107 | } 108 | return ptr->weak_count > 0 ? ptr->weak_count - 1 : 0; 109 | } 110 | 111 | Weak clone() const { 112 | return Weak(*this); 113 | } 114 | }; 115 | 116 | // Downgrade function - create weak from strong 117 | // @safe 118 | template 119 | Weak downgrade(const rusty::Rc& rc) { 120 | return Weak(rc); 121 | } 122 | 123 | } // namespace rc 124 | } // namespace rusty 125 | 126 | #endif // RUSTY_RC_WEAK_HPP 127 | -------------------------------------------------------------------------------- /include/rusty/rusty.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_HPP 2 | #define RUSTY_HPP 3 | 4 | // Rusty - Rust-inspired safe types for C++ 5 | // 6 | // This library provides Rust-like types with proper lifetime annotations 7 | // that work with the Rusty C++ Checker to ensure memory safety. 8 | // 9 | // All types follow Rust's ownership and borrowing principles: 10 | // - Single ownership (Box, Vec) 11 | // - Shared immutable access (Rc, Arc) with built-in polymorphism support 12 | // - Explicit nullability (Option) 13 | // - Explicit error handling (Result) 14 | 15 | // #include "rusty/std_minimal.hpp" // Not needed with standard library 16 | #include "rusty/box.hpp" 17 | #include "rusty/arc.hpp" // Unified Arc with polymorphism (like std::shared_ptr) 18 | #include "rusty/rc.hpp" // Unified Rc with polymorphism (like std::shared_ptr) 19 | #include "rusty/weak.hpp" // Compatibility aliases (Weak for Rc, ArcWeak for Arc) 20 | // TODO: Enable once namespace conflicts are resolved 21 | // #include "rusty/rc/weak.hpp" // Namespace-organized: rusty::rc_impl::Weak 22 | // #include "rusty/sync/weak.hpp" // Namespace-organized: rusty::sync_impl::Weak 23 | #include "rusty/vec.hpp" 24 | #include "rusty/option.hpp" 25 | #include "rusty/result.hpp" 26 | #include "rusty/cell.hpp" 27 | #include "rusty/refcell.hpp" 28 | #include "rusty/string.hpp" 29 | #include "rusty/hashmap.hpp" 30 | #include "rusty/hashset.hpp" 31 | #include "rusty/btreemap.hpp" 32 | #include "rusty/btreeset.hpp" 33 | 34 | // Synchronization primitives (std::sync equivalent) 35 | #include "rusty/mutex.hpp" 36 | #include "rusty/rwlock.hpp" 37 | #include "rusty/condvar.hpp" 38 | #include "rusty/barrier.hpp" 39 | #include "rusty/once.hpp" 40 | 41 | // Convenience aliases in rusty namespace 42 | // @safe 43 | namespace rusty { 44 | // Common Result types 45 | template 46 | using ResultVoid = Result; 47 | 48 | template 49 | using ResultString = Result; 50 | 51 | template 52 | using ResultInt = Result; 53 | 54 | // Smart pointer conversions (Rust-idiomatic names) 55 | template 56 | // @lifetime: owned 57 | Box from_raw(T* ptr) { 58 | return Box(ptr); 59 | } 60 | 61 | // C++ style alias 62 | template 63 | // @lifetime: owned 64 | Box box_from_raw(T* ptr) { 65 | return from_raw(ptr); 66 | } 67 | 68 | template 69 | // @lifetime: owned 70 | Arc arc_from_box(Box&& box) { 71 | T* ptr = box.into_raw(); 72 | Arc result = Arc::new_(std::move(*ptr)); 73 | delete ptr; 74 | return result; 75 | } 76 | 77 | template 78 | // @lifetime: owned 79 | Rc rc_from_box(Box&& box) { 80 | T* ptr = box.into_raw(); 81 | Rc result = Rc::new_(std::move(*ptr)); 82 | delete ptr; 83 | return result; 84 | } 85 | 86 | // Rust-style type aliases for convenience 87 | template 88 | using Boxed = Box; 89 | 90 | template 91 | using Shared = Arc; 92 | 93 | template 94 | using RefCounted = Rc; 95 | } 96 | 97 | #endif // RUSTY_HPP -------------------------------------------------------------------------------- /tests/raii/return_ref_to_local.cpp: -------------------------------------------------------------------------------- 1 | // Test: Return Reference to Local 2 | // Status: NOT DETECTED (requires RAII tracking Phase 1) 3 | // 4 | // This is one of the most common memory safety bugs in C++. 5 | // Rust prevents this at compile time; we should too. 6 | 7 | #include 8 | 9 | // ============================================================================= 10 | // NEGATIVE TESTS - Should produce errors after implementation 11 | // ============================================================================= 12 | 13 | // @safe 14 | std::string& bad_return_ref() { 15 | std::string s = "hello"; 16 | return s; // ERROR: returning reference to local 's' that will be destroyed 17 | } 18 | 19 | // @safe 20 | const std::string& bad_return_const_ref() { 21 | std::string s = "world"; 22 | return s; // ERROR: returning const reference to local 23 | } 24 | 25 | // @safe 26 | int& bad_return_int_ref() { 27 | int x = 42; 28 | return x; // ERROR: returning reference to local int 29 | } 30 | 31 | // @safe 32 | int* bad_return_ptr_to_local() { 33 | int x = 42; 34 | // @unsafe 35 | return &x; // ERROR: returning pointer to local (even in unsafe, should warn) 36 | } 37 | 38 | // @safe 39 | std::string& bad_conditional_return(bool condition) { 40 | std::string a = "a"; 41 | std::string b = "b"; 42 | if (condition) { 43 | return a; // ERROR: 'a' is local 44 | } else { 45 | return b; // ERROR: 'b' is local 46 | } 47 | } 48 | 49 | // @safe 50 | const char* bad_return_c_str() { 51 | std::string s = "temporary"; 52 | return s.c_str(); // ERROR: s destroyed, c_str() points to freed memory 53 | } 54 | 55 | // More subtle: returning reference through a chain 56 | struct Wrapper { 57 | std::string value; 58 | std::string& get() { return value; } 59 | }; 60 | 61 | // @safe 62 | std::string& bad_return_through_member() { 63 | Wrapper w; 64 | w.value = "test"; 65 | return w.get(); // ERROR: w destroyed, w.value destroyed 66 | } 67 | 68 | // ============================================================================= 69 | // POSITIVE TESTS - Should NOT produce errors 70 | // ============================================================================= 71 | 72 | // @safe 73 | std::string good_return_by_value() { 74 | std::string s = "hello"; 75 | return s; // OK: return by value (move semantics) 76 | } 77 | 78 | // @safe 79 | std::string good_return_literal() { 80 | return "hello"; // OK: string literal has static lifetime 81 | } 82 | 83 | // @safe 84 | int good_return_copy() { 85 | int x = 42; 86 | return x; // OK: return by value (copy) 87 | } 88 | 89 | // Static storage - OK to return reference 90 | static std::string global_string = "global"; 91 | 92 | // @safe 93 | std::string& good_return_static_ref() { 94 | return global_string; // OK: global has static lifetime 95 | } 96 | 97 | // Parameter reference - OK if caller owns it 98 | // @safe 99 | std::string& good_return_param_ref(std::string& input) { 100 | return input; // OK: caller owns the string 101 | } 102 | 103 | // @safe 104 | const std::string& good_return_param_const_ref(const std::string& input) { 105 | return input; // OK: just forwarding the reference 106 | } 107 | -------------------------------------------------------------------------------- /include/rusty/cell.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_CELL_HPP 2 | #define RUSTY_CELL_HPP 3 | 4 | #include 5 | #include 6 | #include "unsafe_cell.hpp" 7 | 8 | // Cell - Interior mutability for Copy types 9 | // Provides interior mutability for types that implement Copy 10 | // 11 | // Guarantees: 12 | // - Single-threaded only (not thread-safe) 13 | // - No runtime borrow checking (unlike RefCell) 14 | // - Only for types that can be copied bitwise 15 | // - Zero overhead - uses UnsafeCell internally 16 | 17 | // @safe 18 | namespace rusty { 19 | 20 | // @safe - Cell provides interior mutability for Copy types 21 | template 22 | class Cell { 23 | static_assert(std::is_trivially_copyable_v, 24 | "Cell requires T to be trivially copyable (similar to Rust's Copy trait)"); 25 | private: 26 | UnsafeCell value; 27 | 28 | public: 29 | // @safe - Constructors 30 | Cell() : value() {} 31 | // @safe 32 | explicit Cell(T val) : value(val) {} 33 | 34 | // @safe - Get a copy of the value 35 | // @lifetime: (&'a) -> T 36 | T get() const { 37 | // @unsafe 38 | { return *value.get(); } 39 | } 40 | 41 | // @safe - Set the value 42 | // @lifetime: (&'a, T) -> void 43 | void set(T val) const { 44 | // @unsafe 45 | { *value.get() = val; } 46 | } 47 | 48 | // @safe - Replace the value and return the old one 49 | // @lifetime: (&'a, T) -> T 50 | T replace(T val) const { 51 | // @unsafe 52 | { 53 | T old = *value.get(); 54 | *value.get() = val; 55 | return old; 56 | } 57 | } 58 | 59 | // @safe - Swap the values of two cells 60 | // @lifetime: (&'a, &'a) -> void 61 | void swap(Cell& other) const { 62 | // @unsafe 63 | { 64 | T temp = *value.get(); 65 | *value.get() = *other.value.get(); 66 | *other.value.get() = temp; 67 | } 68 | } 69 | 70 | // @safe - Take the value, leaving Default::default() in its place 71 | // Only available if T has a default constructor 72 | template 73 | typename std::enable_if_t, T> 74 | take() const { 75 | return replace(T{}); 76 | } 77 | 78 | // @unsafe - Get a raw pointer to the value (unsafe in Rust, but needed for C++ interop) 79 | // @lifetime: (&'a) -> *mut T where return: 'a 80 | T* get_mut() const { 81 | return value.get(); 82 | } 83 | 84 | // @safe - Update the value using a function 85 | // @lifetime: (&'a, F) -> void 86 | template 87 | void update(F f) const { 88 | // @unsafe 89 | { 90 | T* ptr = value.get(); 91 | *ptr = f(*ptr); 92 | } 93 | } 94 | 95 | // No copy or move - Cell itself is not copyable/movable 96 | // (though the inner value is) 97 | Cell(const Cell&) = delete; 98 | Cell& operator=(const Cell&) = delete; 99 | Cell(Cell&&) = delete; 100 | Cell& operator=(Cell&&) = delete; 101 | }; 102 | 103 | // @safe - Helper function to create a Cell 104 | template 105 | Cell make_cell(T value) { 106 | return Cell(value); 107 | } 108 | 109 | } // namespace rusty 110 | 111 | #endif // RUSTY_CELL_HPP -------------------------------------------------------------------------------- /tests/test_scope_tracking.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::fs; 3 | 4 | #[test] 5 | fn test_scope_tracking_eliminates_false_positives() { 6 | // This code SHOULD NOT have any errors with scope tracking 7 | let test_code = r#" 8 | void test() { 9 | int value = 42; 10 | 11 | // First scope 12 | { 13 | int& ref1 = value; 14 | ref1 = 100; 15 | } // ref1 out of scope 16 | 17 | // Second scope - should be OK 18 | { 19 | int& ref2 = value; 20 | ref2 = 200; 21 | } // ref2 out of scope 22 | } 23 | "#; 24 | 25 | fs::write("test_scopes.cpp", test_code).unwrap(); 26 | 27 | let output = Command::new("cargo") 28 | .args(&["run", "--", "test_scopes.cpp"]) 29 | 30 | .output() 31 | .expect("Failed to run borrow checker"); 32 | 33 | let stdout = String::from_utf8_lossy(&output.stdout); 34 | 35 | // Should find NO violations 36 | assert!(stdout.contains("no violations found") || 37 | stdout.contains("✓"), 38 | "Should not report false positives for scoped references. Output: {}", stdout); 39 | 40 | // Clean up 41 | let _ = fs::remove_file("test_scopes.cpp"); 42 | } 43 | 44 | #[test] 45 | fn test_scope_tracking_still_catches_real_errors() { 46 | // This code SHOULD have an error 47 | let test_code = r#" 48 | void test() { 49 | int value = 42; 50 | int& ref1 = value; 51 | int& ref2 = value; // ERROR: double mutable borrow 52 | } 53 | "#; 54 | 55 | fs::write("test_double_borrow.cpp", test_code).unwrap(); 56 | 57 | let output = Command::new("cargo") 58 | .args(&["run", "--", "test_double_borrow.cpp"]) 59 | 60 | .output() 61 | .expect("Failed to run borrow checker"); 62 | 63 | let stdout = String::from_utf8_lossy(&output.stdout); 64 | 65 | // Should find the violation 66 | assert!(stdout.contains("violation") || stdout.contains("already mutably borrowed"), 67 | "Should still catch real double borrows. Output: {}", stdout); 68 | 69 | // Clean up 70 | let _ = fs::remove_file("test_double_borrow.cpp"); 71 | } 72 | 73 | #[test] 74 | fn test_nested_scope_tracking() { 75 | let test_code = r#" 76 | void test() { 77 | int value = 42; 78 | 79 | { 80 | const int& cref1 = value; 81 | { 82 | const int& cref2 = value; // Nested const refs - OK 83 | int sum = cref1 + cref2; 84 | } 85 | } 86 | 87 | // All const refs gone 88 | int& mref = value; // Should be OK 89 | mref = 100; 90 | } 91 | "#; 92 | 93 | fs::write("test_nested.cpp", test_code).unwrap(); 94 | 95 | let output = Command::new("cargo") 96 | .args(&["run", "--", "test_nested.cpp"]) 97 | 98 | .output() 99 | .expect("Failed to run borrow checker"); 100 | 101 | let stdout = String::from_utf8_lossy(&output.stdout); 102 | 103 | // Should find NO violations 104 | assert!(stdout.contains("no violations found") || 105 | stdout.contains("✓") || 106 | !stdout.contains("violation"), 107 | "Nested scopes should work correctly. Output: {}", stdout); 108 | 109 | // Clean up 110 | let _ = fs::remove_file("test_nested.cpp"); 111 | } -------------------------------------------------------------------------------- /include/rusty/std_minimal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_STD_MINIMAL_HPP 2 | #define RUSTY_STD_MINIMAL_HPP 3 | 4 | // Minimal std:: declarations for rusty types 5 | // This allows our types to compile without full std library 6 | 7 | namespace std { 8 | 9 | // Forward declarations for move semantics 10 | template 11 | T&& move(T& t) noexcept { 12 | return static_cast(t); 13 | } 14 | 15 | template 16 | T&& forward(T& t) noexcept { 17 | return static_cast(t); 18 | } 19 | 20 | // Memory fence for atomic operations 21 | enum memory_order { 22 | memory_order_relaxed, 23 | memory_order_acquire, 24 | memory_order_release, 25 | memory_order_acq_rel, 26 | memory_order_seq_cst 27 | }; 28 | 29 | inline void atomic_thread_fence(memory_order) {} 30 | 31 | // Minimal atomic for Arc 32 | template 33 | class atomic { 34 | T value; 35 | public: 36 | atomic(T v = T()) : value(v) {} 37 | 38 | T load(memory_order = memory_order_seq_cst) const { 39 | return value; 40 | } 41 | 42 | void store(T v, memory_order = memory_order_seq_cst) { 43 | value = v; 44 | } 45 | 46 | T fetch_add(T v, memory_order = memory_order_seq_cst) { 47 | T old = value; 48 | value += v; 49 | return old; 50 | } 51 | 52 | T fetch_sub(T v, memory_order = memory_order_seq_cst) { 53 | T old = value; 54 | value -= v; 55 | return old; 56 | } 57 | }; 58 | 59 | // Minimal variant for Result (simplified) 60 | template 61 | class variant { 62 | union { 63 | T t_val; 64 | E e_val; 65 | }; 66 | bool is_t; 67 | 68 | public: 69 | variant() : is_t(true) {} 70 | variant(T t) : t_val(t), is_t(true) {} 71 | variant(E e) : e_val(e), is_t(false) {} 72 | 73 | variant& operator=(T t) { 74 | if (!is_t && sizeof(E) > 0) e_val.~E(); 75 | t_val = t; 76 | is_t = true; 77 | return *this; 78 | } 79 | 80 | variant& operator=(E e) { 81 | if (is_t && sizeof(T) > 0) t_val.~T(); 82 | e_val = e; 83 | is_t = false; 84 | return *this; 85 | } 86 | }; 87 | 88 | template 89 | T& get(variant& v) { 90 | return v.t_val; // Simplified, no checking 91 | } 92 | 93 | template 94 | E& get(variant& v) { 95 | return v.e_val; // Simplified, no checking 96 | } 97 | 98 | // initializer_list for Vec 99 | template 100 | class initializer_list { 101 | const T* ptr; 102 | size_t len; 103 | 104 | public: 105 | initializer_list() : ptr(nullptr), len(0) {} 106 | initializer_list(const T* p, size_t l) : ptr(p), len(l) {} 107 | 108 | const T* begin() const { return ptr; } 109 | const T* end() const { return ptr + len; } 110 | size_t size() const { return len; } 111 | }; 112 | } 113 | 114 | #endif // RUSTY_STD_MINIMAL_HPP -------------------------------------------------------------------------------- /tests/test_std_move.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::fs; 3 | 4 | #[test] 5 | fn test_std_move_basic() { 6 | // Create a test file with std::move 7 | let test_code = r#" 8 | #include 9 | #include 10 | 11 | // @safe 12 | void test() { 13 | std::unique_ptr ptr(new int(42)); 14 | std::unique_ptr ptr2 = std::move(ptr); 15 | 16 | // This should error - use after move (requires dereference tracking) 17 | *ptr = 100; 18 | } 19 | "#; 20 | 21 | fs::write("test_move_basic.cpp", test_code).unwrap(); 22 | 23 | let output = Command::new("cargo") 24 | .args(&["run", "--", "test_move_basic.cpp"]) 25 | .output() 26 | .expect("Failed to run borrow checker"); 27 | 28 | let stdout = String::from_utf8_lossy(&output.stdout); 29 | let stderr = String::from_utf8_lossy(&output.stderr); 30 | 31 | // Should detect use after move 32 | assert!(stdout.contains("Use after move") || stderr.contains("Use after move"), 33 | "Should detect use after move. Output: {}\nError: {}", stdout, stderr); 34 | 35 | // Clean up 36 | let _ = fs::remove_file("test_move_basic.cpp"); 37 | } 38 | 39 | #[test] 40 | fn test_std_move_in_function_call() { 41 | let test_code = r#" 42 | #include 43 | #include 44 | 45 | void consume(std::unique_ptr p); 46 | 47 | // @safe 48 | void test() { 49 | std::unique_ptr ptr(new int(42)); 50 | consume(std::move(ptr)); 51 | 52 | // This should error - use after move (requires if-condition tracking) 53 | if (ptr) { 54 | *ptr = 100; 55 | } 56 | } 57 | "#; 58 | 59 | fs::write("test_move_call.cpp", test_code).unwrap(); 60 | 61 | let output = Command::new("cargo") 62 | .args(&["run", "--", "test_move_call.cpp"]) 63 | .output() 64 | .expect("Failed to run borrow checker"); 65 | 66 | let stdout = String::from_utf8_lossy(&output.stdout); 67 | let stderr = String::from_utf8_lossy(&output.stderr); 68 | 69 | // Should detect use after move 70 | assert!(stdout.contains("Use after move") || stdout.contains("has been moved") || 71 | stderr.contains("Use after move") || stderr.contains("has been moved"), 72 | "Should detect use after move in function call. Output: {}\nError: {}", stdout, stderr); 73 | 74 | // Clean up 75 | let _ = fs::remove_file("test_move_call.cpp"); 76 | } 77 | 78 | #[test] 79 | fn test_move_and_reassign() { 80 | let test_code = r#" 81 | #include 82 | #include 83 | 84 | void test() { 85 | std::unique_ptr ptr(new int(42)); 86 | std::unique_ptr ptr2 = std::move(ptr); 87 | 88 | // Reassign after move - this should be OK 89 | ptr = std::unique_ptr(new int(100)); 90 | 91 | // Now ptr is valid again - should NOT error 92 | *ptr = 200; 93 | } 94 | "#; 95 | 96 | fs::write("test_move_reassign.cpp", test_code).unwrap(); 97 | 98 | let output = Command::new("cargo") 99 | .args(&["run", "--", "test_move_reassign.cpp"]) 100 | .output() 101 | .expect("Failed to run borrow checker"); 102 | 103 | let stdout = String::from_utf8_lossy(&output.stdout); 104 | 105 | // This is a limitation - we might incorrectly flag the last use 106 | // For now, we just check that the tool runs without crashing 107 | assert!(output.status.code().is_some(), 108 | "Tool should complete. Output: {}", stdout); 109 | 110 | // Clean up 111 | let _ = fs::remove_file("test_move_reassign.cpp"); 112 | } -------------------------------------------------------------------------------- /examples/test_arc_ffi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../include/rusty/rusty.hpp" 4 | 5 | using namespace rusty; 6 | 7 | struct Animal { 8 | std::string name; 9 | virtual ~Animal() = default; 10 | virtual void speak() const = 0; 11 | }; 12 | 13 | struct Dog : public Animal { 14 | int age; 15 | 16 | Dog(std::string n, int a) : age(a) { 17 | name = std::move(n); 18 | std::cout << "Dog(" << name << ") created\n"; 19 | } 20 | 21 | ~Dog() override { 22 | std::cout << "Dog(" << name << ") destroyed\n"; 23 | } 24 | 25 | void speak() const override { 26 | std::cout << name << " says: Woof! (age " << age << ")\n"; 27 | } 28 | }; 29 | 30 | // Simulate passing Arc across FFI boundary 31 | void test_ffi_round_trip() { 32 | std::cout << "\n=== Test FFI Round Trip with into_raw_parts/from_raw_parts ===\n\n"; 33 | 34 | // Create Arc 35 | Arc dog = Arc::make("Buddy", 5); 36 | std::cout << "Created Arc, strong_count=" << dog.strong_count() << "\n"; 37 | 38 | // Convert to raw parts (like passing to C code) 39 | auto parts = std::move(dog).into_raw_parts(); 40 | std::cout << "Converted to raw parts, dog is now invalid\n"; 41 | std::cout << "Dog still alive (not destroyed yet)\n"; 42 | 43 | // Simulate some C code using the raw pointer 44 | parts.ptr->speak(); 45 | 46 | // Reconstruct Arc from raw parts (like returning from C code) 47 | Arc dog2 = Arc::from_raw_parts(parts.ptr, parts.control); 48 | std::cout << "Reconstructed Arc, strong_count=" << dog2.strong_count() << "\n"; 49 | 50 | // Use the reconstructed Arc 51 | dog2->speak(); 52 | 53 | std::cout << "\n=== Test Complete ===\n"; 54 | // Dog should be destroyed here when dog2 goes out of scope 55 | } 56 | 57 | // Test adopt() for taking ownership of existing raw pointer 58 | void test_adopt() { 59 | std::cout << "\n=== Test Arc::adopt() ===\n\n"; 60 | 61 | // Simulate legacy code that returns raw pointer 62 | Dog* raw_dog = new Dog("Charlie", 3); 63 | std::cout << "Created raw Dog*\n"; 64 | 65 | // Adopt the raw pointer into Arc 66 | Arc dog = Arc::adopt(raw_dog); 67 | std::cout << "Adopted into Arc, strong_count=" << dog.strong_count() << "\n"; 68 | 69 | dog->speak(); 70 | 71 | std::cout << "\n=== Test Complete ===\n"; 72 | // Dog will be properly destroyed when Arc goes out of scope 73 | } 74 | 75 | // Test polymorphic FFI 76 | void test_polymorphic_ffi() { 77 | std::cout << "\n=== Test Polymorphic FFI ===\n\n"; 78 | 79 | // Create Arc 80 | Arc dog = Arc::make("Max", 7); 81 | std::cout << "Created Arc\n"; 82 | 83 | // Convert to Arc (polymorphic) 84 | Arc animal = dog; 85 | std::cout << "Converted to Arc, strong_count=" << animal.strong_count() << "\n"; 86 | 87 | // Export polymorphic Arc to FFI 88 | auto parts = std::move(animal).into_raw_parts(); 89 | std::cout << "Exported Arc to raw parts\n"; 90 | 91 | // Use through base pointer 92 | parts.ptr->speak(); 93 | 94 | // Import back as base type 95 | Arc animal2 = Arc::from_raw_parts(parts.ptr, parts.control); 96 | std::cout << "Imported back as Arc, strong_count=" << animal2.strong_count() << "\n"; 97 | 98 | animal2->speak(); 99 | 100 | std::cout << "\n=== Test Complete ===\n"; 101 | } 102 | 103 | int main() { 104 | test_ffi_round_trip(); 105 | test_adopt(); 106 | test_polymorphic_ffi(); 107 | 108 | std::cout << "\n=== All Tests Passed ===\n"; 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /include/rusty/sync/weak.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_SYNC_WEAK_HPP 2 | #define RUSTY_SYNC_WEAK_HPP 3 | 4 | #include 5 | 6 | #include "../option.hpp" 7 | #include "../arc.hpp" 8 | 9 | namespace rusty { 10 | namespace sync { 11 | 12 | template 13 | class Weak { 14 | private: 15 | friend class rusty::Arc; 16 | 17 | typename rusty::Arc::ControlBlock* ptr; 18 | 19 | Weak(typename rusty::Arc::ControlBlock* p, bool add_ref) 20 | : ptr(p) { 21 | if (ptr && add_ref) { 22 | ptr->weak_count.fetch_add(1, std::memory_order_relaxed); 23 | } 24 | } 25 | 26 | public: 27 | Weak() : ptr(nullptr) {} 28 | 29 | explicit Weak(const rusty::Arc& arc) 30 | : ptr(arc.ptr) { 31 | if (ptr) { 32 | ptr->weak_count.fetch_add(1, std::memory_order_relaxed); 33 | } 34 | } 35 | 36 | Weak(const Weak& other) 37 | : ptr(other.ptr) { 38 | if (ptr) { 39 | ptr->weak_count.fetch_add(1, std::memory_order_relaxed); 40 | } 41 | } 42 | 43 | Weak(Weak&& other) noexcept 44 | : ptr(other.ptr) { 45 | other.ptr = nullptr; 46 | } 47 | 48 | ~Weak() { 49 | reset(); 50 | } 51 | 52 | Weak& operator=(const Weak& other) { 53 | if (this != &other) { 54 | reset(); 55 | ptr = other.ptr; 56 | if (ptr) { 57 | ptr->weak_count.fetch_add(1, std::memory_order_relaxed); 58 | } 59 | } 60 | return *this; 61 | } 62 | 63 | Weak& operator=(Weak&& other) noexcept { 64 | if (this != &other) { 65 | reset(); 66 | ptr = other.ptr; 67 | other.ptr = nullptr; 68 | } 69 | return *this; 70 | } 71 | 72 | Weak& operator=(const rusty::Arc& arc) { 73 | reset(); 74 | ptr = arc.ptr; 75 | if (ptr) { 76 | ptr->weak_count.fetch_add(1, std::memory_order_relaxed); 77 | } 78 | return *this; 79 | } 80 | 81 | void reset() { 82 | if (ptr) { 83 | rusty::Arc::release_weak(ptr); 84 | ptr = nullptr; 85 | } 86 | } 87 | 88 | Option> upgrade() const { 89 | if (!ptr) { 90 | return ::rusty::None; 91 | } 92 | 93 | size_t count = ptr->strong_count.load(std::memory_order_acquire); 94 | while (count != 0) { 95 | if (ptr->strong_count.compare_exchange_weak( 96 | count, 97 | count + 1, 98 | std::memory_order_acquire, 99 | std::memory_order_relaxed)) { 100 | return ::rusty::Some(rusty::Arc(ptr, false)); 101 | } 102 | } 103 | return ::rusty::None; 104 | } 105 | 106 | bool expired() const { 107 | return !ptr || ptr->strong_count.load(std::memory_order_acquire) == 0; 108 | } 109 | 110 | size_t strong_count() const { 111 | return ptr ? ptr->strong_count.load(std::memory_order_acquire) : 0; 112 | } 113 | 114 | size_t weak_count() const { 115 | if (!ptr) { 116 | return 0; 117 | } 118 | size_t count = ptr->weak_count.load(std::memory_order_acquire); 119 | return count > 0 ? count - 1 : 0; 120 | } 121 | 122 | Weak clone() const { 123 | return Weak(*this); 124 | } 125 | }; 126 | 127 | // Downgrade function - create weak from strong 128 | // @safe 129 | template 130 | Weak downgrade(const rusty::Arc& arc) { 131 | return Weak(arc); 132 | } 133 | 134 | } // namespace sync 135 | } // namespace rusty 136 | 137 | #endif // RUSTY_SYNC_WEAK_HPP 138 | -------------------------------------------------------------------------------- /tests/test_stl_lifetime_enforcement.rs: -------------------------------------------------------------------------------- 1 | /// Test to verify if STL lifetime annotations are actually enforced 2 | /// 3 | /// This tests whether the iterator invalidation detection actually works. 4 | 5 | use std::fs; 6 | use std::io::Write; 7 | use std::path::Path; 8 | use std::process::Command; 9 | use tempfile::TempDir; 10 | 11 | fn run_analyzer(cpp_file: &Path) -> (bool, String) { 12 | let output = Command::new("cargo") 13 | .args(&["run", "--", cpp_file.to_str().unwrap()]) 14 | .output() 15 | .expect("Failed to run rusty-cpp-checker"); 16 | 17 | let stdout = String::from_utf8_lossy(&output.stdout).to_string(); 18 | let stderr = String::from_utf8_lossy(&output.stderr).to_string(); 19 | let combined = format!("{}\n{}", stdout, stderr); 20 | 21 | (output.status.success(), combined) 22 | } 23 | 24 | /// Test if iterator invalidation is detected with std::vector 25 | #[test] 26 | fn test_are_stl_annotations_enforced_qualified() { 27 | let temp_dir = TempDir::new().unwrap(); 28 | let source_path = temp_dir.path().join("test.cpp"); 29 | 30 | let code = r#" 31 | #include 32 | 33 | // @safe 34 | void test() { 35 | std::vector vec = {1, 2, 3}; 36 | auto it = vec.begin(); 37 | vec.push_back(4); // Invalidates iterator 38 | int x = *it; // Use of invalidated iterator 39 | } 40 | "#; 41 | 42 | fs::write(&source_path, code).unwrap(); 43 | let (_success, output) = run_analyzer(&source_path); 44 | 45 | println!("=== STL Annotation Enforcement Test (qualified) ==="); 46 | println!("{}", output); 47 | println!("================================================="); 48 | 49 | // Check if we get a borrow/lifetime violation (not a parse error!) 50 | // Must NOT be a fatal error or file not found error 51 | let has_borrow_violation = (output.contains("violation") || output.contains("borrow")) && 52 | !output.contains("Fatal error") && 53 | !output.contains("file not found"); 54 | 55 | if has_borrow_violation { 56 | println!("✅ STL annotations ARE enforced (qualified names)"); 57 | } else { 58 | println!("❌ STL annotations NOT currently enforced"); 59 | println!(" (This is expected - STL lifetime annotations may be documentation only)"); 60 | } 61 | } 62 | 63 | /// Test if iterator invalidation is detected with using namespace std 64 | #[test] 65 | fn test_are_stl_annotations_enforced_unqualified() { 66 | let temp_dir = TempDir::new().unwrap(); 67 | let source_path = temp_dir.path().join("test.cpp"); 68 | 69 | let code = r#" 70 | #include 71 | 72 | using namespace std; 73 | 74 | // @safe 75 | void test() { 76 | vector vec = {1, 2, 3}; // Unqualified 77 | auto it = vec.begin(); 78 | vec.push_back(4); // Invalidates iterator 79 | int x = *it; // Use of invalidated iterator 80 | } 81 | "#; 82 | 83 | fs::write(&source_path, code).unwrap(); 84 | let (_success, output) = run_analyzer(&source_path); 85 | 86 | println!("=== STL Annotation Enforcement Test (unqualified) ==="); 87 | println!("{}", output); 88 | println!("======================================================"); 89 | 90 | let has_borrow_violation = (output.contains("violation") || output.contains("borrow")) && 91 | !output.contains("Fatal error") && 92 | !output.contains("file not found"); 93 | 94 | if has_borrow_violation { 95 | println!("✅ STL annotations ARE enforced with 'using namespace std'"); 96 | } else { 97 | println!("❌ STL annotations NOT currently enforced with 'using namespace std'"); 98 | println!(" (This is expected - STL lifetime annotations may be documentation only)"); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /include/rusty/once.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace rusty { 7 | 8 | // Once - Ensures a piece of code is executed exactly once 9 | // Matches Rust's std::sync::Once behavior 10 | // 11 | // Usage: 12 | // static Once INIT; 13 | // static int* global_data = nullptr; 14 | // 15 | // INIT.call_once([] { 16 | // global_data = new int(42); 17 | // }); 18 | // 19 | class Once { 20 | private: 21 | std::once_flag flag_; 22 | 23 | public: 24 | Once() = default; 25 | 26 | // Execute the given function exactly once 27 | // If multiple threads call call_once() simultaneously, 28 | // exactly one will execute the function, and the others will wait 29 | template 30 | void call_once(F&& func) { 31 | std::call_once(flag_, std::forward(func)); 32 | } 33 | 34 | // Non-copyable, non-movable 35 | Once(const Once&) = delete; 36 | Once& operator=(const Once&) = delete; 37 | Once(Once&&) = delete; 38 | Once& operator=(Once&&) = delete; 39 | 40 | ~Once() = default; 41 | }; 42 | 43 | // OnceCell - A cell which can be written to only once 44 | // Similar to Rust's once_cell crate (now std::sync::OnceLock in Rust) 45 | // 46 | // Usage: 47 | // static OnceCell CELL; 48 | // 49 | // // First write succeeds 50 | // CELL.set(42); 51 | // 52 | // // Subsequent writes are ignored 53 | // CELL.set(100); // Does nothing 54 | // 55 | // // Get value (returns nullptr if not initialized) 56 | // const int* value = CELL.get(); 57 | // 58 | template 59 | class OnceCell { 60 | private: 61 | std::once_flag flag_; 62 | alignas(T) unsigned char storage_[sizeof(T)]; 63 | std::atomic initialized_{false}; 64 | 65 | T* as_ptr() { return reinterpret_cast(storage_); } 66 | const T* as_ptr() const { return reinterpret_cast(storage_); } 67 | 68 | public: 69 | OnceCell() = default; 70 | 71 | // Set the value (only succeeds if not already set) 72 | // Returns true if the value was set, false if already initialized 73 | bool set(T value) { 74 | bool success = false; 75 | std::call_once(flag_, [this, &value, &success]() { 76 | new (storage_) T(std::move(value)); 77 | initialized_.store(true, std::memory_order_release); 78 | success = true; 79 | }); 80 | return success; 81 | } 82 | 83 | // Get the value if initialized, nullptr otherwise 84 | const T* get() const { 85 | if (initialized_.load(std::memory_order_acquire)) { 86 | return as_ptr(); 87 | } 88 | return nullptr; 89 | } 90 | 91 | // Get mutable reference to value if initialized, nullptr otherwise 92 | T* get_mut() { 93 | if (initialized_.load(std::memory_order_acquire)) { 94 | return as_ptr(); 95 | } 96 | return nullptr; 97 | } 98 | 99 | // Get or initialize the value 100 | template 101 | const T& get_or_init(F&& func) { 102 | std::call_once(flag_, [this, &func]() { 103 | new (storage_) T(func()); 104 | initialized_.store(true, std::memory_order_release); 105 | }); 106 | return *as_ptr(); 107 | } 108 | 109 | // Check if the cell is initialized 110 | bool is_initialized() const { 111 | return initialized_.load(std::memory_order_acquire); 112 | } 113 | 114 | // Non-copyable, non-movable 115 | OnceCell(const OnceCell&) = delete; 116 | OnceCell& operator=(const OnceCell&) = delete; 117 | OnceCell(OnceCell&&) = delete; 118 | OnceCell& operator=(OnceCell&&) = delete; 119 | 120 | ~OnceCell() { 121 | if (initialized_.load(std::memory_order_acquire)) { 122 | as_ptr()->~T(); 123 | } 124 | } 125 | }; 126 | 127 | } // namespace rusty 128 | -------------------------------------------------------------------------------- /tests/test_this_tracking.rs: -------------------------------------------------------------------------------- 1 | use rusty_cpp::analysis::this_tracking::ThisPointerTracker; 2 | use rusty_cpp::parser::MethodQualifier; 3 | use rusty_cpp::ir::BorrowKind; 4 | 5 | #[test] 6 | fn test_const_method_restrictions() { 7 | let tracker = ThisPointerTracker::new(Some(MethodQualifier::Const)); 8 | 9 | // Can read 10 | assert!(tracker.can_read_member("field").is_ok()); 11 | 12 | // Cannot modify 13 | assert!(tracker.can_modify_member("field").is_err()); 14 | 15 | // Cannot move 16 | assert!(tracker.can_move_member("field").is_err()); 17 | 18 | // Can borrow immutably 19 | assert!(tracker.can_borrow_member("field", BorrowKind::Immutable).is_ok()); 20 | 21 | // Cannot borrow mutably 22 | assert!(tracker.can_borrow_member("field", BorrowKind::Mutable).is_err()); 23 | } 24 | 25 | #[test] 26 | fn test_nonconst_method_restrictions() { 27 | let tracker = ThisPointerTracker::new(Some(MethodQualifier::NonConst)); 28 | 29 | // Can read 30 | assert!(tracker.can_read_member("field").is_ok()); 31 | 32 | // Can modify 33 | assert!(tracker.can_modify_member("field").is_ok()); 34 | 35 | // CANNOT move (key restriction!) 36 | assert!(tracker.can_move_member("field").is_err()); 37 | assert!(tracker.can_move_member("field").unwrap_err().contains("&mut self")); 38 | 39 | // Can borrow mutably 40 | assert!(tracker.can_borrow_member("field", BorrowKind::Mutable).is_ok()); 41 | } 42 | 43 | #[test] 44 | fn test_rvalue_method_permissions() { 45 | let tracker = ThisPointerTracker::new(Some(MethodQualifier::RvalueRef)); 46 | 47 | // Can read 48 | assert!(tracker.can_read_member("field").is_ok()); 49 | 50 | // Can modify 51 | assert!(tracker.can_modify_member("field").is_ok()); 52 | 53 | // CAN move (full ownership!) 54 | assert!(tracker.can_move_member("field").is_ok()); 55 | 56 | // Can borrow mutably 57 | assert!(tracker.can_borrow_member("field", BorrowKind::Mutable).is_ok()); 58 | } 59 | 60 | #[test] 61 | fn test_move_tracking() { 62 | let mut tracker = ThisPointerTracker::new(Some(MethodQualifier::RvalueRef)); 63 | 64 | // Can move initially 65 | assert!(tracker.can_move_member("field").is_ok()); 66 | 67 | // Mark as moved 68 | tracker.mark_field_moved("field".to_string()); 69 | 70 | // Cannot read after move 71 | assert!(tracker.can_read_member("field").is_err()); 72 | 73 | // Cannot move again 74 | assert!(tracker.can_move_member("field").is_err()); 75 | 76 | // Cannot borrow after move 77 | assert!(tracker.can_borrow_member("field", BorrowKind::Immutable).is_err()); 78 | } 79 | 80 | #[test] 81 | fn test_borrow_conflicts() { 82 | let mut tracker = ThisPointerTracker::new(Some(MethodQualifier::NonConst)); 83 | 84 | // Create mutable borrow 85 | tracker.mark_field_borrowed("field".to_string(), BorrowKind::Mutable); 86 | 87 | // Cannot create another borrow while mutably borrowed 88 | assert!(tracker.can_borrow_member("field", BorrowKind::Immutable).is_err()); 89 | assert!(tracker.can_borrow_member("field", BorrowKind::Mutable).is_err()); 90 | 91 | // Clear the borrow 92 | tracker.clear_field_borrow("field"); 93 | 94 | // Now can borrow again 95 | assert!(tracker.can_borrow_member("field", BorrowKind::Immutable).is_ok()); 96 | } 97 | 98 | #[test] 99 | fn test_multiple_immutable_borrows() { 100 | let mut tracker = ThisPointerTracker::new(Some(MethodQualifier::NonConst)); 101 | 102 | // Create immutable borrow 103 | tracker.mark_field_borrowed("field".to_string(), BorrowKind::Immutable); 104 | 105 | // Can create another immutable borrow 106 | assert!(tracker.can_borrow_member("field", BorrowKind::Immutable).is_ok()); 107 | 108 | // Cannot create mutable borrow 109 | assert!(tracker.can_borrow_member("field", BorrowKind::Mutable).is_err()); 110 | } 111 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Rusty-CPP Documentation 2 | 3 | This directory contains comprehensive documentation for the Rusty-CPP static analyzer. 4 | 5 | ## Directory Structure 6 | 7 | ### `/features/` 8 | Complete implementation summaries for major features: 9 | 10 | - **[template_support.md](features/template_support.md)** - Template function and class analysis 11 | - Template free functions and methods 12 | - Generic type analysis (no instantiation needed) 13 | - Multi-parameter templates (T, U, etc.) 14 | 15 | - **[variadic_templates.md](features/variadic_templates.md)** - Variadic template support 16 | - Parameter pack recognition 17 | - Pack expansion detection 18 | - Variadic template classes 19 | - Pack ownership semantics 20 | - Template argument pack expansion 21 | 22 | - **[unsafe_blocks.md](features/unsafe_blocks.md)** - @unsafe block implementation 23 | - Block-level safety escapes 24 | - Scope tracking with depth counter 25 | - Integration with borrow checker and safety analysis 26 | 27 | - **[std_library_annotations.md](features/std_library_annotations.md)** - C++ STL function annotations 28 | - ~200+ whitelisted safe functions 29 | - Containers, algorithms, smart pointers, I/O 30 | - No explicit annotations needed in user code 31 | 32 | - **[cast_operations.md](features/cast_operations.md)** - Cast operation safety 33 | - Why casts require @unsafe context 34 | - Design rationale for type casting safety 35 | 36 | ### Root Documentation Files 37 | 38 | - **[RUST_COMPARISON.md](RUST_COMPARISON.md)** - ⭐ RustyCpp vs Rust borrow checker comparison 39 | - What's implemented vs missing 40 | - Detailed gap analysis with code examples 41 | - Workarounds for missing features 42 | - Implementation priorities 43 | 44 | - **[RAII_TRACKING.md](RAII_TRACKING.md)** - RAII tracking implementation 45 | - Container/iterator lifetime tracking 46 | - Member lifetime tracking 47 | - Lambda escape analysis 48 | - new/delete tracking 49 | 50 | - **[PARTIAL_MOVES_PLAN.md](PARTIAL_MOVES_PLAN.md)** - Partial moves improvement plan 51 | - Current status (basic support implemented) 52 | - Nested field tracking plan 53 | - Partial borrow tracking plan 54 | - Implementation priorities 55 | 56 | - **[annotation_reference.md](annotation_reference.md)** - Syntax reference for @safe, @unsafe, @lifetime annotations 57 | - **[annotations.md](annotations.md)** - Detailed annotation system documentation 58 | - **[method_qualifiers.md](method_qualifiers.md)** - C++ method qualifier handling (const, &&, etc.) 59 | - **[control_flow_fix_summary.md](control_flow_fix_summary.md)** - Control flow analysis improvements 60 | - **[fixing_control_flow.md](fixing_control_flow.md)** - Detailed control flow implementation 61 | - **[submodule_integration.md](submodule_integration.md)** - Git submodule integration guide 62 | 63 | ### Lock-Free MPSC Channel Documentation 64 | 65 | - **[mpsc_lockfree_user_guide.md](mpsc_lockfree_user_guide.md)** - ⭐ User guide for the lock-free MPSC channel 66 | - Quick start and API reference 67 | - Common patterns and examples 68 | - Performance guidelines 69 | - Best practices and troubleshooting 70 | 71 | - **[mpsc_lockfree_developer_guide.md](mpsc_lockfree_developer_guide.md)** - Developer guide and implementation details 72 | - Architecture and design decisions 73 | - Memory ordering and concurrency 74 | - Performance characteristics 75 | - Testing strategy and benchmarks 76 | 77 | ## Quick Links 78 | 79 | - Main project documentation: [../CLAUDE.md](../CLAUDE.md) 80 | - User-facing README: [../README.md](../README.md) 81 | - Source code: [../src/](../src/) 82 | - Tests: [../tests/](../tests/) 83 | - Examples: [../examples/](../examples/) 84 | 85 | ## Contributing 86 | 87 | When adding new features, please: 88 | 1. Create a comprehensive implementation summary in `features/` 89 | 2. Update this README with a link to the new documentation 90 | 3. Update CLAUDE.md with the feature summary 91 | 4. Add tests to validate the feature 92 | -------------------------------------------------------------------------------- /tests/raii/member_outlives_object.cpp: -------------------------------------------------------------------------------- 1 | // Test: Member Reference Outlives Containing Object 2 | // Status: NOT DETECTED (requires RAII tracking Phase 3) 3 | // 4 | // When you take a reference to an object's member, that reference 5 | // must not outlive the containing object. 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | struct Container { 12 | std::string data; 13 | int value; 14 | std::vector items; 15 | 16 | const std::string& get_data() const { return data; } 17 | int& get_value() { return value; } 18 | std::vector& get_items() { return items; } 19 | }; 20 | 21 | // ============================================================================= 22 | // NEGATIVE TESTS - Should produce errors after implementation 23 | // ============================================================================= 24 | 25 | // @safe 26 | const std::string& bad_return_member_ref() { 27 | Container c; 28 | c.data = "hello"; 29 | return c.get_data(); // ERROR: c.data destroyed when c destroyed 30 | } 31 | 32 | // @safe 33 | int& bad_return_member_value_ref() { 34 | Container c; 35 | c.value = 42; 36 | return c.get_value(); // ERROR: c.value destroyed when c destroyed 37 | } 38 | 39 | // @safe 40 | void bad_store_member_ptr() { 41 | const std::string* ptr; 42 | { 43 | Container c; 44 | c.data = "hello"; 45 | ptr = &c.data; // ptr points to c.data 46 | } // c destroyed, c.data destroyed 47 | 48 | // ERROR: ptr is dangling 49 | // @unsafe 50 | auto len = ptr->length(); 51 | } 52 | 53 | // @safe 54 | void bad_store_member_ref() { 55 | int* ref_ptr; 56 | { 57 | Container c; 58 | c.value = 42; 59 | ref_ptr = &c.value; 60 | } // c destroyed 61 | 62 | // ERROR: ref_ptr is dangling 63 | // @unsafe 64 | *ref_ptr = 10; 65 | } 66 | 67 | // Nested member access 68 | struct Outer { 69 | Container inner; 70 | }; 71 | 72 | // @safe 73 | const std::string& bad_nested_member_ref() { 74 | Outer o; 75 | o.inner.data = "nested"; 76 | return o.inner.get_data(); // ERROR: o.inner.data destroyed when o destroyed 77 | } 78 | 79 | // Through unique_ptr 80 | // @safe 81 | void bad_unique_ptr_member() { 82 | int* raw; 83 | { 84 | auto ptr = std::make_unique(); 85 | ptr->value = 42; 86 | raw = &ptr->value; 87 | } // ptr destroyed, Container destroyed 88 | 89 | // ERROR: raw is dangling 90 | // @unsafe 91 | *raw = 10; 92 | } 93 | 94 | // Vector element reference 95 | // @safe 96 | void bad_vector_member_ref() { 97 | int* elem_ptr; 98 | { 99 | Container c; 100 | c.items = {1, 2, 3}; 101 | elem_ptr = &c.items[0]; 102 | } // c destroyed, c.items destroyed 103 | 104 | // ERROR: elem_ptr is dangling 105 | // @unsafe 106 | *elem_ptr = 10; 107 | } 108 | 109 | // ============================================================================= 110 | // POSITIVE TESTS - Should NOT produce errors 111 | // ============================================================================= 112 | 113 | // @safe 114 | void good_member_access_in_scope() { 115 | Container c; 116 | c.data = "hello"; 117 | const std::string& ref = c.get_data(); 118 | auto len = ref.length(); // OK: ref and c have same scope 119 | } 120 | 121 | // @safe 122 | void good_copy_member_value() { 123 | int val; 124 | { 125 | Container c; 126 | c.value = 42; 127 | val = c.value; // Copy, not reference 128 | } 129 | int x = val; // OK: val is a copy 130 | } 131 | 132 | // @safe 133 | std::string good_return_member_by_value(Container& c) { 134 | return c.data; // OK: returns copy 135 | } 136 | 137 | // @safe 138 | const std::string& good_return_param_member(Container& c) { 139 | return c.get_data(); // OK: c is owned by caller 140 | } 141 | -------------------------------------------------------------------------------- /examples/undeclared_calling_demo.cpp: -------------------------------------------------------------------------------- 1 | // This example demonstrates the calling rules for undeclared functions 2 | // Key rule: Undeclared functions can call other undeclared functions 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // ============================================================================ 9 | // Undeclared functions (no annotation) - can call anything 10 | // ============================================================================ 11 | 12 | // Helper function - undeclared 13 | void log_message(const char* msg) { 14 | std::cout << "[LOG] " << msg << std::endl; 15 | } 16 | 17 | // Another undeclared function 18 | void process_data() { 19 | log_message("Processing data"); // OK: undeclared calling undeclared 20 | 21 | std::vector data = {1, 2, 3}; // OK: undeclared can use STL 22 | for (int val : data) { 23 | std::cout << val << " "; 24 | } 25 | std::cout << std::endl; 26 | } 27 | 28 | // Undeclared function calling chain 29 | void initialize_system() { 30 | log_message("Initializing system"); // OK: undeclared calling undeclared 31 | process_data(); // OK: undeclared calling undeclared 32 | } 33 | 34 | // Main function (undeclared by default) 35 | int main() { 36 | std::cout << "=== Undeclared Function Calling Demo ===" << std::endl; 37 | 38 | // Main is undeclared, so it can call anything 39 | initialize_system(); // OK: undeclared calling undeclared 40 | process_data(); // OK: undeclared calling undeclared 41 | log_message("Done"); // OK: undeclared calling undeclared 42 | 43 | // Can also call explicitly marked functions 44 | safe_operation(); // OK: undeclared can call safe 45 | unsafe_operation(); // OK: undeclared can call unsafe 46 | 47 | return 0; 48 | } 49 | 50 | // ============================================================================ 51 | // Safe functions - CANNOT call undeclared functions 52 | // ============================================================================ 53 | 54 | // @safe 55 | void safe_operation() { 56 | // log_message("Safe op"); // ERROR: safe cannot call undeclared 57 | // process_data(); // ERROR: safe cannot call undeclared 58 | 59 | // Can only call: 60 | // 1. Other safe functions 61 | // 2. Explicitly unsafe functions 62 | // 3. Whitelisted standard functions 63 | printf("Safe operation\n"); // OK: printf is whitelisted 64 | } 65 | 66 | // ============================================================================ 67 | // Unsafe functions - can call anything 68 | // ============================================================================ 69 | 70 | // @unsafe 71 | void unsafe_operation() { 72 | log_message("Unsafe op"); // OK: unsafe can call undeclared 73 | process_data(); // OK: unsafe can call undeclared 74 | initialize_system(); // OK: unsafe can call undeclared 75 | 76 | // Can use raw pointers and do anything 77 | int* ptr = new int(42); 78 | *ptr = 100; 79 | delete ptr; 80 | } 81 | 82 | // ============================================================================ 83 | // The Three-State System Rationale: 84 | // 85 | // 1. UNDECLARED (default): Legacy/unaudited code 86 | // - Not checked by the borrow checker 87 | // - Can call any functions (undeclared, safe, or unsafe) 88 | // - Represents existing codebases that haven't been audited yet 89 | // 90 | // 2. SAFE: Audited and verified safe 91 | // - Full borrow checking enforced 92 | // - Can only call safe or explicitly unsafe functions 93 | // - CANNOT call undeclared (forces explicit auditing) 94 | // 95 | // 3. UNSAFE: Audited but known to be unsafe 96 | // - Not checked by the borrow checker 97 | // - Can call any functions 98 | // - Explicitly documented as containing unsafe operations 99 | // 100 | // This creates an "audit ratchet" - once you mark something as safe, 101 | // you must audit everything it depends on. 102 | // ============================================================================ -------------------------------------------------------------------------------- /src/parser/template_context.rs: -------------------------------------------------------------------------------- 1 | /// Template Context Tracking 2 | /// 3 | /// This module tracks template type parameters (like T, U, V) to distinguish them 4 | /// from regular function calls during safety checking. 5 | /// 6 | /// When we see `T x = ...` in a template, `T` is a type parameter, not an undeclared 7 | /// function. This module helps identify such cases. 8 | 9 | use std::collections::HashSet; 10 | 11 | #[derive(Debug, Clone, Default)] 12 | pub struct TemplateContext { 13 | /// Type parameters in current template scope 14 | /// e.g., for `template`, this would contain {"T", "U"} 15 | type_parameters: HashSet, 16 | } 17 | 18 | impl TemplateContext { 19 | /// Create a new empty template context 20 | pub fn new() -> Self { 21 | Self { 22 | type_parameters: HashSet::new(), 23 | } 24 | } 25 | 26 | /// Enter a template scope with the given type parameters 27 | /// 28 | /// # Example 29 | /// ``` 30 | /// use rusty_cpp::parser::TemplateContext; 31 | /// let mut ctx = TemplateContext::new(); 32 | /// ctx.enter_template(vec!["T".to_string(), "U".to_string()]); 33 | /// assert!(ctx.is_type_parameter("T")); 34 | /// ``` 35 | pub fn enter_template(&mut self, params: Vec) { 36 | self.type_parameters.extend(params); 37 | } 38 | 39 | /// Exit the current template scope, clearing all type parameters 40 | pub fn exit_template(&mut self) { 41 | self.type_parameters.clear(); 42 | } 43 | 44 | /// Check if a name is a template type parameter 45 | /// 46 | /// # Arguments 47 | /// * `name` - The identifier to check (e.g., "T", "U", "Value") 48 | /// 49 | /// # Returns 50 | /// `true` if `name` is a known type parameter in current scope 51 | pub fn is_type_parameter(&self, name: &str) -> bool { 52 | self.type_parameters.contains(name) 53 | } 54 | 55 | /// Get all type parameters in current scope 56 | pub fn get_type_parameters(&self) -> &HashSet { 57 | &self.type_parameters 58 | } 59 | 60 | /// Check if we're currently in a template scope 61 | pub fn is_in_template(&self) -> bool { 62 | !self.type_parameters.is_empty() 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn test_empty_context() { 72 | let ctx = TemplateContext::new(); 73 | assert!(!ctx.is_type_parameter("T")); 74 | assert!(!ctx.is_in_template()); 75 | } 76 | 77 | #[test] 78 | fn test_single_type_parameter() { 79 | let mut ctx = TemplateContext::new(); 80 | ctx.enter_template(vec!["T".to_string()]); 81 | 82 | assert!(ctx.is_type_parameter("T")); 83 | assert!(!ctx.is_type_parameter("U")); 84 | assert!(ctx.is_in_template()); 85 | } 86 | 87 | #[test] 88 | fn test_multiple_type_parameters() { 89 | let mut ctx = TemplateContext::new(); 90 | ctx.enter_template(vec![ 91 | "T".to_string(), 92 | "U".to_string(), 93 | "Value".to_string(), 94 | ]); 95 | 96 | assert!(ctx.is_type_parameter("T")); 97 | assert!(ctx.is_type_parameter("U")); 98 | assert!(ctx.is_type_parameter("Value")); 99 | assert!(!ctx.is_type_parameter("NotAParam")); 100 | } 101 | 102 | #[test] 103 | fn test_exit_template() { 104 | let mut ctx = TemplateContext::new(); 105 | ctx.enter_template(vec!["T".to_string()]); 106 | assert!(ctx.is_type_parameter("T")); 107 | 108 | ctx.exit_template(); 109 | assert!(!ctx.is_type_parameter("T")); 110 | assert!(!ctx.is_in_template()); 111 | } 112 | 113 | #[test] 114 | fn test_get_type_parameters() { 115 | let mut ctx = TemplateContext::new(); 116 | ctx.enter_template(vec!["T".to_string(), "U".to_string()]); 117 | 118 | let params = ctx.get_type_parameters(); 119 | assert_eq!(params.len(), 2); 120 | assert!(params.contains("T")); 121 | assert!(params.contains("U")); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /include/rusty/box.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_BOX_HPP 2 | #define RUSTY_BOX_HPP 3 | 4 | #include // for std::move, std::forward 5 | 6 | // Box - A smart pointer for heap-allocated values with single ownership 7 | // Equivalent to Rust's Box 8 | // 9 | // Guarantees: 10 | // - Single ownership (no copying) 11 | // - Automatic deallocation when Box goes out of scope 12 | // - Move semantics only 13 | // - Null state after move 14 | 15 | // @safe 16 | namespace rusty { 17 | 18 | template 19 | class Box { 20 | private: 21 | T* ptr; 22 | 23 | public: 24 | // Constructors 25 | // No default constructor - Box must always own a value (non-nullable) 26 | Box() = delete; 27 | 28 | // @lifetime: owned 29 | explicit Box(T* p) : ptr(p) {} 30 | 31 | // Factory method - Box::make() 32 | // @lifetime: owned 33 | static Box make(T value) { 34 | // @unsafe 35 | { 36 | // new and std::move are unsafe operations 37 | return Box(new T(std::move(value))); 38 | } 39 | } 40 | 41 | // No copy constructor - Box cannot be copied 42 | Box(const Box&) = delete; 43 | Box& operator=(const Box&) = delete; 44 | 45 | // Move constructor - transfers ownership 46 | // @lifetime: owned 47 | Box(Box&& other) noexcept : ptr(other.ptr) { 48 | other.ptr = nullptr; // Other box becomes empty 49 | } 50 | 51 | // Move assignment - transfers ownership 52 | // @lifetime: owned 53 | Box& operator=(Box&& other) noexcept { 54 | // @unsafe 55 | { 56 | if (this != &other) { 57 | delete ptr; 58 | ptr = other.ptr; 59 | other.ptr = nullptr; 60 | } 61 | return *this; 62 | } 63 | } 64 | 65 | // Destructor - automatic cleanup 66 | ~Box() { 67 | // @unsafe 68 | { 69 | delete ptr; 70 | } 71 | } 72 | 73 | // Dereference - borrow the value 74 | // @lifetime: (&'a) -> &'a 75 | T& operator*() { 76 | // @unsafe 77 | { 78 | // Pointer dereference is unsafe, but Box guarantees ptr is valid 79 | return *ptr; 80 | } 81 | } 82 | 83 | // @lifetime: (&'a) -> &'a 84 | const T& operator*() const { 85 | // @unsafe 86 | { 87 | return *ptr; 88 | } 89 | } 90 | 91 | // Arrow operator - access members 92 | // @lifetime: (&'a) -> &'a 93 | T* operator->() { 94 | return ptr; 95 | } 96 | 97 | // @lifetime: (&'a) -> &'a 98 | const T* operator->() const { 99 | return ptr; 100 | } 101 | 102 | // Check if box contains a value 103 | bool is_valid() const { 104 | return ptr != nullptr; 105 | } 106 | 107 | // Explicit bool conversion 108 | explicit operator bool() const { 109 | return is_valid(); 110 | } 111 | 112 | // Take ownership of the raw pointer (Rust: Box::into_raw) 113 | // After this, the Box is empty and caller is responsible for deletion 114 | // @lifetime: owned 115 | T* into_raw() { 116 | T* temp = ptr; 117 | ptr = nullptr; 118 | return temp; 119 | } 120 | 121 | // C++-style alias for into_raw 122 | // @lifetime: owned 123 | T* release() { 124 | return into_raw(); 125 | } 126 | 127 | // Get raw pointer without transferring ownership 128 | // @lifetime: (&'a) -> &'a 129 | T* get() const { 130 | return ptr; 131 | } 132 | 133 | // Note: No reset() method - Box is non-nullable like Rust's Box 134 | // To replace the value, use assignment: box = Box::make(new_value) 135 | // To destroy, let it go out of scope or use std::move 136 | }; 137 | 138 | // Factory function following C++ make_* convention 139 | template 140 | // @lifetime: owned 141 | Box make_box(Args&&... args) { 142 | // @unsafe 143 | { 144 | // new and std::forward are unsafe operations 145 | return Box(new T(std::forward(args)...)); 146 | } 147 | } 148 | 149 | } // namespace rusty 150 | 151 | #endif // RUSTY_BOX_HPP -------------------------------------------------------------------------------- /tests/test_undeclared_can_call_undeclared.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::process::Command; 3 | use tempfile::TempDir; 4 | 5 | #[test] 6 | fn test_undeclared_can_call_undeclared() { 7 | let temp_dir = TempDir::new().unwrap(); 8 | 9 | let cpp_path = temp_dir.path().join("test.cpp"); 10 | let cpp_content = r#" 11 | // No annotation - undeclared function 12 | void helper() { 13 | // Do something 14 | } 15 | 16 | // No annotation - undeclared function 17 | void undeclared_function() { 18 | helper(); // OK: undeclared can call undeclared 19 | printf("test"); // OK: undeclared can call anything 20 | } 21 | 22 | // @safe 23 | void safe_function() { 24 | // helper(); // Would be ERROR: safe cannot call undeclared 25 | printf("test"); // OK: printf is whitelisted 26 | } 27 | 28 | // @unsafe 29 | void unsafe_function() { 30 | helper(); // OK: unsafe can call undeclared 31 | undeclared_function(); // OK: unsafe can call undeclared 32 | } 33 | "#; 34 | fs::write(&cpp_path, cpp_content).unwrap(); 35 | 36 | let output = run_checker(&cpp_path); 37 | 38 | // Should have no violations - undeclared calling undeclared is fine 39 | assert!(output.contains("no violations found") || 40 | !output.contains("undeclared_function"), 41 | "Undeclared functions should be able to call other undeclared functions, got: {}", output); 42 | } 43 | 44 | #[test] 45 | fn test_safe_cannot_call_undeclared() { 46 | let temp_dir = TempDir::new().unwrap(); 47 | 48 | let cpp_path = temp_dir.path().join("test.cpp"); 49 | let cpp_content = r#" 50 | // No annotation - undeclared function 51 | void helper() { 52 | // Do something 53 | } 54 | 55 | // @safe 56 | void safe_function() { 57 | helper(); // ERROR: safe cannot call undeclared 58 | } 59 | "#; 60 | fs::write(&cpp_path, cpp_content).unwrap(); 61 | 62 | let output = run_checker(&cpp_path); 63 | 64 | // Should report violation for safe calling undeclared 65 | assert!(output.contains("violation") || output.contains("undeclared"), 66 | "Safe functions should not be able to call undeclared functions, got: {}", output); 67 | } 68 | 69 | #[test] 70 | fn test_undeclared_chain() { 71 | let temp_dir = TempDir::new().unwrap(); 72 | 73 | let cpp_path = temp_dir.path().join("test.cpp"); 74 | let cpp_content = r#" 75 | // Chain of undeclared functions - all should be OK 76 | void func_a() { 77 | // Do something 78 | } 79 | 80 | void func_b() { 81 | func_a(); // OK: undeclared calling undeclared 82 | } 83 | 84 | void func_c() { 85 | func_b(); // OK: undeclared calling undeclared 86 | } 87 | 88 | void main() { 89 | func_c(); // OK: undeclared (main) calling undeclared 90 | } 91 | 92 | // @safe 93 | void safe_func() { 94 | // func_c(); // Would be ERROR: safe cannot call undeclared 95 | } 96 | "#; 97 | fs::write(&cpp_path, cpp_content).unwrap(); 98 | 99 | let output = run_checker(&cpp_path); 100 | 101 | // Should have no violations 102 | assert!(output.contains("no violations found") || 103 | (!output.contains("func_a") && !output.contains("func_b") && !output.contains("func_c")), 104 | "Chains of undeclared functions should be allowed, got: {}", output); 105 | } 106 | 107 | // Helper function 108 | fn run_checker(cpp_file: &std::path::Path) -> String { 109 | let z3_header = if cfg!(target_os = "macos") { 110 | "/opt/homebrew/include/z3.h" 111 | } else { 112 | "/usr/include/z3.h" 113 | }; 114 | 115 | let mut cmd = Command::new("cargo"); 116 | cmd.args(&["run", "--quiet", "--", cpp_file.to_str().unwrap()]) 117 | .env("Z3_SYS_Z3_HEADER", z3_header); 118 | 119 | if cfg!(target_os = "macos") { 120 | cmd.env("DYLD_LIBRARY_PATH", "/opt/homebrew/Cellar/llvm/19.1.7/lib"); 121 | } else { 122 | cmd.env("LD_LIBRARY_PATH", "/usr/lib/llvm-14/lib"); 123 | } 124 | 125 | let output = cmd.output().expect("Failed to execute checker"); 126 | 127 | let stdout = String::from_utf8_lossy(&output.stdout); 128 | let stderr = String::from_utf8_lossy(&output.stderr); 129 | format!("{}{}", stdout, stderr) 130 | } -------------------------------------------------------------------------------- /examples/test_option_as_ref.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../include/rusty/option.hpp" 4 | 5 | // @safe 6 | int main() { 7 | std::cout << "Testing Option::as_ref() and as_mut()...\n\n"; 8 | 9 | // Test 1: Basic as_ref() 10 | { 11 | std::cout << "Test 1: Basic as_ref()\n"; 12 | auto opt = rusty::Some("hello"); 13 | 14 | auto ref_opt = opt.as_ref(); 15 | if (ref_opt.is_some()) { 16 | const auto& s = ref_opt.unwrap(); 17 | std::cout << " Value: " << s << "\n"; 18 | std::cout << " Length: " << s.length() << "\n"; 19 | } 20 | 21 | // Original opt still usable 22 | std::cout << " Original: " << opt.unwrap() << "\n\n"; 23 | } 24 | 25 | // Test 2: as_ref() on None 26 | { 27 | std::cout << "Test 2: as_ref() on None\n"; 28 | rusty::Option opt = rusty::None; 29 | 30 | auto ref_opt = opt.as_ref(); 31 | std::cout << " Is None: " << (ref_opt.is_none() ? "yes" : "no") << "\n\n"; 32 | } 33 | 34 | // Test 3: as_mut() 35 | { 36 | std::cout << "Test 3: as_mut()\n"; 37 | auto opt = rusty::Some("hello"); 38 | 39 | auto mut_opt = opt.as_mut(); 40 | if (mut_opt.is_some()) { 41 | auto& s = mut_opt.unwrap(); 42 | s.append(" world"); 43 | std::cout << " Modified: " << s << "\n"; 44 | } 45 | 46 | std::cout << " Final value: " << opt.unwrap() << "\n\n"; 47 | } 48 | 49 | // Test 4: as_ref() with map() 50 | { 51 | std::cout << "Test 4: as_ref() with map()\n"; 52 | auto opt = rusty::Some("hello"); 53 | 54 | auto len_opt = opt.as_ref().map([](const std::string& s) { 55 | return s.length(); 56 | }); 57 | 58 | if (len_opt.is_some()) { 59 | std::cout << " Length: " << len_opt.unwrap() << "\n"; 60 | } 61 | 62 | std::cout << " Original still exists: " << opt.unwrap() << "\n\n"; 63 | } 64 | 65 | // Test 5: Option directly 66 | { 67 | std::cout << "Test 5: Option directly\n"; 68 | std::string s = "hello"; 69 | rusty::Option ref_opt(s); 70 | 71 | if (ref_opt.is_some()) { 72 | auto& str_ref = ref_opt.unwrap(); 73 | str_ref.append(" world"); 74 | std::cout << " Modified through Option: " << str_ref << "\n"; 75 | } 76 | 77 | std::cout << " Original string: " << s << "\n\n"; 78 | } 79 | 80 | // Test 6: Option 81 | { 82 | std::cout << "Test 6: Option\n"; 83 | const std::string s = "hello"; 84 | rusty::Option ref_opt(s); 85 | 86 | if (ref_opt.is_some()) { 87 | const auto& str_ref = ref_opt.unwrap(); 88 | std::cout << " Value: " << str_ref << "\n"; 89 | std::cout << " Length: " << str_ref.length() << "\n"; 90 | } 91 | std::cout << "\n"; 92 | } 93 | 94 | // Test 7: Multiple as_ref() calls 95 | { 96 | std::cout << "Test 7: Multiple as_ref() calls\n"; 97 | auto opt = rusty::Some("hello"); 98 | 99 | auto ref1 = opt.as_ref(); 100 | auto ref2 = opt.as_ref(); 101 | 102 | if (ref1.is_some() && ref2.is_some()) { 103 | std::cout << " Both references valid\n"; 104 | std::cout << " ref1: " << ref1.unwrap() << "\n"; 105 | std::cout << " ref2: " << ref2.unwrap() << "\n"; 106 | } 107 | std::cout << "\n"; 108 | } 109 | 110 | // Test 8: contains() on Option 111 | { 112 | std::cout << "Test 8: contains() on Option\n"; 113 | std::string s = "hello"; 114 | rusty::Option ref_opt(s); 115 | 116 | std::cout << " Contains 'hello': " << (ref_opt.contains(s) ? "yes" : "no") << "\n"; 117 | 118 | std::string other = "world"; 119 | std::cout << " Contains 'world': " << (ref_opt.contains(other) ? "yes" : "no") << "\n"; 120 | std::cout << "\n"; 121 | } 122 | 123 | std::cout << "All tests completed successfully!\n"; 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /include/rusty/unsafe_cell.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RUSTY_UNSAFE_CELL_HPP 2 | #define RUSTY_UNSAFE_CELL_HPP 3 | 4 | // UnsafeCell - The core primitive for interior mutability 5 | // 6 | // This is the fundamental building block for all interior mutability in Rust. 7 | // Unlike Cell or RefCell, UnsafeCell provides no safety guarantees. 8 | // 9 | // Guarantees: 10 | // - Single-threaded only (not thread-safe) 11 | // - NO borrow checking (unsafe) 12 | // - Direct mutable access to inner value through const methods 13 | // - Zero overhead - just wraps the value 14 | // 15 | // Safety: 16 | // - You MUST ensure no data races or aliasing violations 17 | // - You MUST NOT create multiple mutable references simultaneously 18 | // - You MUST ensure references don't outlive the UnsafeCell 19 | 20 | // @safe 21 | namespace rusty { 22 | 23 | template 24 | class UnsafeCell { 25 | private: 26 | T value; 27 | 28 | public: 29 | // Constructors 30 | UnsafeCell() : value() {} 31 | explicit UnsafeCell(T val) : value(std::move(val)) {} 32 | 33 | // Factory method (Rust-style) 34 | static UnsafeCell new_(T value) { 35 | return UnsafeCell(std::move(value)); 36 | } 37 | 38 | // Get a raw mutable pointer to the inner value 39 | // This is the ONLY way to access the value 40 | // @lifetime: (&'a) -> *mut T where return: 'a 41 | // SAFETY: Caller must ensure: 42 | // 1. No data races (single-threaded access only) 43 | // 2. No aliasing violations (don't create multiple mutable refs) 44 | // 3. Returned pointer doesn't outlive the UnsafeCell 45 | T* get() const { 46 | return const_cast(&value); 47 | } 48 | 49 | // Get a const raw pointer to the inner value (for reading) 50 | // @lifetime: (&'a) -> *const T where return: 'a 51 | const T* get_const() const { 52 | return &value; 53 | } 54 | 55 | // @safe - Get mutable reference when you have exclusive access (Rust-like API) 56 | // This is safe because non-const method requires exclusive (&mut) access 57 | // Matches Rust's UnsafeCell::get_mut(&mut self) -> &mut T 58 | // @lifetime: (&'a mut) -> &'a mut T 59 | T& get_mut() { 60 | return value; 61 | } 62 | 63 | // @safe - Get const reference when you have exclusive access 64 | // @lifetime: (&'a mut) -> &'a T 65 | const T& get_mut() const { 66 | return value; 67 | } 68 | 69 | // @safe - Get mutable reference through shared access (interior mutability) 70 | // Similar to Rust's unchecked access patterns - no runtime checks performed. 71 | // Has internal @unsafe block. 72 | // SAFETY: Caller must ensure no data races or aliasing violations 73 | // @lifetime: (&'a) -> &'a mut T 74 | T& as_mut_unchecked() const { 75 | // @unsafe 76 | { return *const_cast(&value); } 77 | } 78 | 79 | // @safe - Get const reference through shared access (has internal @unsafe block) 80 | // @lifetime: (&'a) -> &'a T 81 | const T& as_ref_unchecked() const { 82 | // @unsafe 83 | { return value; } 84 | } 85 | 86 | // Take ownership of the value, leaving default in its place 87 | // Only available if T has a default constructor 88 | template 89 | typename std::enable_if_t, T> 90 | take() const { 91 | T old = *get(); 92 | *get() = T{}; 93 | return old; 94 | } 95 | 96 | // Replace the value and return the old one 97 | // @lifetime: (&'a, T) -> T 98 | T replace(T new_value) const { 99 | T old = *get(); 100 | *get() = new_value; 101 | return old; 102 | } 103 | 104 | // No copy or move - UnsafeCell itself is not copyable/movable 105 | // (This prevents accidental aliasing of the inner pointer) 106 | UnsafeCell(const UnsafeCell&) = delete; 107 | UnsafeCell& operator=(const UnsafeCell&) = delete; 108 | UnsafeCell(UnsafeCell&&) = delete; 109 | UnsafeCell& operator=(UnsafeCell&&) = delete; 110 | }; 111 | 112 | // Helper function to create an UnsafeCell 113 | template 114 | UnsafeCell make_unsafe_cell(T value) { 115 | return UnsafeCell(value); 116 | } 117 | 118 | } // namespace rusty 119 | 120 | #endif // RUSTY_UNSAFE_CELL_HPP 121 | -------------------------------------------------------------------------------- /tests/raii/partial_borrow_nested_test.cpp: -------------------------------------------------------------------------------- 1 | // Test: Nested field borrow tracking 2 | // Goal: Track borrows of nested fields like p.inner.field 3 | 4 | #include 5 | 6 | struct Inner { 7 | std::string data; 8 | int value; 9 | }; 10 | 11 | struct Outer { 12 | Inner inner; 13 | std::string name; 14 | }; 15 | 16 | // TEST 1: Borrow different nested fields mutably - should be OK 17 | // @safe 18 | void test_nested_different_fields_mutable() { 19 | Outer o; 20 | o.inner.data = "hello"; 21 | o.name = "world"; 22 | 23 | std::string& r1 = o.inner.data; // Mutable borrow of o.inner.data 24 | std::string& r2 = o.name; // OK: o.name is separate from o.inner.data 25 | 26 | r1 = "modified"; 27 | r2 = "also modified"; 28 | } 29 | 30 | // TEST 2: Double mutable borrow of same nested field - should ERROR 31 | // @safe 32 | void test_nested_same_field_double_mutable() { 33 | Outer o; 34 | o.inner.data = "hello"; 35 | 36 | std::string& r1 = o.inner.data; // Mutable borrow of o.inner.data 37 | std::string& r2 = o.inner.data; // ERROR: o.inner.data already mutably borrowed 38 | 39 | r1 = "modified"; 40 | } 41 | 42 | // TEST 3: Borrow sibling nested fields - should be OK 43 | // @safe 44 | void test_nested_sibling_fields() { 45 | Outer o; 46 | o.inner.data = "hello"; 47 | o.inner.value = 42; 48 | 49 | std::string& r1 = o.inner.data; // Mutable borrow of o.inner.data 50 | int& r2 = o.inner.value; // OK: o.inner.value is separate 51 | 52 | r1 = "modified"; 53 | r2 = 100; 54 | } 55 | 56 | // TEST 4: Borrow parent after borrowing nested - should ERROR 57 | // @safe 58 | void test_borrow_parent_after_nested() { 59 | Outer o; 60 | o.inner.data = "hello"; 61 | 62 | std::string& r1 = o.inner.data; // Mutable borrow of o.inner.data 63 | Inner& r2 = o.inner; // ERROR: Cannot borrow o.inner while o.inner.data borrowed 64 | 65 | r1 = "modified"; 66 | } 67 | 68 | // TEST 5: Borrow nested after borrowing parent - should ERROR 69 | // @safe 70 | void test_borrow_nested_after_parent() { 71 | Outer o; 72 | o.inner.data = "hello"; 73 | 74 | Inner& r1 = o.inner; // Mutable borrow of whole o.inner 75 | std::string& r2 = o.inner.data; // ERROR: Cannot borrow o.inner.data while o.inner borrowed 76 | 77 | r2 = "modified"; 78 | } 79 | 80 | // TEST 6: Multiple immutable borrows of nested field - should be OK 81 | // @safe 82 | void test_nested_multiple_immutable() { 83 | Outer o; 84 | o.inner.data = "hello"; 85 | 86 | const std::string& r1 = o.inner.data; // Immutable borrow 87 | const std::string& r2 = o.inner.data; // OK: multiple immutable allowed 88 | 89 | // Both r1 and r2 valid for reading 90 | } 91 | 92 | // TEST 7: Immutable + mutable borrow of different nested fields - should be OK 93 | // @safe 94 | void test_nested_mixed_different_fields() { 95 | Outer o; 96 | o.inner.data = "hello"; 97 | o.inner.value = 42; 98 | 99 | const std::string& r1 = o.inner.data; // Immutable borrow of data 100 | int& r2 = o.inner.value; // OK: value is separate field 101 | 102 | r2 = 100; 103 | } 104 | 105 | // TEST 8: Deep nesting (3+ levels) borrow - should be OK for different paths 106 | struct Level3 { std::string data; }; 107 | struct Level2 { Level3 level3; int x; }; 108 | struct Level1 { Level2 level2; }; 109 | struct Root { Level1 level1; std::string name; }; 110 | 111 | // @safe 112 | void test_deep_nested_different_paths() { 113 | Root r; 114 | r.level1.level2.level3.data = "deep"; 115 | r.name = "root"; 116 | 117 | std::string& r1 = r.level1.level2.level3.data; // Deep nested borrow 118 | std::string& r2 = r.name; // OK: different path 119 | 120 | r1 = "modified deep"; 121 | r2 = "modified root"; 122 | } 123 | 124 | // TEST 9: Deep nesting double borrow same field - should ERROR 125 | // @safe 126 | void test_deep_nested_same_field() { 127 | Root r; 128 | r.level1.level2.level3.data = "deep"; 129 | 130 | std::string& r1 = r.level1.level2.level3.data; // Deep nested borrow 131 | std::string& r2 = r.level1.level2.level3.data; // ERROR: already borrowed 132 | 133 | r1 = "modified"; 134 | } 135 | 136 | int main() { return 0; } 137 | -------------------------------------------------------------------------------- /tests/raii/temporary_lifetime.cpp: -------------------------------------------------------------------------------- 1 | // Test: Temporary Object Lifetime 2 | // Status: NOT DETECTED (requires RAII tracking Phase 4) 3 | // 4 | // C++ temporaries have complex lifetime rules. References to temporaries 5 | // can dangle if the temporary is destroyed before the reference is used. 6 | 7 | #include 8 | #include 9 | 10 | std::string get_string() { 11 | return std::string("temporary"); 12 | } 13 | 14 | struct Result { 15 | std::string value; 16 | const std::string& get() const { return value; } 17 | }; 18 | 19 | Result get_result() { 20 | return Result{"result"}; 21 | } 22 | 23 | // ============================================================================= 24 | // NEGATIVE TESTS - Should produce errors after implementation 25 | // ============================================================================= 26 | 27 | // @safe 28 | const std::string& bad_return_temporary() { 29 | return std::string("temp"); // ERROR: temporary destroyed at semicolon 30 | } 31 | 32 | // @safe 33 | const std::string& bad_return_temporary_from_call() { 34 | return get_string(); // ERROR: returned temporary destroyed immediately 35 | } 36 | 37 | // @safe 38 | void bad_ref_to_temp_member() { 39 | const std::string& ref = get_result().get(); 40 | // ERROR: Result temporary destroyed, ref dangles 41 | // @unsafe 42 | auto len = ref.length(); 43 | } 44 | 45 | // @safe 46 | void bad_pointer_to_temporary() { 47 | // @unsafe 48 | const std::string* ptr = &get_string(); 49 | // ERROR: temporary destroyed, ptr dangles 50 | // @unsafe 51 | auto len = ptr->length(); 52 | } 53 | 54 | // Ternary with temporary 55 | // @safe 56 | void bad_ternary_temporary(bool condition) { 57 | std::string a = "a"; 58 | const std::string& ref = condition ? a : std::string("temp"); 59 | // ERROR if condition==false: temporary destroyed, ref dangles 60 | // @unsafe 61 | auto len = ref.length(); 62 | } 63 | 64 | // Chained method calls with temporary 65 | // @safe 66 | void bad_chained_temp() { 67 | // @unsafe 68 | const char* ptr = get_string().c_str(); 69 | // ERROR: temporary string destroyed, ptr dangles 70 | // @unsafe 71 | char c = ptr[0]; 72 | } 73 | 74 | // Temporary in range-for 75 | // @safe 76 | void bad_temp_in_range_for() { 77 | // This is actually OK in C++ due to lifetime extension in range-for 78 | // But this variant is NOT: 79 | // @unsafe 80 | auto& items_ref = std::vector{1, 2, 3}; 81 | // Temporary vector destroyed here, but ref survives 82 | for (int x : items_ref) { // ERROR: items_ref is dangling 83 | // ... 84 | } 85 | } 86 | 87 | // ============================================================================= 88 | // POSITIVE TESTS - Should NOT produce errors (lifetime extension applies) 89 | // ============================================================================= 90 | 91 | // @safe 92 | void good_const_ref_extends_lifetime() { 93 | const std::string& ref = get_string(); 94 | // OK: const ref extends temporary's lifetime to ref's scope 95 | auto len = ref.length(); 96 | } 97 | 98 | // @safe 99 | void good_rvalue_ref_extends_lifetime() { 100 | std::string&& rref = get_string(); 101 | // OK: rvalue ref extends temporary's lifetime 102 | auto len = rref.length(); 103 | } 104 | 105 | // @safe 106 | void good_copy_from_temporary() { 107 | std::string s = get_string(); 108 | // OK: s is a copy (or move), owns the data 109 | auto len = s.length(); 110 | } 111 | 112 | // @safe 113 | void good_temp_in_expression() { 114 | auto len = get_string().length(); 115 | // OK: temporary lives until end of full expression 116 | } 117 | 118 | // @safe 119 | void good_range_for_with_temp() { 120 | for (int x : std::vector{1, 2, 3}) { 121 | // OK: temporary lifetime extended for range-for 122 | int y = x; 123 | } 124 | } 125 | 126 | // @safe 127 | void good_pass_temp_to_function() { 128 | // Assuming process takes const std::string& 129 | // process(get_string()); 130 | // OK: temporary lives until function returns 131 | } 132 | 133 | // @safe 134 | int good_immediate_use_of_temp() { 135 | return get_result().value.length(); 136 | // OK: all temporaries live until end of full expression 137 | } 138 | -------------------------------------------------------------------------------- /examples/std_safety_demo.cpp: -------------------------------------------------------------------------------- 1 | // This example demonstrates that STL functions are undeclared by default 2 | // and therefore cannot be called from safe functions without explicit marking 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // ============================================================================ 11 | // Safe function trying to use STL - should fail 12 | // ============================================================================ 13 | 14 | // @safe 15 | void safe_with_stl_attempt() { 16 | // All of these should be errors - STL functions are undeclared 17 | 18 | // std::vector operations 19 | std::vector vec; // ERROR: constructor is undeclared 20 | vec.push_back(42); // ERROR: push_back is undeclared 21 | vec.size(); // ERROR: size is undeclared 22 | 23 | // std::string operations 24 | // std::string str = "test"; // ERROR: constructor is undeclared 25 | // str.length(); // ERROR: length is undeclared 26 | 27 | // std::unique_ptr 28 | // auto ptr = std::make_unique(42); // ERROR: make_unique is undeclared 29 | 30 | // Algorithms 31 | // std::vector v = {1, 2, 3}; 32 | // std::sort(v.begin(), v.end()); // ERROR: sort is undeclared 33 | 34 | // Even cout is undeclared (except we whitelisted it) 35 | std::cout << "This might work if we whitelisted cout\n"; 36 | } 37 | 38 | // ============================================================================ 39 | // Unsafe function can use STL freely 40 | // ============================================================================ 41 | 42 | // @unsafe 43 | void unsafe_with_stl() { 44 | // All STL usage is allowed in unsafe functions 45 | std::vector vec; 46 | vec.push_back(42); 47 | 48 | std::string str = "test"; 49 | auto len = str.length(); 50 | 51 | auto ptr = std::make_unique(42); 52 | 53 | std::vector v = {3, 1, 2}; 54 | std::sort(v.begin(), v.end()); 55 | 56 | std::cout << "Unsafe function can use all STL freely\n"; 57 | } 58 | 59 | // ============================================================================ 60 | // Undeclared function (default) can also use STL 61 | // ============================================================================ 62 | 63 | void undeclared_with_stl() { 64 | // Undeclared functions are not checked, so STL usage is fine 65 | std::vector names = {"Alice", "Bob", "Charlie"}; 66 | for (const auto& name : names) { 67 | std::cout << "Hello, " << name << "\n"; 68 | } 69 | } 70 | 71 | // ============================================================================ 72 | // Solution: Create safe wrappers or mark STL functions as safe/unsafe 73 | // ============================================================================ 74 | 75 | // If you need STL in safe code, you have options: 76 | // 1. Create safe wrapper functions marked @safe 77 | // 2. Use external annotations to mark specific STL functions as @safe 78 | // 3. Move STL usage to @unsafe functions and expose safe interfaces 79 | 80 | // Example safe wrapper: 81 | // @safe 82 | int safe_vector_size(/* would need safe reference type */) { 83 | // In practice, you'd need proper lifetime annotations 84 | // This is just conceptual 85 | return 0; 86 | } 87 | 88 | int main() { 89 | std::cout << "STL Safety Demo\n"; 90 | std::cout << "================\n"; 91 | 92 | // Main is undeclared, so it can call anything 93 | safe_with_stl_attempt(); 94 | unsafe_with_stl(); 95 | undeclared_with_stl(); 96 | 97 | return 0; 98 | } 99 | 100 | // ============================================================================ 101 | // Key Insight: 102 | // 103 | // The STL is massive and mostly unaudited from a borrow-checking perspective. 104 | // By treating it as undeclared by default, we force developers to: 105 | // 1. Explicitly audit and mark STL functions they want to use 106 | // 2. Create safe wrappers with proper lifetime management 107 | // 3. Isolate STL usage in unsafe boundaries 108 | // 109 | // This prevents accidental misuse of STL functions that might violate 110 | // borrow checking rules (like iterator invalidation). 111 | // ============================================================================ -------------------------------------------------------------------------------- /tests/raii/double_free.cpp: -------------------------------------------------------------------------------- 1 | // Test: Double Free and Use After Free with Raw Pointers 2 | // Status: NOT DETECTED (requires RAII tracking Phase 6) 3 | // 4 | // While modern C++ prefers smart pointers, raw new/delete is still 5 | // common. We should detect double-free and use-after-free. 6 | 7 | #include 8 | 9 | // ============================================================================= 10 | // NEGATIVE TESTS - Should produce errors after implementation 11 | // ============================================================================= 12 | 13 | // @unsafe 14 | void bad_double_delete() { 15 | int* ptr = new int(42); 16 | delete ptr; 17 | delete ptr; // ERROR: double free 18 | } 19 | 20 | // @unsafe 21 | void bad_use_after_delete() { 22 | int* ptr = new int(42); 23 | delete ptr; 24 | *ptr = 10; // ERROR: use after free 25 | } 26 | 27 | // @unsafe 28 | void bad_use_after_delete_read() { 29 | int* ptr = new int(42); 30 | delete ptr; 31 | int x = *ptr; // ERROR: read after free 32 | } 33 | 34 | // @unsafe 35 | void bad_delete_then_return() { 36 | int* ptr = new int(42); 37 | delete ptr; 38 | // ERROR: returning deleted pointer 39 | // (caller might try to use it) 40 | } 41 | 42 | // @unsafe 43 | void bad_conditional_double_free(bool condition) { 44 | int* ptr = new int(42); 45 | if (condition) { 46 | delete ptr; 47 | } 48 | delete ptr; // ERROR: might be double free 49 | } 50 | 51 | // @unsafe 52 | void bad_loop_double_free() { 53 | int* ptr = new int(42); 54 | for (int i = 0; i < 2; i++) { 55 | delete ptr; // ERROR: second iteration is double free 56 | } 57 | } 58 | 59 | // @unsafe 60 | void bad_delete_stack_variable() { 61 | int x = 42; 62 | int* ptr = &x; 63 | delete ptr; // ERROR: deleting stack memory 64 | } 65 | 66 | // @unsafe 67 | void bad_delete_static() { 68 | static int x = 42; 69 | delete &x; // ERROR: deleting static memory 70 | } 71 | 72 | // Array delete mismatch 73 | // @unsafe 74 | void bad_array_delete_mismatch() { 75 | int* ptr = new int[10]; 76 | delete ptr; // ERROR: should be delete[] 77 | } 78 | 79 | // @unsafe 80 | void bad_scalar_delete_array() { 81 | int* ptr = new int(42); 82 | delete[] ptr; // ERROR: should be delete (not delete[]) 83 | } 84 | 85 | // malloc/free with new/delete 86 | // @unsafe 87 | void bad_malloc_delete() { 88 | int* ptr = (int*)malloc(sizeof(int)); 89 | delete ptr; // ERROR: malloc'd memory should use free() 90 | } 91 | 92 | // @unsafe 93 | void bad_new_free() { 94 | int* ptr = new int(42); 95 | free(ptr); // ERROR: new'd memory should use delete 96 | } 97 | 98 | // Use after free through alias 99 | // @unsafe 100 | void bad_alias_use_after_free() { 101 | int* ptr1 = new int(42); 102 | int* ptr2 = ptr1; // ptr2 aliases ptr1 103 | delete ptr1; 104 | *ptr2 = 10; // ERROR: use after free through alias 105 | } 106 | 107 | // ============================================================================= 108 | // POSITIVE TESTS - Should NOT produce errors 109 | // ============================================================================= 110 | 111 | // @unsafe 112 | void good_new_delete_pair() { 113 | int* ptr = new int(42); 114 | *ptr = 100; // OK: ptr is valid 115 | delete ptr; 116 | // ptr not used after delete - OK 117 | } 118 | 119 | // @unsafe 120 | void good_array_new_delete() { 121 | int* arr = new int[10]; 122 | arr[0] = 1; 123 | delete[] arr; // Correct: array delete for array new 124 | } 125 | 126 | // @unsafe 127 | void good_malloc_free_pair() { 128 | int* ptr = (int*)malloc(sizeof(int)); 129 | *ptr = 42; 130 | free(ptr); // Correct: free for malloc 131 | } 132 | 133 | // @unsafe 134 | void good_conditional_delete(bool condition) { 135 | int* ptr = new int(42); 136 | if (condition) { 137 | delete ptr; 138 | ptr = nullptr; // Good practice: null after delete 139 | } 140 | if (ptr) { 141 | delete ptr; // Only deletes if not already deleted 142 | } 143 | } 144 | 145 | // @unsafe 146 | void good_reassign_after_delete() { 147 | int* ptr = new int(42); 148 | delete ptr; 149 | ptr = new int(100); // Reassign to new allocation - OK 150 | *ptr = 200; // Valid 151 | delete ptr; 152 | } 153 | 154 | // @unsafe 155 | int* good_return_new() { 156 | return new int(42); // OK: caller takes ownership 157 | } 158 | 159 | // @unsafe 160 | void good_take_ownership(int* ptr) { 161 | // Assuming we take ownership 162 | *ptr = 100; 163 | delete ptr; // OK if we own it 164 | } 165 | -------------------------------------------------------------------------------- /tests/rusty_box_test.cpp: -------------------------------------------------------------------------------- 1 | // Tests for rusty::Box 2 | #include "../include/rusty/box.hpp" 3 | #include 4 | #include 5 | 6 | using namespace rusty; 7 | 8 | // Test basic construction and destruction 9 | void test_box_construction() { 10 | printf("test_box_construction: "); 11 | { 12 | auto box1 = Box::make(42); 13 | assert(box1.is_valid()); 14 | assert(*box1 == 42); 15 | 16 | auto box2 = make_box(100); 17 | assert(box2.is_valid()); 18 | assert(*box2 == 100); 19 | 20 | auto box3 = make_box(200); 21 | assert(box3.is_valid()); 22 | assert(*box3 == 200); 23 | } 24 | printf("PASS\n"); 25 | } 26 | 27 | // Test move semantics 28 | void test_box_move() { 29 | printf("test_box_move: "); 30 | { 31 | auto box1 = Box::make(42); 32 | assert(box1.is_valid()); 33 | 34 | auto box2 = std::move(box1); 35 | assert(!box1.is_valid()); // box1 should be empty after move 36 | assert(box2.is_valid()); 37 | assert(*box2 == 42); 38 | 39 | auto box3 = Box::make(0); // Create with initial value 40 | box3 = std::move(box2); 41 | assert(!box2.is_valid()); // box2 should be empty after move 42 | assert(box3.is_valid()); 43 | assert(*box3 == 42); 44 | } 45 | printf("PASS\n"); 46 | } 47 | 48 | // Test into_raw and release 49 | void test_box_raw_pointer() { 50 | printf("test_box_raw_pointer: "); 51 | { 52 | auto box1 = Box::make(42); 53 | int* raw = box1.into_raw(); 54 | assert(!box1.is_valid()); // box1 should be empty after into_raw 55 | assert(*raw == 42); 56 | delete raw; // Manual cleanup required after into_raw 57 | 58 | auto box2 = Box::make(100); 59 | int* raw2 = box2.release(); // C++ style alias 60 | assert(!box2.is_valid()); 61 | assert(*raw2 == 100); 62 | delete raw2; 63 | } 64 | printf("PASS\n"); 65 | } 66 | 67 | // Test replacement via move assignment 68 | void test_box_replacement() { 69 | printf("test_box_replacement: "); 70 | { 71 | auto box1 = Box::make(42); 72 | assert(box1.is_valid()); 73 | assert(*box1 == 42); 74 | 75 | // Replace the value by move assignment 76 | box1 = Box::make(100); 77 | assert(box1.is_valid()); 78 | assert(*box1 == 100); 79 | 80 | // Move to another box to empty box1 81 | auto box2 = std::move(box1); 82 | assert(!box1.is_valid()); 83 | assert(box2.is_valid()); 84 | assert(*box2 == 100); 85 | } 86 | printf("PASS\n"); 87 | } 88 | 89 | // Test with custom struct 90 | struct TestStruct { 91 | int value; 92 | bool* destroyed; 93 | 94 | TestStruct(int v, bool* d) : value(v), destroyed(d) { *destroyed = false; } 95 | ~TestStruct() { *destroyed = true; } 96 | }; 97 | 98 | void test_box_destructor() { 99 | printf("test_box_destructor: "); 100 | bool destroyed = false; 101 | { 102 | // Create the Box directly with a pointer to avoid copy 103 | Box box(new TestStruct(42, &destroyed)); 104 | assert(!destroyed); 105 | } 106 | assert(destroyed); // Should be destroyed when box goes out of scope 107 | printf("PASS\n"); 108 | } 109 | 110 | // Test arrow operator 111 | void test_box_arrow() { 112 | printf("test_box_arrow: "); 113 | { 114 | bool destroyed = false; 115 | Box box(new TestStruct(42, &destroyed)); 116 | assert(box->value == 42); 117 | box->value = 100; 118 | assert(box->value == 100); 119 | } 120 | printf("PASS\n"); 121 | } 122 | 123 | // Test moved-from box (empty state) 124 | void test_box_moved_from() { 125 | printf("test_box_moved_from: "); 126 | { 127 | auto box1 = Box::make(42); 128 | auto box2 = std::move(box1); // box1 is now empty 129 | 130 | assert(!box1.is_valid()); 131 | assert(!box1); // Test explicit bool conversion 132 | assert(box1.get() == nullptr); 133 | 134 | assert(box2.is_valid()); 135 | assert(*box2 == 42); 136 | } 137 | printf("PASS\n"); 138 | } 139 | 140 | int main() { 141 | printf("=== Testing rusty::Box ===\n"); 142 | 143 | test_box_construction(); 144 | test_box_move(); 145 | test_box_raw_pointer(); 146 | test_box_replacement(); 147 | test_box_destructor(); 148 | test_box_arrow(); 149 | test_box_moved_from(); 150 | 151 | printf("\nAll Box tests passed!\n"); 152 | return 0; 153 | } --------------------------------------------------------------------------------