- Cpp2Rust เป็นตัวแปลแบบอาศัยไวยากรณ์ที่ทำงานบนพื้นฐานของ clang AST โดยแปลงโค้ด C++ อินพุตให้เป็นโค้ด Rust ที่ปลอดภัยทั้งหมดโดยอัตโนมัติ
- กระบวนการแปลจะพาร์สไฟล์ C++ ด้วย clang เพื่อสร้าง AST จากนั้นเดิน AST และสร้างโค้ด Rust ออกมาในรูปสตริง ก่อนจัดรูปแบบและส่งออกเป็นไฟล์
.rs เดียวด้วย rustfmt
- ค่าเริ่มต้นคือ โมเดล reference counting และสามารถสร้าง unsafe Rust ได้ด้วย
--model=unsafe เพื่อใช้ในการดีบักและเปรียบเทียบประสิทธิภาพ
- โค้ดที่สร้างขึ้นพึ่งพาไลบรารีรันไทม์
libcc2rs และพอยน์เตอร์แบบ C จะถูกแปลงเป็น Ptr<T> ที่จำลอง null, arithmetic และ aliasing
- การแปลทั้งโปรแกรมต้องใช้
compile_commands.json และสำหรับโปรเจกต์ CMake สามารถสร้างได้ด้วยแฟลก CMAKE_EXPORT_COMPILE_COMMANDS=ON
แปลงจาก C++ เป็น Rust ที่ปลอดภัยโดยอัตโนมัติ
ไปป์ไลน์การแปล
- เริ่มจากพาร์สไฟล์ C++ อินพุตด้วย clang เพื่อสร้าง AST
- จากนั้นเดิน AST และส่งออกโค้ด Rust ในรูปสตริง
- หากจำเป็นจะมีการแทรกการเรียกใช้ไลบรารีรันไทม์
libcc2rs
- ตัวอย่างคือการจัดการ raw pointer semantics
- ขั้นสุดท้ายใช้
rustfmt เพื่อจัดระเบียบโค้ด Rust เป็นไฟล์ .rs เดียว
โมเดล Rust ที่ปลอดภัยและ unsafe Rust
- การทำงานปริยายใช้ โมเดล reference counting และสร้างเอาต์พุตเป็น Rust ที่ปลอดภัยทั้งหมด
- มีตัวสร้าง unsafe Rust ให้ด้วย
- อาร์กิวเมนต์บรรทัดคำสั่งคือ
--model=unsafe
- ใช้สำหรับ การดีบัก และการเปรียบเทียบประสิทธิภาพ
ไลบรารีรันไทม์ libcc2rs
- โค้ดที่สร้างขึ้นพึ่งพา ไลบรารีรันไทม์ ที่ออกแบบมาเพื่อทำให้กระบวนการแปลงง่ายขึ้น
- พอยน์เตอร์แบบ C จะถูกแปลงเป็นชนิด
Ptr<T> ที่ libcc2rs จัดเตรียมไว้ให้
Ptr<T> ใช้จำลองความหมายของพอยน์เตอร์แบบ C
- null
- pointer arithmetic
- aliasing
- borrow checker ของ Rust จะถูกทำให้ผ่านได้ด้วย การทำงานรันไทม์ที่มีการตรวจสอบ
ขั้นตอนการบิลด์และรัน
- ตัวอย่างการติดตั้ง dependency บน Ubuntu มี
libclang-22-dev, clang++-22, ninja-build, cmake, ruff
- การบิลด์ทำตามลำดับ
cmake -GNinja .., ninja, ninja check
- คำสั่งสำหรับแปลไฟล์เดี่ยวมีรูปแบบดังนี้
./build/cpp2rust/cpp2rust --file=<file>.cpp -o=<file>.rs
- หากต้องการสร้าง unsafe Rust ให้รันดังนี้
./build/cpp2rust/cpp2rust --file=<file>.cpp -o=<file>.rs --model=unsafe
ตัวอย่างขั้นต่ำและการแปลทั้งโปรแกรม
- ตัวอย่าง
printf("hello world\n") ใน hello.cpp จะถูกแปลงเป็นโค้ดที่มี println!("hello world") ของ Rust
hello.rs ที่แปลงแล้วสามารถคอมไพล์และรันได้ดังนี้
rustc hello.rs -L ../libcc2rs/target/debug
./hello
- การแปลทั้งโปรแกรมต้องใช้
compile_commands.json
- ใน CMake สามารถสร้าง
compile_commands.json ได้ด้วยแฟลกต่อไปนี้
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..
- คำสั่งแปลทั้งโปรแกรมมีรูปแบบดังนี้
./build/cpp2rust/cpp2rust --dir=<dir> -o <output>.rs
<dir> ต้องเป็นไดเรกทอรีที่มี compile_commands.json
คำสั่งทดสอบ
- รันการทดสอบทั้งหมดด้วย
ninja check
- รัน unit test ด้วย
ninja check-unit
- รัน unit test ของ
libcc2rs ด้วย ninja check-libcc2rs
- รัน unit test ของ
libcc2rs-macros ด้วย ninja check-libcc2rs-macros
- หากต้องการสร้างเอาต์พุตที่คาดหวังใหม่หลังจากแก้ไขโดยตั้งใจ ให้ใช้
REPLACE_EXPECTED=1 ninja check-unit
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
ผมได้ฟังการนำเสนอของ Dan Wallach เมื่อเดือนกุมภาพันธ์ปีนี้ ซึ่งเขาแนะนำโครงการ TRACTOR(Translating All C to Rust) ของ DARPA [1] [2] [3]
เห็นว่าการทดสอบและประเมินผลของโปรแกรมวิจัยนี้ดำเนินการโดยทีม MIT Lincoln Laboratory
เท่าที่ผมรู้ยังอยู่ในขั้นวิจัย แต่ผมสนใจหัวข้อนี้มาก และค่อนข้างอยากรู้ว่าเครื่องมือกับไอเดียแบบนี้จะออกมาอย่างไร
ดูเหมือนว่าจะช่วยกำจัดบั๊ก “เล็กๆ น้อยๆ” จำนวนมากที่มาจากการจัดการหน่วยความจำได้จริง
[1] https://www.darpa.mil/research/programs/translating-all-c-to-rust
[2] https://github.com/DARPA-TRACTOR-Program
[3] https://ll.mit.edu/r-d/projects/…
น่าสนใจ ถ้าได้เห็น เดโมของไลบรารีหรือแอป สักหนึ่งหรือสองตัวที่ผู้เขียนแปลงด้วยเครื่องมือนี้ และผู้เขียนเองก็มองว่าน่าสนใจ ก็คงดีมาก
เช่น ถ้าดู https://github.com/Cpp2Rust/cpp2rust-testsuite/… ก็อาจเป็น Rust ที่ปลอดภัย แต่แน่นอนว่ายากจะเรียกว่าเป็น Rust แบบ idiomatic
ผมไม่ค่อยเข้าใจว่าทำไมต้องมี ขั้นตอนการแปลง ถ้ามีข้อมูลเพียงพอที่จะเปลี่ยนเป็น Rust ที่ปลอดภัยด้านหน่วยความจำได้ ก็แปลว่าน่าจะมีข้อมูลพอให้รับประกันด้วย static analysis ได้ว่า C++ ก็ปลอดภัยไม่ใช่หรือ?
เขาระบุว่าช่องโหว่ด้านความปลอดภัยในซอฟต์แวร์ที่กระจายใช้อย่างแพร่หลายราว 70% มาจาก บั๊กด้าน memory safety ในภาษาอย่าง C และ C++ และแม้จะลงทุนกับมาตรการลดความเสี่ยงอย่าง static analysis, sanitizer และการแยกโดเมนด้วยฮาร์ดแวร์มาหลายทศวรรษ ผู้โจมตีก็ยังคงใช้ประโยชน์จากการดำเนินการกับหน่วยความจำที่ไม่ปลอดภัยได้
เขาอธิบายว่าทางออกที่มีแนวโน้มดีในระยะยาวคือการย้ายโค้ดเบส C++ เดิมไปยังภาษาที่ปลอดภัยด้านหน่วยความจำอย่าง Rust แต่การทำด้วยมือมีต้นทุนสูงเกินไปและเกิดข้อผิดพลาดได้ง่าย
Cpp2Rust อ้างว่าเป็นระบบแรกที่สามารถแปลงโปรแกรม C++ โดยอัตโนมัติให้เป็นโค้ด Rust ที่ทำงานเทียบเท่ากันและปลอดภัยด้านหน่วยความจำ
โดยยอมแลกประสิทธิภาพบางส่วนกับความปลอดภัย และแก้ความไม่สอดคล้องพื้นฐานระหว่างการอ้างอิงแบบ alias ที่ไม่จำกัดของ C++ กับโมเดล ownership ของ Rust ด้วยการแทรกการตรวจสอบ ownership และ mutability ตอนรันไทม์ เพื่อรักษาความหมายเดิมพร้อมรับประกันความปลอดภัย
นอกจากนี้ยังสร้างการปรับแต่งแบบข้ามซอร์สสำหรับโค้ด Rust เพื่อลด overhead ของการตรวจสอบแบบไดนามิก และลบการดำเนินการด้าน ownership ที่ซ้ำซ้อน เพื่อกู้ประสิทธิภาพที่เสียไปกลับมาได้มากพอสมควร
ดังนั้นแรงจูงใจไม่ใช่แค่การพิสูจน์ความปลอดภัย แต่เป็นการ พอร์ตไปยัง Rust และทำให้การพัฒนาต่อจากนี้ย้ายไป Rust ได้ง่ายขึ้น
ไอเดียของโปรเจกต์แบบนี้คือการสร้างจุดเริ่มต้นสำหรับรีแฟกเตอร์ต่อให้เป็น Rust แบบ idiomatic มากขึ้น และเมื่อทำเช่นนั้นก็จะทำให้โค้ดวิเคราะห์ได้
หวังว่าจะสามารถเอาการตรวจสอบตอนรันไทม์หรือส่วนที่ไม่ปลอดภัยออกจากโค้ดที่แปลงแล้วได้ด้วย
อาจบอกได้ว่า “ก็รีแฟกเตอร์ C++ ให้เป็นทรง Rust แล้วทำให้วิเคราะห์ได้เหมือนกันก็พอไม่ใช่หรือ” แต่ C++ มี semantics ที่ต่างออกไปและอนุญาตความยืดหยุ่นมากกว่า ทำให้เส้นทางที่ต้องวิเคราะห์เพิ่มขึ้นแบบเอ็กซ์โปเนนเชียล หรือทำให้ static analysis ไปติดทางตันที่คลุมเครือได้
คุณต้องสัญญาว่าจะไม่ทำสิ่งที่สร้างงานยากให้ static analyzer และไม่ใช้ประโยชน์จากกรณีขอบต่างๆ ซึ่งถ้าจะทำเช่นนั้นก็อาจต้องสร้าง dialect ของ C++ ที่มี semantics จำกัดมากขึ้นและมี annotation เพิ่มเติม
Circle/Safe C++ แสดงให้เห็นว่ามีความเป็นไปได้ แต่ C++ WG ปฏิเสธแนวทางนั้นอย่างหนัก ดังนั้นจึงเหลือทางเลือกแค่เขียน dialect ของ C++ ที่คล้าย Rust และไม่ได้รับการสนับสนุนเอง หรือไม่ก็ใช้ Rust ไปเลย
ถ้าโค้ดอินพุตเอง ไม่ปลอดภัย จริงๆ แล้วจะทำงานอย่างไร?