From c6b33b01a06e841436600d21f0fa15f655a521b0 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 10:26:01 +0100 Subject: [PATCH 01/19] Don't map user-defined expressions --- cpp2rust/compat/platform_flags.h | 2 +- cpp2rust/converter/converter.cpp | 2 +- cpp2rust/converter/converter_lib.cpp | 17 ++++++++++++++++- cpp2rust/converter/converter_lib.h | 4 +++- cpp2rust/converter/mapper.cpp | 6 +++++- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/cpp2rust/compat/platform_flags.h b/cpp2rust/compat/platform_flags.h index 09ceb590..0f563870 100644 --- a/cpp2rust/compat/platform_flags.h +++ b/cpp2rust/compat/platform_flags.h @@ -9,7 +9,7 @@ static inline std::vector getPlatformClangBeginFlags() { std::vector flags = { "-resource-dir=" CLANG_RESOURCE_DIR, - "-I" COMPAT_INCLUDE_DIR, + "-isystem" COMPAT_INCLUDE_DIR, "-D_FORTIFY_SOURCE=0", }; #ifdef MACOS_SDK_PATH diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 703343d6..cab732b0 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -276,7 +276,7 @@ bool Converter::Convert(clang::Decl *decl) { return TraverseDecl(decl); } bool Converter::VisitTranslationUnitDecl(clang::TranslationUnitDecl *decl) { for (auto *child : decl->decls()) { - if (IsConvertibleDecl(child) && + if (IsUserDefinedDecl(child) && (IsInMainFile(child) || !decl_ids_.contains(GetID(child)))) { Convert(child); } diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 4f60cf64..051aed2d 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -129,7 +129,7 @@ bool IsInMainFile(const clang::Decl *decl) { return src_mgr.isInMainFile(src_mgr.getExpansionLoc(loc)); } -bool IsConvertibleDecl(const clang::Decl *decl) { +bool IsUserDefinedDecl(const clang::Decl *decl) { const auto &ctx = decl->getASTContext(); const auto &src_mgr = ctx.getSourceManager(); const auto src_loc = decl->getLocation(); @@ -138,6 +138,21 @@ bool IsConvertibleDecl(const clang::Decl *decl) { !src_mgr.isInSystemMacro(src_loc); } +bool RefersToUserDefinedDecl(const clang::Expr *expr) { + expr = expr->IgnoreParenImpCasts(); + const clang::Decl *decl = nullptr; + if (const auto *call = llvm::dyn_cast(expr)) { + decl = call->getDirectCallee(); + } else if (const auto *ref = llvm::dyn_cast(expr)) { + decl = ref->getDecl(); + } else if (const auto *member = llvm::dyn_cast(expr)) { + decl = member->getMemberDecl(); + } else if (const auto *ctor = llvm::dyn_cast(expr)) { + decl = ctor->getConstructor(); + } + return decl && IsUserDefinedDecl(decl); +} + bool IsUnsignedArithOp(const clang::BinaryOperator *expr) { clang::QualType lhs_type; clang::QualType rhs_type; diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index aa5d0c2b..c208ea1c 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -39,7 +39,9 @@ bool IsComparisonWithNullOp(const clang::BinaryOperator *expr); bool IsInMainFile(const clang::Decl *decl); -bool IsConvertibleDecl(const clang::Decl *decl); +bool IsUserDefinedDecl(const clang::Decl *decl); + +bool RefersToUserDefinedDecl(const clang::Expr *expr); bool IsUnsignedArithOp(const clang::BinaryOperator *expr); diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index e2b444f4..c86f903a 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -385,6 +385,9 @@ search(std::unordered_multimap &map, const std::string &txt, } TranslationRule::ExprRule *search(const clang::Expr *expr) { + if (RefersToUserDefinedDecl(expr)) { + return nullptr; + } auto qualified_name = ToString(expr); auto [rule, subs] = search(exprs_, qualified_name, GetExprMapKey(qualified_name)); @@ -598,7 +601,8 @@ const TranslationRule::ExprRule *GetExprRule(const clang::Expr *expr) { std::string MapFunctionName(const clang::FunctionDecl *decl) { assert(decl); - if (exprs_.contains(GetExprMapKey(ToString(decl)))) { + if (!IsUserDefinedDecl(decl) && + exprs_.contains(GetExprMapKey(ToString(decl)))) { return std::format("libcc2rs::{}_{}", decl->getNameAsString(), model_ == Model::kRefCount ? "refcount" : "unsafe"); } From adaf453d050869aede8fb5dffba37fd82c20f3be Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 10:40:42 +0100 Subject: [PATCH 02/19] Add user_defined_same_as_libc test --- .../out/refcount/user_defined_same_as_libc.rs | 27 +++++++++++++++++++ .../out/unsafe/user_defined_same_as_libc.rs | 26 ++++++++++++++++++ tests/unit/user_defined_same_as_libc.c | 10 +++++++ 3 files changed, 63 insertions(+) create mode 100644 tests/unit/out/refcount/user_defined_same_as_libc.rs create mode 100644 tests/unit/out/unsafe/user_defined_same_as_libc.rs create mode 100644 tests/unit/user_defined_same_as_libc.c diff --git a/tests/unit/out/refcount/user_defined_same_as_libc.rs b/tests/unit/out/refcount/user_defined_same_as_libc.rs new file mode 100644 index 00000000..e072e117 --- /dev/null +++ b/tests/unit/out/refcount/user_defined_same_as_libc.rs @@ -0,0 +1,27 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn strchr_0(s: Ptr, c: i32) -> Ptr { + let s: Value> = Rc::new(RefCell::new(s)); + let c: Value = Rc::new(RefCell::new(c)); + return Ptr::::null(); +} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let p: Value> = Rc::new(RefCell::new( + ({ + let _s: Ptr = Ptr::from_string_literal("hello"); + let _c: i32 = ('l' as i32); + strchr_0(_s, _c) + }), + )); + assert!(((((*p.borrow()).is_null()) as i32) != 0)); + return 0; +} diff --git a/tests/unit/out/unsafe/user_defined_same_as_libc.rs b/tests/unit/out/unsafe/user_defined_same_as_libc.rs new file mode 100644 index 00000000..8a34abfe --- /dev/null +++ b/tests/unit/out/unsafe/user_defined_same_as_libc.rs @@ -0,0 +1,26 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn strchr_0(mut s: *const u8, mut c: i32) -> *mut u8 { + return std::ptr::null_mut(); +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut p: *const u8 = (unsafe { + let _s: *const u8 = b"hello\0".as_ptr().cast_mut().cast_const(); + let _c: i32 = ('l' as i32); + strchr_0(_s, _c) + }) + .cast_const(); + assert!(((((p).is_null()) as i32) != 0)); + return 0; +} diff --git a/tests/unit/user_defined_same_as_libc.c b/tests/unit/user_defined_same_as_libc.c new file mode 100644 index 00000000..ee5d7e32 --- /dev/null +++ b/tests/unit/user_defined_same_as_libc.c @@ -0,0 +1,10 @@ +#include +#include + +char *strchr(const char *s, int c) { return NULL; } + +int main() { + const char *p = strchr("hello", 'l'); + assert(p == NULL); + return 0; +} From 2cc77802471ede96ab5f1583f123f3cd037228aa Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 14:35:54 +0100 Subject: [PATCH 03/19] Replace strchr with a function that the compiler won't replace with a builitn --- .../out/refcount/user_defined_same_as_libc.rs | 20 ++++++++++--------- .../out/unsafe/user_defined_same_as_libc.rs | 17 ++++++++-------- tests/unit/user_defined_same_as_libc.c | 12 +++++++---- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/tests/unit/out/refcount/user_defined_same_as_libc.rs b/tests/unit/out/refcount/user_defined_same_as_libc.rs index e072e117..8e294032 100644 --- a/tests/unit/out/refcount/user_defined_same_as_libc.rs +++ b/tests/unit/out/refcount/user_defined_same_as_libc.rs @@ -6,22 +6,24 @@ use std::io::prelude::*; use std::io::{Read, Seek, Write}; use std::os::fd::AsFd; use std::rc::{Rc, Weak}; -pub fn strchr_0(s: Ptr, c: i32) -> Ptr { - let s: Value> = Rc::new(RefCell::new(s)); - let c: Value = Rc::new(RefCell::new(c)); - return Ptr::::null(); +pub fn fopen_0(path: Ptr, mode: Ptr) -> Ptr<::std::fs::File> { + let path: Value> = Rc::new(RefCell::new(path)); + let mode: Value> = Rc::new(RefCell::new(mode)); + (*path.borrow()).clone(); + (*mode.borrow()).clone(); + return Ptr::null(); } pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let p: Value> = Rc::new(RefCell::new( + let fp: Value> = Rc::new(RefCell::new( ({ - let _s: Ptr = Ptr::from_string_literal("hello"); - let _c: i32 = ('l' as i32); - strchr_0(_s, _c) + let _path: Ptr = Ptr::from_string_literal("/etc/passwd"); + let _mode: Ptr = Ptr::from_string_literal("r"); + fopen_0(_path, _mode) }), )); - assert!(((((*p.borrow()).is_null()) as i32) != 0)); + assert!(((((*fp.borrow()).is_null()) as i32) != 0)); return 0; } diff --git a/tests/unit/out/unsafe/user_defined_same_as_libc.rs b/tests/unit/out/unsafe/user_defined_same_as_libc.rs index 8a34abfe..0fbe1a05 100644 --- a/tests/unit/out/unsafe/user_defined_same_as_libc.rs +++ b/tests/unit/out/unsafe/user_defined_same_as_libc.rs @@ -6,7 +6,9 @@ use std::collections::BTreeMap; use std::io::{Read, Seek, Write}; use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; use std::rc::Rc; -pub unsafe fn strchr_0(mut s: *const u8, mut c: i32) -> *mut u8 { +pub unsafe fn fopen_0(mut path: *const u8, mut mode: *const u8) -> *mut ::std::fs::File { + &(path); + &(mode); return std::ptr::null_mut(); } pub fn main() { @@ -15,12 +17,11 @@ pub fn main() { } } unsafe fn main_0() -> i32 { - let mut p: *const u8 = (unsafe { - let _s: *const u8 = b"hello\0".as_ptr().cast_mut().cast_const(); - let _c: i32 = ('l' as i32); - strchr_0(_s, _c) - }) - .cast_const(); - assert!(((((p).is_null()) as i32) != 0)); + let mut fp: *mut ::std::fs::File = (unsafe { + let _path: *const u8 = b"/etc/passwd\0".as_ptr().cast_mut().cast_const(); + let _mode: *const u8 = b"r\0".as_ptr().cast_mut().cast_const(); + fopen_0(_path, _mode) + }); + assert!(((((fp).is_null()) as i32) != 0)); return 0; } diff --git a/tests/unit/user_defined_same_as_libc.c b/tests/unit/user_defined_same_as_libc.c index ee5d7e32..82f081a4 100644 --- a/tests/unit/user_defined_same_as_libc.c +++ b/tests/unit/user_defined_same_as_libc.c @@ -1,10 +1,14 @@ #include -#include +#include -char *strchr(const char *s, int c) { return NULL; } +FILE *fopen(const char *path, const char *mode) { + (void)path; + (void)mode; + return NULL; +} int main() { - const char *p = strchr("hello", 'l'); - assert(p == NULL); + FILE *fp = fopen("/etc/passwd", "r"); + assert(fp == NULL); return 0; } From 1a34d672f5c5c7728e74ad905f6c99802e650eee Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 09:31:13 +0100 Subject: [PATCH 04/19] Move all string.h/cstring related tests in a single file --- tests/unit/cstring.cpp | 53 ++++++++ tests/unit/out/unsafe/cstring.rs | 166 ++++++++++++++++++++++++ tests/unit/out/unsafe/strchr_c.rs | 21 --- tests/unit/out/unsafe/strchr_cpp.rs | 21 --- tests/unit/out/unsafe/string_h.rs | 190 ++++++++++++++++++++++++++++ tests/unit/strchr_c.c | 12 -- tests/unit/strchr_cpp.cpp | 12 -- tests/unit/string_h.c | 53 ++++++++ 8 files changed, 462 insertions(+), 66 deletions(-) create mode 100644 tests/unit/cstring.cpp create mode 100644 tests/unit/out/unsafe/cstring.rs delete mode 100644 tests/unit/out/unsafe/strchr_c.rs delete mode 100644 tests/unit/out/unsafe/strchr_cpp.rs create mode 100644 tests/unit/out/unsafe/string_h.rs delete mode 100644 tests/unit/strchr_c.c delete mode 100644 tests/unit/strchr_cpp.cpp create mode 100644 tests/unit/string_h.c diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp new file mode 100644 index 00000000..d1cfb13e --- /dev/null +++ b/tests/unit/cstring.cpp @@ -0,0 +1,53 @@ +// no-compile: refcount +#include +#include + +static void test_memcpy() { + const char src[] = "hello"; + char dst[6] = {0}; + void *r = std::memcpy(dst, src, 6); + assert(r == dst); + assert(dst[0] == 'h' && dst[1] == 'e' && dst[2] == 'l'); + assert(dst[3] == 'l' && dst[4] == 'o' && dst[5] == '\0'); +} + +static void test_memset() { + char buf[4]; + void *r = std::memset(buf, 'x', 4); + assert(r == buf); + assert(buf[0] == 'x' && buf[1] == 'x' && buf[2] == 'x' && buf[3] == 'x'); +} + +static void test_memcmp() { + const char a[] = {1, 2, 3, 4}; + const char b[] = {1, 2, 3, 4}; + const char c[] = {1, 2, 9, 4}; + assert(std::memcmp(a, b, 4) == 0); + assert(std::memcmp(a, c, 4) < 0); + assert(std::memcmp(c, a, 4) > 0); +} + +static void test_memmove() { + char buf[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; + void *r = std::memmove(buf + 1, buf, 4); + assert(r == buf + 1); + assert(buf[0] == 'a' && buf[1] == 'a' && buf[2] == 'b'); + assert(buf[3] == 'c' && buf[4] == 'd' && buf[5] == '\0'); +} + +static void test_strchr() { + const char *s = "hello world"; + const char *r = std::strchr(s, 'w'); + assert(r != nullptr); + assert(*r == 'w'); + assert(std::strchr(s, 'z') == nullptr); +} + +int main() { + test_memcpy(); + test_memset(); + test_memcmp(); + test_memmove(); + test_strchr(); + return 0; +} diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs new file mode 100644 index 00000000..607346ae --- /dev/null +++ b/tests/unit/out/unsafe/cstring.rs @@ -0,0 +1,166 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn test_memcpy_0() { + let src: [u8; 6] = *b"hello\0"; + let mut dst: [u8; 6] = [0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8]; + let mut r: *mut ::libc::c_void = { + if 6_u64 != 0 { + ::std::ptr::copy_nonoverlapping( + (src.as_ptr() as *const u8 as *const ::libc::c_void), + (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 6_u64 as usize, + ) + } + (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) + }; + assert!(((r) == (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void))); + assert!( + (((dst[(0) as usize] as i32) == (('h' as u8) as i32)) + && ((dst[(1) as usize] as i32) == (('e' as u8) as i32))) + && ((dst[(2) as usize] as i32) == (('l' as u8) as i32)) + ); + assert!( + (((dst[(3) as usize] as i32) == (('l' as u8) as i32)) + && ((dst[(4) as usize] as i32) == (('o' as u8) as i32))) + && ((dst[(5) as usize] as i32) == (('\0' as u8) as i32)) + ); +} +pub unsafe fn test_memset_1() { + let mut buf: [u8; 4] = [0_u8; 4]; + let mut r: *mut ::libc::c_void = { + let byte_0 = (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) as *mut u8; + for offset in 0..4_u64 { + *byte_0.offset(offset as isize) = (('x' as u8) as i32) as u8; + } + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) + }; + assert!(((r) == (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void))); + assert!( + ((((buf[(0) as usize] as i32) == (('x' as u8) as i32)) + && ((buf[(1) as usize] as i32) == (('x' as u8) as i32))) + && ((buf[(2) as usize] as i32) == (('x' as u8) as i32))) + && ((buf[(3) as usize] as i32) == (('x' as u8) as i32)) + ); +} +pub unsafe fn test_memcmp_2() { + let a: [u8; 4] = [1_u8, 2_u8, 3_u8, 4_u8]; + let b: [u8; 4] = [1_u8, 2_u8, 3_u8, 4_u8]; + let c: [u8; 4] = [1_u8, 2_u8, 9_u8, 4_u8]; + assert!( + (({ + let sa = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (b.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) + ); + assert!( + (({ + let sa = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (c.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) < (0)) + ); + assert!( + (({ + let sa = core::slice::from_raw_parts( + (c.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) > (0)) + ); +} +pub unsafe fn test_memmove_3() { + let mut buf: [u8; 6] = [ + ('a' as u8), + ('b' as u8), + ('c' as u8), + ('d' as u8), + ('e' as u8), + ('\0' as u8), + ]; + let mut r: *mut ::libc::c_void = { + if 4_u64 != 0 { + ::std::ptr::copy_nonoverlapping( + (buf.as_mut_ptr() as *const u8 as *const ::libc::c_void), + (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void), + 4_u64 as usize, + ) + } + (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void) + }; + assert!(((r) == (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void))); + assert!( + (((buf[(0) as usize] as i32) == (('a' as u8) as i32)) + && ((buf[(1) as usize] as i32) == (('a' as u8) as i32))) + && ((buf[(2) as usize] as i32) == (('b' as u8) as i32)) + ); + assert!( + (((buf[(3) as usize] as i32) == (('c' as u8) as i32)) + && ((buf[(4) as usize] as i32) == (('d' as u8) as i32))) + && ((buf[(5) as usize] as i32) == (('\0' as u8) as i32)) + ); +} +pub unsafe fn test_strchr_4() { + let mut s: *const u8 = b"hello world\0".as_ptr(); + let mut r: *const u8 = libc::strchr(s as *const i8, (('w' as u8) as i32)) as *const u8; + assert!(!((r).is_null())); + assert!((((*r) as i32) == (('w' as u8) as i32))); + assert!((libc::strchr(s as *const i8, (('z' as u8) as i32)) as *const u8).is_null()); +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + (unsafe { test_memcpy_0() }); + (unsafe { test_memset_1() }); + (unsafe { test_memcmp_2() }); + (unsafe { test_memmove_3() }); + (unsafe { test_strchr_4() }); + return 0; +} diff --git a/tests/unit/out/unsafe/strchr_c.rs b/tests/unit/out/unsafe/strchr_c.rs deleted file mode 100644 index 0acaf4ad..00000000 --- a/tests/unit/out/unsafe/strchr_c.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate libc; -use libc::*; -extern crate libcc2rs; -use libcc2rs::*; -use std::collections::BTreeMap; -use std::io::{Read, Seek, Write}; -use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; -use std::rc::Rc; -pub fn main() { - unsafe { - std::process::exit(main_0() as i32); - } -} -unsafe fn main_0() -> i32 { - let mut s: *const u8 = (b"hello world\0".as_ptr().cast_mut()).cast_const(); - let mut r: *const u8 = (libc::strchr(s as *const i8, ('w' as i32)) as *mut u8).cast_const(); - assert!((((!((r).is_null())) as i32) != 0)); - assert!((((((*r) as i32) == ('w' as i32)) as i32) != 0)); - assert!(((((libc::strchr(s as *const i8, ('z' as i32)) as *mut u8).is_null()) as i32) != 0)); - return 0; -} diff --git a/tests/unit/out/unsafe/strchr_cpp.rs b/tests/unit/out/unsafe/strchr_cpp.rs deleted file mode 100644 index 309f23e6..00000000 --- a/tests/unit/out/unsafe/strchr_cpp.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate libc; -use libc::*; -extern crate libcc2rs; -use libcc2rs::*; -use std::collections::BTreeMap; -use std::io::{Read, Seek, Write}; -use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; -use std::rc::Rc; -pub fn main() { - unsafe { - std::process::exit(main_0() as i32); - } -} -unsafe fn main_0() -> i32 { - let mut s: *const u8 = b"hello world\0".as_ptr(); - let mut r: *const u8 = libc::strchr(s as *const i8, (('w' as u8) as i32)) as *const u8; - assert!(!((r).is_null())); - assert!((((*r) as i32) == (('w' as u8) as i32))); - assert!((libc::strchr(s as *const i8, (('z' as u8) as i32)) as *const u8).is_null()); - return 0; -} diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs new file mode 100644 index 00000000..47a766dc --- /dev/null +++ b/tests/unit/out/unsafe/string_h.rs @@ -0,0 +1,190 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn test_memcpy_0() { + let src: [u8; 6] = *b"hello\0"; + let mut dst: [u8; 6] = [0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8]; + let mut r: *mut ::libc::c_void = { + if 6_u64 != 0 { + ::std::ptr::copy_nonoverlapping( + (src.as_ptr() as *const u8 as *const ::libc::c_void), + (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 6_u64 as usize, + ) + } + (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) + }; + assert!(((((r) == (dst.as_mut_ptr() as *mut u8 as *mut ::libc::c_void)) as i32) != 0)); + assert!( + ((((((((((dst[(0) as usize] as i32) == ('h' as i32)) as i32) != 0) + && ((((dst[(1) as usize] as i32) == ('e' as i32)) as i32) != 0)) as i32) + != 0) + && ((((dst[(2) as usize] as i32) == ('l' as i32)) as i32) != 0)) as i32) + != 0) + ); + assert!( + ((((((((((dst[(3) as usize] as i32) == ('l' as i32)) as i32) != 0) + && ((((dst[(4) as usize] as i32) == ('o' as i32)) as i32) != 0)) as i32) + != 0) + && ((((dst[(5) as usize] as i32) == ('\0' as i32)) as i32) != 0)) as i32) + != 0) + ); +} +pub unsafe fn test_memset_1() { + let mut buf: [u8; 4] = [0_u8; 4]; + let mut r: *mut ::libc::c_void = { + let byte_0 = (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) as *mut u8; + for offset in 0..4_u64 { + *byte_0.offset(offset as isize) = ('x' as i32) as u8; + } + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void) + }; + assert!(((((r) == (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void)) as i32) != 0)); + assert!( + (((((((((((((buf[(0) as usize] as i32) == ('x' as i32)) as i32) != 0) + && ((((buf[(1) as usize] as i32) == ('x' as i32)) as i32) != 0)) + as i32) + != 0) + && ((((buf[(2) as usize] as i32) == ('x' as i32)) as i32) != 0)) as i32) + != 0) + && ((((buf[(3) as usize] as i32) == ('x' as i32)) as i32) != 0)) as i32) + != 0) + ); +} +pub unsafe fn test_memcmp_2() { + let a: [u8; 4] = [1_u8, 2_u8, 3_u8, 4_u8]; + let b: [u8; 4] = [1_u8, 2_u8, 3_u8, 4_u8]; + let c: [u8; 4] = [1_u8, 2_u8, 9_u8, 4_u8]; + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (b.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) as i32) + != 0) + ); + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (c.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) < (0)) as i32) + != 0) + ); + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (c.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (a.as_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 4_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) > (0)) as i32) + != 0) + ); +} +pub unsafe fn test_memmove_3() { + let mut buf: [u8; 6] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('c' as i32) as u8), + (('d' as i32) as u8), + (('e' as i32) as u8), + (('\0' as i32) as u8), + ]; + let mut r: *mut ::libc::c_void = { + if 4_u64 != 0 { + ::std::ptr::copy_nonoverlapping( + (buf.as_mut_ptr() as *const u8 as *const ::libc::c_void), + (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void), + 4_u64 as usize, + ) + } + (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void) + }; + assert!( + ((((r) == (buf.as_mut_ptr().offset((1) as isize) as *mut u8 as *mut ::libc::c_void)) + as i32) + != 0) + ); + assert!( + ((((((((((buf[(0) as usize] as i32) == ('a' as i32)) as i32) != 0) + && ((((buf[(1) as usize] as i32) == ('a' as i32)) as i32) != 0)) as i32) + != 0) + && ((((buf[(2) as usize] as i32) == ('b' as i32)) as i32) != 0)) as i32) + != 0) + ); + assert!( + ((((((((((buf[(3) as usize] as i32) == ('c' as i32)) as i32) != 0) + && ((((buf[(4) as usize] as i32) == ('d' as i32)) as i32) != 0)) as i32) + != 0) + && ((((buf[(5) as usize] as i32) == ('\0' as i32)) as i32) != 0)) as i32) + != 0) + ); +} +pub unsafe fn test_strchr_4() { + let mut s: *const u8 = (b"hello world\0".as_ptr().cast_mut()).cast_const(); + let mut r: *mut u8 = + libc::strchr((s as *mut u8).cast_const() as *const i8, ('w' as i32)) as *mut u8; + assert!((((!((r).is_null())) as i32) != 0)); + assert!((((((*r) as i32) == ('w' as i32)) as i32) != 0)); + assert!( + ((((libc::strchr((s as *mut u8).cast_const() as *const i8, ('z' as i32)) as *mut u8) + .is_null()) as i32) + != 0) + ); +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + (unsafe { test_memcpy_0() }); + (unsafe { test_memset_1() }); + (unsafe { test_memcmp_2() }); + (unsafe { test_memmove_3() }); + (unsafe { test_strchr_4() }); + return 0; +} diff --git a/tests/unit/strchr_c.c b/tests/unit/strchr_c.c deleted file mode 100644 index e0285064..00000000 --- a/tests/unit/strchr_c.c +++ /dev/null @@ -1,12 +0,0 @@ -// no-compile: refcount -#include -#include - -int main() { - const char *s = "hello world"; - const char *r = strchr(s, 'w'); - assert(r != NULL); - assert(*r == 'w'); - assert(strchr(s, 'z') == NULL); - return 0; -} diff --git a/tests/unit/strchr_cpp.cpp b/tests/unit/strchr_cpp.cpp deleted file mode 100644 index 4a9e5be5..00000000 --- a/tests/unit/strchr_cpp.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// no-compile: refcount -#include -#include - -int main() { - const char *s = "hello world"; - const char *r = strchr(s, 'w'); - assert(r != NULL); - assert(*r == 'w'); - assert(strchr(s, 'z') == NULL); - return 0; -} diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c new file mode 100644 index 00000000..262f63bd --- /dev/null +++ b/tests/unit/string_h.c @@ -0,0 +1,53 @@ +// no-compile: refcount +#include +#include + +static void test_memcpy(void) { + const char src[] = "hello"; + char dst[6] = {0}; + void *r = memcpy(dst, src, 6); + assert(r == dst); + assert(dst[0] == 'h' && dst[1] == 'e' && dst[2] == 'l'); + assert(dst[3] == 'l' && dst[4] == 'o' && dst[5] == '\0'); +} + +static void test_memset(void) { + char buf[4]; + void *r = memset(buf, 'x', 4); + assert(r == buf); + assert(buf[0] == 'x' && buf[1] == 'x' && buf[2] == 'x' && buf[3] == 'x'); +} + +static void test_memcmp(void) { + const char a[] = {1, 2, 3, 4}; + const char b[] = {1, 2, 3, 4}; + const char c[] = {1, 2, 9, 4}; + assert(memcmp(a, b, 4) == 0); + assert(memcmp(a, c, 4) < 0); + assert(memcmp(c, a, 4) > 0); +} + +static void test_memmove(void) { + char buf[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; + void *r = memmove(buf + 1, buf, 4); + assert(r == buf + 1); + assert(buf[0] == 'a' && buf[1] == 'a' && buf[2] == 'b'); + assert(buf[3] == 'c' && buf[4] == 'd' && buf[5] == '\0'); +} + +static void test_strchr(void) { + const char *s = "hello world"; + char *r = strchr((char *)s, 'w'); + assert(r != NULL); + assert(*r == 'w'); + assert(strchr((char *)s, 'z') == NULL); +} + +int main(void) { + test_memcpy(); + test_memset(); + test_memcmp(); + test_memmove(); + test_strchr(); + return 0; +} From 006871182216ec83fbe218293ea56c6fe3dec74d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 10:26:30 +0100 Subject: [PATCH 05/19] Add strlen rule + test --- rules/cstring/ir_unsafe.json | 25 +++++++++++++++++++++++++ rules/cstring/src.c | 2 ++ rules/cstring/tgt_unsafe.rs | 4 ++++ tests/unit/cstring.cpp | 7 +++++++ tests/unit/out/unsafe/cstring.rs | 6 ++++++ tests/unit/out/unsafe/string_h.rs | 18 ++++++++++++++++++ tests/unit/string_h.c | 7 +++++++ 7 files changed, 69 insertions(+) diff --git a/rules/cstring/ir_unsafe.json b/rules/cstring/ir_unsafe.json index 91b7b08f..4032c405 100644 --- a/rules/cstring/ir_unsafe.json +++ b/rules/cstring/ir_unsafe.json @@ -388,5 +388,30 @@ "type": "*const u8", "is_unsafe_pointer": true } + }, + "f7": { + "body": [ + { + "text": "libc::strlen(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8) as u64" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "u64" + } } } diff --git a/rules/cstring/src.c b/rules/cstring/src.c index 24cbd0e5..0d2aa6ca 100644 --- a/rules/cstring/src.c +++ b/rules/cstring/src.c @@ -12,3 +12,5 @@ int f3(const void *s1, const void *s2, size_t n) { return memcmp(s1, s2, n); } void *f4(void *dst, const void *src, size_t n) { return memmove(dst, src, n); } char *f5(const char *a0, int a1) { return strchr(a0, a1); } + +size_t f7(const char *a0) { return strlen(a0); } diff --git a/rules/cstring/tgt_unsafe.rs b/rules/cstring/tgt_unsafe.rs index 8e0ab804..d0e5207c 100644 --- a/rules/cstring/tgt_unsafe.rs +++ b/rules/cstring/tgt_unsafe.rs @@ -43,3 +43,7 @@ unsafe fn f5(a0: *const u8, a1: i32) -> *mut u8 { unsafe fn f6(a0: *const u8, a1: i32) -> *const u8 { libc::strchr(a0 as *const i8, a1) as *const u8 } + +unsafe fn f7(a0: *const u8) -> u64 { + libc::strlen(a0 as *const i8) as u64 +} diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp index d1cfb13e..3a31ac39 100644 --- a/tests/unit/cstring.cpp +++ b/tests/unit/cstring.cpp @@ -43,11 +43,18 @@ static void test_strchr() { assert(std::strchr(s, 'z') == nullptr); } +static void test_strlen() { + assert(std::strlen("") == 0); + assert(std::strlen("hello") == 5); + assert(std::strlen("hello world") == 11); +} + int main() { test_memcpy(); test_memset(); test_memcmp(); test_memmove(); test_strchr(); + test_strlen(); return 0; } diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs index 607346ae..58d124b7 100644 --- a/tests/unit/out/unsafe/cstring.rs +++ b/tests/unit/out/unsafe/cstring.rs @@ -151,6 +151,11 @@ pub unsafe fn test_strchr_4() { assert!((((*r) as i32) == (('w' as u8) as i32))); assert!((libc::strchr(s as *const i8, (('z' as u8) as i32)) as *const u8).is_null()); } +pub unsafe fn test_strlen_5() { + assert!(((libc::strlen(b"\0".as_ptr() as *const i8) as u64) == (0_u64))); + assert!(((libc::strlen(b"hello\0".as_ptr() as *const i8) as u64) == (5_u64))); + assert!(((libc::strlen(b"hello world\0".as_ptr() as *const i8) as u64) == (11_u64))); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -162,5 +167,6 @@ unsafe fn main_0() -> i32 { (unsafe { test_memcmp_2() }); (unsafe { test_memmove_3() }); (unsafe { test_strchr_4() }); + (unsafe { test_strlen_5() }); return 0; } diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs index 47a766dc..5288a184 100644 --- a/tests/unit/out/unsafe/string_h.rs +++ b/tests/unit/out/unsafe/string_h.rs @@ -175,6 +175,23 @@ pub unsafe fn test_strchr_4() { != 0) ); } +pub unsafe fn test_strlen_5() { + assert!( + ((((libc::strlen((b"\0".as_ptr().cast_mut()).cast_const() as *const i8) as u64) == (0_u64)) + as i32) + != 0) + ); + assert!( + ((((libc::strlen((b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8) as u64) + == (5_u64)) as i32) + != 0) + ); + assert!( + ((((libc::strlen((b"hello world\0".as_ptr().cast_mut()).cast_const() as *const i8) as u64) + == (11_u64)) as i32) + != 0) + ); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -186,5 +203,6 @@ unsafe fn main_0() -> i32 { (unsafe { test_memcmp_2() }); (unsafe { test_memmove_3() }); (unsafe { test_strchr_4() }); + (unsafe { test_strlen_5() }); return 0; } diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c index 262f63bd..b51a6975 100644 --- a/tests/unit/string_h.c +++ b/tests/unit/string_h.c @@ -43,11 +43,18 @@ static void test_strchr(void) { assert(strchr((char *)s, 'z') == NULL); } +static void test_strlen(void) { + assert(strlen("") == 0); + assert(strlen("hello") == 5); + assert(strlen("hello world") == 11); +} + int main(void) { test_memcpy(); test_memset(); test_memcmp(); test_memmove(); test_strchr(); + test_strlen(); return 0; } From 8972eca67d22799e5720744c0a1fa7bbd6890442 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 10:58:42 +0100 Subject: [PATCH 06/19] Add strncmp/strcmp rules + tests --- rules/cstring/ir_unsafe.json | 88 ++++++++++++++++++++++++++++ rules/cstring/src.c | 4 ++ rules/cstring/tgt_unsafe.rs | 8 +++ tests/unit/cstring.cpp | 27 +++++++++ tests/unit/out/unsafe/cstring.rs | 78 +++++++++++++++++++++++++ tests/unit/out/unsafe/string_h.rs | 95 +++++++++++++++++++++++++++++++ tests/unit/string_h.c | 27 +++++++++ 7 files changed, 327 insertions(+) diff --git a/rules/cstring/ir_unsafe.json b/rules/cstring/ir_unsafe.json index 4032c405..459ffcf3 100644 --- a/rules/cstring/ir_unsafe.json +++ b/rules/cstring/ir_unsafe.json @@ -413,5 +413,93 @@ "return_type": { "type": "u64" } + }, + "f8": { + "body": [ + { + "text": "libc::strcmp(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, + "f9": { + "body": [ + { + "text": "libc::strncmp(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "i32" + } } } diff --git a/rules/cstring/src.c b/rules/cstring/src.c index 0d2aa6ca..9ad46ccf 100644 --- a/rules/cstring/src.c +++ b/rules/cstring/src.c @@ -14,3 +14,7 @@ void *f4(void *dst, const void *src, size_t n) { return memmove(dst, src, n); } char *f5(const char *a0, int a1) { return strchr(a0, a1); } size_t f7(const char *a0) { return strlen(a0); } + +int f8(const char *a0, const char *a1) { return strcmp(a0, a1); } + +int f9(const char *a0, const char *a1, size_t a2) { return strncmp(a0, a1, a2); } diff --git a/rules/cstring/tgt_unsafe.rs b/rules/cstring/tgt_unsafe.rs index d0e5207c..0193fce7 100644 --- a/rules/cstring/tgt_unsafe.rs +++ b/rules/cstring/tgt_unsafe.rs @@ -47,3 +47,11 @@ unsafe fn f6(a0: *const u8, a1: i32) -> *const u8 { unsafe fn f7(a0: *const u8) -> u64 { libc::strlen(a0 as *const i8) as u64 } + +unsafe fn f8(a0: *const u8, a1: *const u8) -> i32 { + libc::strcmp(a0 as *const i8, a1 as *const i8) +} + +unsafe fn f9(a0: *const u8, a1: *const u8, a2: usize) -> i32 { + libc::strncmp(a0 as *const i8, a1 as *const i8, a2 as usize) +} diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp index 3a31ac39..41d5f018 100644 --- a/tests/unit/cstring.cpp +++ b/tests/unit/cstring.cpp @@ -49,6 +49,31 @@ static void test_strlen() { assert(std::strlen("hello world") == 11); } +static void test_strcmp() { + assert(std::strcmp("abc", "abc") == 0); + assert(std::strcmp("abc", "abd") < 0); + assert(std::strcmp("abd", "abc") > 0); + const char *p = "abc"; + const char *q = "abd"; + char buf[] = {'a', 'b', 'c', '\0'}; + assert(std::strcmp(p, p) == 0); + assert(std::strcmp(p, q) < 0); + assert(std::strcmp(buf, p) == 0); +} + +static void test_strncmp() { + assert(std::strncmp("abcdef", "abcxyz", 3) == 0); + assert(std::strncmp("abcdef", "abcxyz", 4) < 0); + assert(std::strncmp("abcxyz", "abcdef", 4) > 0); + const char *p = "abcdef"; + const char *q = "abcxyz"; + char buf[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'}; + std::size_t n = 3; + assert(std::strncmp(p, q, n) == 0); + assert(std::strncmp(p, q, n + 1) < 0); + assert(std::strncmp(buf, p, 6) == 0); +} + int main() { test_memcpy(); test_memset(); @@ -56,5 +81,7 @@ int main() { test_memmove(); test_strchr(); test_strlen(); + test_strcmp(); + test_strncmp(); return 0; } diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs index 58d124b7..5ad20ff2 100644 --- a/tests/unit/out/unsafe/cstring.rs +++ b/tests/unit/out/unsafe/cstring.rs @@ -156,6 +156,82 @@ pub unsafe fn test_strlen_5() { assert!(((libc::strlen(b"hello\0".as_ptr() as *const i8) as u64) == (5_u64))); assert!(((libc::strlen(b"hello world\0".as_ptr() as *const i8) as u64) == (11_u64))); } +pub unsafe fn test_strcmp_6() { + assert!( + ((libc::strcmp( + b"abc\0".as_ptr() as *const i8, + b"abc\0".as_ptr() as *const i8 + )) == (0)) + ); + assert!( + ((libc::strcmp( + b"abc\0".as_ptr() as *const i8, + b"abd\0".as_ptr() as *const i8 + )) < (0)) + ); + assert!( + ((libc::strcmp( + b"abd\0".as_ptr() as *const i8, + b"abc\0".as_ptr() as *const i8 + )) > (0)) + ); + let mut p: *const u8 = b"abc\0".as_ptr(); + let mut q: *const u8 = b"abd\0".as_ptr(); + let mut buf: [u8; 4] = [('a' as u8), ('b' as u8), ('c' as u8), ('\0' as u8)]; + assert!(((libc::strcmp(p as *const i8, p as *const i8)) == (0))); + assert!(((libc::strcmp(p as *const i8, q as *const i8)) < (0))); + assert!(((libc::strcmp((buf.as_mut_ptr()).cast_const() as *const i8, p as *const i8)) == (0))); +} +pub unsafe fn test_strncmp_7() { + assert!( + ((libc::strncmp( + b"abcdef\0".as_ptr() as *const i8, + b"abcxyz\0".as_ptr() as *const i8, + 3_u64 as usize + )) == (0)) + ); + assert!( + ((libc::strncmp( + b"abcdef\0".as_ptr() as *const i8, + b"abcxyz\0".as_ptr() as *const i8, + 4_u64 as usize + )) < (0)) + ); + assert!( + ((libc::strncmp( + b"abcxyz\0".as_ptr() as *const i8, + b"abcdef\0".as_ptr() as *const i8, + 4_u64 as usize + )) > (0)) + ); + let mut p: *const u8 = b"abcdef\0".as_ptr(); + let mut q: *const u8 = b"abcxyz\0".as_ptr(); + let mut buf: [u8; 7] = [ + ('a' as u8), + ('b' as u8), + ('c' as u8), + ('d' as u8), + ('e' as u8), + ('f' as u8), + ('\0' as u8), + ]; + let mut n: u64 = 3_u64; + assert!(((libc::strncmp(p as *const i8, q as *const i8, n as usize)) == (0))); + assert!( + ((libc::strncmp( + p as *const i8, + q as *const i8, + (n).wrapping_add(1_u64) as usize + )) < (0)) + ); + assert!( + ((libc::strncmp( + (buf.as_mut_ptr()).cast_const() as *const i8, + p as *const i8, + 6_u64 as usize + )) == (0)) + ); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -168,5 +244,7 @@ unsafe fn main_0() -> i32 { (unsafe { test_memmove_3() }); (unsafe { test_strchr_4() }); (unsafe { test_strlen_5() }); + (unsafe { test_strcmp_6() }); + (unsafe { test_strncmp_7() }); return 0; } diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs index 5288a184..fa9c63b9 100644 --- a/tests/unit/out/unsafe/string_h.rs +++ b/tests/unit/out/unsafe/string_h.rs @@ -192,6 +192,99 @@ pub unsafe fn test_strlen_5() { != 0) ); } +pub unsafe fn test_strcmp_6() { + assert!( + ((((libc::strcmp( + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) == (0)) as i32) + != 0) + ); + assert!( + ((((libc::strcmp( + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abd\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) < (0)) as i32) + != 0) + ); + assert!( + ((((libc::strcmp( + (b"abd\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) > (0)) as i32) + != 0) + ); + let mut p: *const u8 = (b"abc\0".as_ptr().cast_mut()).cast_const(); + let mut q: *const u8 = (b"abd\0".as_ptr().cast_mut()).cast_const(); + let mut buf: [u8; 4] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('c' as i32) as u8), + (('\0' as i32) as u8), + ]; + assert!(((((libc::strcmp(p as *const i8, p as *const i8)) == (0)) as i32) != 0)); + assert!(((((libc::strcmp(p as *const i8, q as *const i8)) < (0)) as i32) != 0)); + assert!( + ((((libc::strcmp((buf.as_mut_ptr()).cast_const() as *const i8, p as *const i8)) == (0)) + as i32) + != 0) + ); +} +pub unsafe fn test_strncmp_7() { + assert!( + ((((libc::strncmp( + (b"abcdef\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abcxyz\0".as_ptr().cast_mut()).cast_const() as *const i8, + 3_u64 as usize + )) == (0)) as i32) + != 0) + ); + assert!( + ((((libc::strncmp( + (b"abcdef\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abcxyz\0".as_ptr().cast_mut()).cast_const() as *const i8, + 4_u64 as usize + )) < (0)) as i32) + != 0) + ); + assert!( + ((((libc::strncmp( + (b"abcxyz\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abcdef\0".as_ptr().cast_mut()).cast_const() as *const i8, + 4_u64 as usize + )) > (0)) as i32) + != 0) + ); + let mut p: *const u8 = (b"abcdef\0".as_ptr().cast_mut()).cast_const(); + let mut q: *const u8 = (b"abcxyz\0".as_ptr().cast_mut()).cast_const(); + let mut buf: [u8; 7] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('c' as i32) as u8), + (('d' as i32) as u8), + (('e' as i32) as u8), + (('f' as i32) as u8), + (('\0' as i32) as u8), + ]; + let mut n: u64 = 3_u64; + assert!(((((libc::strncmp(p as *const i8, q as *const i8, n as usize)) == (0)) as i32) != 0)); + assert!( + ((((libc::strncmp( + p as *const i8, + q as *const i8, + (n).wrapping_add(1_u64) as usize + )) < (0)) as i32) + != 0) + ); + assert!( + ((((libc::strncmp( + (buf.as_mut_ptr()).cast_const() as *const i8, + p as *const i8, + 6_u64 as usize + )) == (0)) as i32) + != 0) + ); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -204,5 +297,7 @@ unsafe fn main_0() -> i32 { (unsafe { test_memmove_3() }); (unsafe { test_strchr_4() }); (unsafe { test_strlen_5() }); + (unsafe { test_strcmp_6() }); + (unsafe { test_strncmp_7() }); return 0; } diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c index b51a6975..da03f9ae 100644 --- a/tests/unit/string_h.c +++ b/tests/unit/string_h.c @@ -49,6 +49,31 @@ static void test_strlen(void) { assert(strlen("hello world") == 11); } +static void test_strcmp(void) { + assert(strcmp("abc", "abc") == 0); + assert(strcmp("abc", "abd") < 0); + assert(strcmp("abd", "abc") > 0); + const char *p = "abc"; + const char *q = "abd"; + char buf[] = {'a', 'b', 'c', '\0'}; + assert(strcmp(p, p) == 0); + assert(strcmp(p, q) < 0); + assert(strcmp(buf, p) == 0); +} + +static void test_strncmp(void) { + assert(strncmp("abcdef", "abcxyz", 3) == 0); + assert(strncmp("abcdef", "abcxyz", 4) < 0); + assert(strncmp("abcxyz", "abcdef", 4) > 0); + const char *p = "abcdef"; + const char *q = "abcxyz"; + char buf[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'}; + size_t n = 3; + assert(strncmp(p, q, n) == 0); + assert(strncmp(p, q, n + 1) < 0); + assert(strncmp(buf, p, 6) == 0); +} + int main(void) { test_memcpy(); test_memset(); @@ -56,5 +81,7 @@ int main(void) { test_memmove(); test_strchr(); test_strlen(); + test_strcmp(); + test_strncmp(); return 0; } From cbdf169f5b1eb4503b229367b56cc34d075ff007 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 11:23:37 +0100 Subject: [PATCH 07/19] Add strdup rules + tests --- rules/cstring/ir_unsafe.json | 240 ++++++++++++++++++++++++++++++ rules/cstring/src.c | 6 + rules/cstring/tgt_unsafe.rs | 24 +++ tests/unit/cstring.cpp | 42 ++++++ tests/unit/out/unsafe/cstring.rs | 63 ++++++++ tests/unit/out/unsafe/string_h.rs | 91 +++++++++++ tests/unit/string_h.c | 42 ++++++ 7 files changed, 508 insertions(+) diff --git a/rules/cstring/ir_unsafe.json b/rules/cstring/ir_unsafe.json index 459ffcf3..5a38c21a 100644 --- a/rules/cstring/ir_unsafe.json +++ b/rules/cstring/ir_unsafe.json @@ -66,6 +66,246 @@ "is_unsafe_pointer": true } }, + "f10": { + "body": [ + { + "text": "libc::memchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const ::libc::c_void, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "*mut ::libc::c_void", + "is_unsafe_pointer": true + } + }, + "f11": { + "body": [ + { + "text": "libc::strrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ") as *mut u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f12": { + "body": [ + { + "text": "libc::memchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const ::libc::c_void, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize) as *const ::libc::c_void" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "*const ::libc::c_void", + "is_unsafe_pointer": true + } + }, + "f13": { + "body": [ + { + "text": "libc::strrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ") as *const u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + } + }, + "return_type": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "f14": { + "body": [ + { + "text": "libc::strrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ") as *mut u8" + } + ], + "params": { + "a0": { + "type": "*mut u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f15": { + "body": [ + { + "text": "libc::strdup(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8) as *mut u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, "f2": { "body": [ { diff --git a/rules/cstring/src.c b/rules/cstring/src.c index 9ad46ccf..6651c811 100644 --- a/rules/cstring/src.c +++ b/rules/cstring/src.c @@ -18,3 +18,9 @@ size_t f7(const char *a0) { return strlen(a0); } int f8(const char *a0, const char *a1) { return strcmp(a0, a1); } int f9(const char *a0, const char *a1, size_t a2) { return strncmp(a0, a1, a2); } + +void *f10(const void *a0, int a1, size_t a2) { return memchr(a0, a1, a2); } + +char *f11(const char *a0, int a1) { return strrchr(a0, a1); } + +char *f15(const char *a0) { return strdup(a0); } diff --git a/rules/cstring/tgt_unsafe.rs b/rules/cstring/tgt_unsafe.rs index 0193fce7..d0afbca5 100644 --- a/rules/cstring/tgt_unsafe.rs +++ b/rules/cstring/tgt_unsafe.rs @@ -55,3 +55,27 @@ unsafe fn f8(a0: *const u8, a1: *const u8) -> i32 { unsafe fn f9(a0: *const u8, a1: *const u8, a2: usize) -> i32 { libc::strncmp(a0 as *const i8, a1 as *const i8, a2 as usize) } + +unsafe fn f10(a0: *const u8, a1: i32, a2: usize) -> *mut ::libc::c_void { + libc::memchr(a0 as *const ::libc::c_void, a1, a2 as usize) +} + +unsafe fn f11(a0: *const u8, a1: i32) -> *mut u8 { + libc::strrchr(a0 as *const i8, a1) as *mut u8 +} + +unsafe fn f12(a0: *const u8, a1: i32, a2: usize) -> *const ::libc::c_void { + libc::memchr(a0 as *const ::libc::c_void, a1, a2 as usize) as *const ::libc::c_void +} + +unsafe fn f13(a0: *const u8, a1: i32) -> *const u8 { + libc::strrchr(a0 as *const i8, a1) as *const u8 +} + +unsafe fn f14(a0: *mut u8, a1: i32) -> *mut u8 { + libc::strrchr(a0 as *const i8, a1) as *mut u8 +} + +unsafe fn f15(a0: *const u8) -> *mut u8 { + libc::strdup(a0 as *const i8) as *mut u8 +} diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp index 41d5f018..c3d86f5f 100644 --- a/tests/unit/cstring.cpp +++ b/tests/unit/cstring.cpp @@ -1,5 +1,6 @@ // no-compile: refcount #include +#include #include static void test_memcpy() { @@ -74,6 +75,44 @@ static void test_strncmp() { assert(std::strncmp(buf, p, 6) == 0); } +static void test_memchr() { + const char data[] = {0x10, 0x20, 0x30, 0x40}; + const void *r = std::memchr(data, 0x30, 4); + assert(r == &data[2]); + assert(std::memchr(data, 0x99, 4) == nullptr); + const void *p = data; + std::size_t n = 4; + assert(std::memchr(p, 0x10, n) == p); +} + +static void test_strrchr() { + const char *s = "hello world"; + const char *r = std::strrchr(s, 'l'); + assert(r != nullptr); + assert(*r == 'l'); + assert(r == s + 9); + assert(std::strrchr(s, 'z') == nullptr); + char buf[] = {'a', 'b', 'a', '\0'}; + assert(std::strrchr(buf, 'a') == &buf[2]); +} + +static void test_strdup() { + char *d = strdup("hello"); + assert(d != nullptr); + assert(std::strcmp(d, "hello") == 0); + std::free(d); + const char *p = "world"; + char buf[] = {'a', 'b', 'c', '\0'}; + char *d2 = strdup(p); + assert(d2 != nullptr); + assert(std::strcmp(d2, p) == 0); + std::free(d2); + char *d3 = strdup(buf); + assert(d3 != nullptr); + assert(std::strcmp(d3, buf) == 0); + std::free(d3); +} + int main() { test_memcpy(); test_memset(); @@ -83,5 +122,8 @@ int main() { test_strlen(); test_strcmp(); test_strncmp(); + test_memchr(); + test_strrchr(); + test_strdup(); return 0; } diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs index 5ad20ff2..4f97de01 100644 --- a/tests/unit/out/unsafe/cstring.rs +++ b/tests/unit/out/unsafe/cstring.rs @@ -232,6 +232,66 @@ pub unsafe fn test_strncmp_7() { )) == (0)) ); } +pub unsafe fn test_memchr_8() { + let data: [u8; 4] = [16_u8, 32_u8, 48_u8, 64_u8]; + let mut r: *const ::libc::c_void = libc::memchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 48, + 4_u64 as usize, + ) as *const ::libc::c_void; + assert!(((r) == ((&data[(2) as usize] as *const u8) as *const u8 as *const ::libc::c_void))); + assert!((libc::memchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 153, + 4_u64 as usize + ) as *const ::libc::c_void) + .is_null()); + let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); + let mut n: u64 = 4_u64; + assert!( + ((libc::memchr(p as *const ::libc::c_void, 16, n as usize) as *const ::libc::c_void) + == (p)) + ); +} +pub unsafe fn test_strrchr_9() { + let mut s: *const u8 = b"hello world\0".as_ptr(); + let mut r: *const u8 = libc::strrchr(s as *const i8, (('l' as u8) as i32)) as *const u8; + assert!(!((r).is_null())); + assert!((((*r) as i32) == (('l' as u8) as i32))); + assert!(((r) == (s.offset((9) as isize)))); + assert!((libc::strrchr(s as *const i8, (('z' as u8) as i32)) as *const u8).is_null()); + let mut buf: [u8; 4] = [('a' as u8), ('b' as u8), ('a' as u8), ('\0' as u8)]; + assert!( + ((libc::strrchr(buf.as_mut_ptr() as *const i8, (('a' as u8) as i32)) as *mut u8) + == (&mut buf[(2) as usize] as *mut u8)) + ); +} +pub unsafe fn test_strdup_10() { + let mut d: *mut u8 = libc::strdup(b"hello\0".as_ptr() as *const i8) as *mut u8; + assert!(!((d).is_null())); + assert!( + ((libc::strcmp( + (d).cast_const() as *const i8, + b"hello\0".as_ptr() as *const i8 + )) == (0)) + ); + libc::free((d as *mut u8 as *mut ::libc::c_void)); + let mut p: *const u8 = b"world\0".as_ptr(); + let mut buf: [u8; 4] = [('a' as u8), ('b' as u8), ('c' as u8), ('\0' as u8)]; + let mut d2: *mut u8 = libc::strdup(p as *const i8) as *mut u8; + assert!(!((d2).is_null())); + assert!(((libc::strcmp((d2).cast_const() as *const i8, p as *const i8)) == (0))); + libc::free((d2 as *mut u8 as *mut ::libc::c_void)); + let mut d3: *mut u8 = libc::strdup((buf.as_mut_ptr()).cast_const() as *const i8) as *mut u8; + assert!(!((d3).is_null())); + assert!( + ((libc::strcmp( + (d3).cast_const() as *const i8, + (buf.as_mut_ptr()).cast_const() as *const i8 + )) == (0)) + ); + libc::free((d3 as *mut u8 as *mut ::libc::c_void)); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -246,5 +306,8 @@ unsafe fn main_0() -> i32 { (unsafe { test_strlen_5() }); (unsafe { test_strcmp_6() }); (unsafe { test_strncmp_7() }); + (unsafe { test_memchr_8() }); + (unsafe { test_strrchr_9() }); + (unsafe { test_strdup_10() }); return 0; } diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs index fa9c63b9..7f8bace9 100644 --- a/tests/unit/out/unsafe/string_h.rs +++ b/tests/unit/out/unsafe/string_h.rs @@ -285,6 +285,94 @@ pub unsafe fn test_strncmp_7() { != 0) ); } +pub unsafe fn test_memchr_8() { + let data: [u8; 4] = [16_u8, 32_u8, 48_u8, 64_u8]; + let mut r: *mut ::libc::c_void = libc::memchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 48, + 4_u64 as usize, + ); + assert!( + ((((r) == ((&data[(2) as usize] as *const u8) as *mut u8 as *mut ::libc::c_void)) as i32) + != 0) + ); + assert!( + ((((libc::memchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 153, + 4_u64 as usize + )) + .is_null()) as i32) + != 0) + ); + let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); + let mut n: u64 = 4_u64; + assert!( + ((((libc::memchr(p as *const ::libc::c_void, 16, n as usize)) + == (p as *mut ::libc::c_void as *mut ::libc::c_void)) as i32) + != 0) + ); +} +pub unsafe fn test_strrchr_9() { + let mut s: *const u8 = (b"hello world\0".as_ptr().cast_mut()).cast_const(); + let mut r: *mut u8 = + libc::strrchr((s as *mut u8).cast_const() as *const i8, ('l' as i32)) as *mut u8; + assert!((((!((r).is_null())) as i32) != 0)); + assert!((((((*r) as i32) == ('l' as i32)) as i32) != 0)); + assert!(((((r) == (s.offset((9) as isize) as *mut u8)) as i32) != 0)); + assert!( + ((((libc::strrchr((s as *mut u8).cast_const() as *const i8, ('z' as i32)) as *mut u8) + .is_null()) as i32) + != 0) + ); + let mut buf: [u8; 4] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('a' as i32) as u8), + (('\0' as i32) as u8), + ]; + assert!( + ((((libc::strrchr((buf.as_mut_ptr()).cast_const() as *const i8, ('a' as i32)) as *mut u8) + == (&mut buf[(2) as usize] as *mut u8)) as i32) + != 0) + ); +} +pub unsafe fn test_strdup_10() { + let mut d: *mut u8 = + libc::strdup((b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8) as *mut u8; + assert!((((!((d).is_null())) as i32) != 0)); + assert!( + ((((libc::strcmp( + (d).cast_const() as *const i8, + (b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) == (0)) as i32) + != 0) + ); + libc::free((d as *mut u8 as *mut ::libc::c_void)); + let mut p: *const u8 = (b"world\0".as_ptr().cast_mut()).cast_const(); + let mut buf: [u8; 4] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('c' as i32) as u8), + (('\0' as i32) as u8), + ]; + let mut d2: *mut u8 = libc::strdup(p as *const i8) as *mut u8; + assert!((((!((d2).is_null())) as i32) != 0)); + assert!( + ((((libc::strcmp((d2).cast_const() as *const i8, p as *const i8)) == (0)) as i32) != 0) + ); + libc::free((d2 as *mut u8 as *mut ::libc::c_void)); + let mut d3: *mut u8 = libc::strdup((buf.as_mut_ptr()).cast_const() as *const i8) as *mut u8; + assert!((((!((d3).is_null())) as i32) != 0)); + assert!( + ((((libc::strcmp( + (d3).cast_const() as *const i8, + (buf.as_mut_ptr()).cast_const() as *const i8 + )) == (0)) as i32) + != 0) + ); + libc::free((d3 as *mut u8 as *mut ::libc::c_void)); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -299,5 +387,8 @@ unsafe fn main_0() -> i32 { (unsafe { test_strlen_5() }); (unsafe { test_strcmp_6() }); (unsafe { test_strncmp_7() }); + (unsafe { test_memchr_8() }); + (unsafe { test_strrchr_9() }); + (unsafe { test_strdup_10() }); return 0; } diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c index da03f9ae..0da9b90f 100644 --- a/tests/unit/string_h.c +++ b/tests/unit/string_h.c @@ -1,5 +1,6 @@ // no-compile: refcount #include +#include #include static void test_memcpy(void) { @@ -74,6 +75,44 @@ static void test_strncmp(void) { assert(strncmp(buf, p, 6) == 0); } +static void test_memchr(void) { + const char data[] = {0x10, 0x20, 0x30, 0x40}; + void *r = memchr(data, 0x30, 4); + assert(r == &data[2]); + assert(memchr(data, 0x99, 4) == NULL); + const void *p = data; + size_t n = 4; + assert(memchr(p, 0x10, n) == p); +} + +static void test_strrchr(void) { + const char *s = "hello world"; + char *r = strrchr((char *)s, 'l'); + assert(r != NULL); + assert(*r == 'l'); + assert(r == s + 9); + assert(strrchr((char *)s, 'z') == NULL); + char buf[] = {'a', 'b', 'a', '\0'}; + assert(strrchr(buf, 'a') == &buf[2]); +} + +static void test_strdup(void) { + char *d = strdup("hello"); + assert(d != NULL); + assert(strcmp(d, "hello") == 0); + free(d); + const char *p = "world"; + char buf[] = {'a', 'b', 'c', '\0'}; + char *d2 = strdup(p); + assert(d2 != NULL); + assert(strcmp(d2, p) == 0); + free(d2); + char *d3 = strdup(buf); + assert(d3 != NULL); + assert(strcmp(d3, buf) == 0); + free(d3); +} + int main(void) { test_memcpy(); test_memset(); @@ -83,5 +122,8 @@ int main(void) { test_strlen(); test_strcmp(); test_strncmp(); + test_memchr(); + test_strrchr(); + test_strdup(); return 0; } From 9016882026c5ca7f87bd9f655453830317fd841c Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 12:04:14 +0100 Subject: [PATCH 08/19] Add more string rules --- rules/cstring/ir_unsafe.json | 498 ++++++++++++++++++++++++++++++ rules/cstring/src.c | 14 + rules/cstring/src.cpp | 19 ++ rules/cstring/tgt_unsafe.rs | 48 +++ tests/unit/cstring.cpp | 65 ++++ tests/unit/out/unsafe/cstring.rs | 129 ++++++++ tests/unit/out/unsafe/string_h.rs | 182 +++++++++++ tests/unit/string_h.c | 65 ++++ 8 files changed, 1020 insertions(+) diff --git a/rules/cstring/ir_unsafe.json b/rules/cstring/ir_unsafe.json index 5a38c21a..28190efd 100644 --- a/rules/cstring/ir_unsafe.json +++ b/rules/cstring/ir_unsafe.json @@ -306,6 +306,160 @@ "is_unsafe_pointer": true } }, + "f16": { + "body": [ + { + "text": "libc::strcspn(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as u64" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "u64" + } + }, + "f17": { + "body": [ + { + "text": "libc::strspn(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as u64" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "u64" + } + }, + "f18": { + "body": [ + { + "text": "libc::strstr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *mut u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f19": { + "body": [ + { + "text": "libc::strstr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *const u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, "f2": { "body": [ { @@ -381,6 +535,350 @@ "is_unsafe_pointer": true } }, + "f20": { + "body": [ + { + "text": "libc::strstr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *mut u8" + } + ], + "params": { + "a0": { + "type": "*mut u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f21": { + "body": [ + { + "text": "libc::strpbrk(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *mut u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f22": { + "body": [ + { + "text": "libc::strpbrk(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *const u8" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "f23": { + "body": [ + { + "text": "libc::strpbrk(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8) as *mut u8" + } + ], + "params": { + "a0": { + "type": "*mut u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "*mut u8", + "is_unsafe_pointer": true + } + }, + "f24": { + "body": [ + { + "text": "libc::memrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const ::libc::c_void, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "*mut ::libc::c_void", + "is_unsafe_pointer": true + } + }, + "f25": { + "body": [ + { + "text": "libc::memrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const ::libc::c_void, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize) as *const ::libc::c_void" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "*const ::libc::c_void", + "is_unsafe_pointer": true + } + }, + "f26": { + "body": [ + { + "text": "libc::memrchr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const ::libc::c_void, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize)" + } + ], + "params": { + "a0": { + "type": "*mut u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "i32" + }, + "a2": { + "type": "usize" + } + }, + "return_type": { + "type": "*mut ::libc::c_void", + "is_unsafe_pointer": true + } + }, + "f27": { + "body": [ + { + "text": "libc::strcasecmp(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, "f3": { "body": [ { diff --git a/rules/cstring/src.c b/rules/cstring/src.c index 6651c811..2f751e50 100644 --- a/rules/cstring/src.c +++ b/rules/cstring/src.c @@ -1,7 +1,9 @@ // Copyright (c) 2022-present INESC-ID. // Distributed under the MIT license that can be found in the LICENSE file. +#define _GNU_SOURCE #include +#include void *f1(void *dst, const void *src, size_t n) { return memcpy(dst, src, n); } @@ -24,3 +26,15 @@ void *f10(const void *a0, int a1, size_t a2) { return memchr(a0, a1, a2); } char *f11(const char *a0, int a1) { return strrchr(a0, a1); } char *f15(const char *a0) { return strdup(a0); } + +size_t f16(const char *a0, const char *a1) { return strcspn(a0, a1); } + +size_t f17(const char *a0, const char *a1) { return strspn(a0, a1); } + +char *f18(const char *a0, const char *a1) { return strstr(a0, a1); } + +char *f21(const char *a0, const char *a1) { return strpbrk(a0, a1); } + +void *f24(const void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } + +int f27(const char *a0, const char *a1) { return strcasecmp(a0, a1); } diff --git a/rules/cstring/src.cpp b/rules/cstring/src.cpp index 3e1c77cc..bac269b3 100644 --- a/rules/cstring/src.cpp +++ b/rules/cstring/src.cpp @@ -1,6 +1,25 @@ // Copyright (c) 2022-present INESC-ID. // Distributed under the MIT license that can be found in the LICENSE file. +#define _GNU_SOURCE #include const char *f6(const char *a0, int a1) { return strchr(a0, a1); } + +const void *f12(const void *a0, int a1, size_t a2) { return memchr(a0, a1, a2); } + +const char *f13(const char *a0, int a1) { return strrchr(a0, a1); } + +char *f14(char *a0, int a1) { return strrchr(a0, a1); } + +const char *f19(const char *a0, const char *a1) { return strstr(a0, a1); } + +char *f20(char *a0, const char *a1) { return strstr(a0, a1); } + +const char *f22(const char *a0, const char *a1) { return strpbrk(a0, a1); } + +char *f23(char *a0, const char *a1) { return strpbrk(a0, a1); } + +const void *f25(const void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } + +void *f26(void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } diff --git a/rules/cstring/tgt_unsafe.rs b/rules/cstring/tgt_unsafe.rs index d0afbca5..9c6f6e95 100644 --- a/rules/cstring/tgt_unsafe.rs +++ b/rules/cstring/tgt_unsafe.rs @@ -79,3 +79,51 @@ unsafe fn f14(a0: *mut u8, a1: i32) -> *mut u8 { unsafe fn f15(a0: *const u8) -> *mut u8 { libc::strdup(a0 as *const i8) as *mut u8 } + +unsafe fn f16(a0: *const u8, a1: *const u8) -> u64 { + libc::strcspn(a0 as *const i8, a1 as *const i8) as u64 +} + +unsafe fn f17(a0: *const u8, a1: *const u8) -> u64 { + libc::strspn(a0 as *const i8, a1 as *const i8) as u64 +} + +unsafe fn f18(a0: *const u8, a1: *const u8) -> *mut u8 { + libc::strstr(a0 as *const i8, a1 as *const i8) as *mut u8 +} + +unsafe fn f19(a0: *const u8, a1: *const u8) -> *const u8 { + libc::strstr(a0 as *const i8, a1 as *const i8) as *const u8 +} + +unsafe fn f20(a0: *mut u8, a1: *const u8) -> *mut u8 { + libc::strstr(a0 as *const i8, a1 as *const i8) as *mut u8 +} + +unsafe fn f21(a0: *const u8, a1: *const u8) -> *mut u8 { + libc::strpbrk(a0 as *const i8, a1 as *const i8) as *mut u8 +} + +unsafe fn f22(a0: *const u8, a1: *const u8) -> *const u8 { + libc::strpbrk(a0 as *const i8, a1 as *const i8) as *const u8 +} + +unsafe fn f23(a0: *mut u8, a1: *const u8) -> *mut u8 { + libc::strpbrk(a0 as *const i8, a1 as *const i8) as *mut u8 +} + +unsafe fn f24(a0: *const u8, a1: i32, a2: usize) -> *mut ::libc::c_void { + libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) +} + +unsafe fn f25(a0: *const u8, a1: i32, a2: usize) -> *const ::libc::c_void { + libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) as *const ::libc::c_void +} + +unsafe fn f26(a0: *mut u8, a1: i32, a2: usize) -> *mut ::libc::c_void { + libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) +} + +unsafe fn f27(a0: *const u8, a1: *const u8) -> i32 { + libc::strcasecmp(a0 as *const i8, a1 as *const i8) +} diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp index c3d86f5f..7a361393 100644 --- a/tests/unit/cstring.cpp +++ b/tests/unit/cstring.cpp @@ -1,7 +1,9 @@ // no-compile: refcount +#define _GNU_SOURCE #include #include #include +#include static void test_memcpy() { const char src[] = "hello"; @@ -113,6 +115,63 @@ static void test_strdup() { std::free(d3); } +static void test_strcspn() { + assert(std::strcspn("hello", "el") == 1); + assert(std::strcspn("abc", "xyz") == 3); + assert(std::strcspn("", "abc") == 0); + const char *s = "hello"; + const char *rej = "el"; + assert(std::strcspn(s, rej) == 1); +} + +static void test_strspn() { + assert(std::strspn("hello", "hel") == 4); + assert(std::strspn("abc", "xyz") == 0); + assert(std::strspn("aaa", "a") == 3); + const char *s = "hello"; + const char *acc = "hel"; + assert(std::strspn(s, acc) == 4); +} + +static void test_strstr() { + const char *h = "hello world"; + const char *r = std::strstr(h, "world"); + assert(r != nullptr); + assert(r == h + 6); + assert(std::strstr(h, "xyz") == nullptr); + char buf[] = {'h', 'e', 'l', 'l', 'o', '\0'}; + assert(std::strstr(buf, "ll") == &buf[2]); +} + +static void test_strpbrk() { + const char *s = "hello world"; + const char *r = std::strpbrk(s, "wo"); + assert(r != nullptr); + assert(r == s + 4); + assert(std::strpbrk(s, "xyz") == nullptr); + char buf[] = {'a', 'b', 'c', '\0'}; + assert(std::strpbrk(buf, "b") == &buf[1]); +} + +static void test_memrchr() { + const char data[] = {1, 2, 3, 2, 4}; + const void *r = memrchr(data, 2, 5); + assert(r == &data[3]); + assert(memrchr(data, 99, 5) == nullptr); + const void *p = data; + std::size_t n = 5; + assert(memrchr(p, 1, n) == p); +} + +static void test_strcasecmp() { + assert(strcasecmp("HELLO", "hello") == 0); + assert(strcasecmp("abc", "abd") < 0); + assert(strcasecmp("abd", "abc") > 0); + const char *p = "FOO"; + const char *q = "foo"; + assert(strcasecmp(p, q) == 0); +} + int main() { test_memcpy(); test_memset(); @@ -125,5 +184,11 @@ int main() { test_memchr(); test_strrchr(); test_strdup(); + test_strcspn(); + test_strspn(); + test_strstr(); + test_strpbrk(); + test_memrchr(); + test_strcasecmp(); return 0; } diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs index 4f97de01..d9feb41c 100644 --- a/tests/unit/out/unsafe/cstring.rs +++ b/tests/unit/out/unsafe/cstring.rs @@ -292,6 +292,129 @@ pub unsafe fn test_strdup_10() { ); libc::free((d3 as *mut u8 as *mut ::libc::c_void)); } +pub unsafe fn test_strcspn_11() { + assert!( + ((libc::strcspn( + b"hello\0".as_ptr() as *const i8, + b"el\0".as_ptr() as *const i8 + ) as u64) + == (1_u64)) + ); + assert!( + ((libc::strcspn( + b"abc\0".as_ptr() as *const i8, + b"xyz\0".as_ptr() as *const i8 + ) as u64) + == (3_u64)) + ); + assert!( + ((libc::strcspn(b"\0".as_ptr() as *const i8, b"abc\0".as_ptr() as *const i8) as u64) + == (0_u64)) + ); + let mut s: *const u8 = b"hello\0".as_ptr(); + let mut rej: *const u8 = b"el\0".as_ptr(); + assert!(((libc::strcspn(s as *const i8, rej as *const i8) as u64) == (1_u64))); +} +pub unsafe fn test_strspn_12() { + assert!( + ((libc::strspn( + b"hello\0".as_ptr() as *const i8, + b"hel\0".as_ptr() as *const i8 + ) as u64) + == (4_u64)) + ); + assert!( + ((libc::strspn( + b"abc\0".as_ptr() as *const i8, + b"xyz\0".as_ptr() as *const i8 + ) as u64) + == (0_u64)) + ); + assert!( + ((libc::strspn(b"aaa\0".as_ptr() as *const i8, b"a\0".as_ptr() as *const i8) as u64) + == (3_u64)) + ); + let mut s: *const u8 = b"hello\0".as_ptr(); + let mut acc: *const u8 = b"hel\0".as_ptr(); + assert!(((libc::strspn(s as *const i8, acc as *const i8) as u64) == (4_u64))); +} +pub unsafe fn test_strstr_13() { + let mut h: *const u8 = b"hello world\0".as_ptr(); + let mut r: *const u8 = + libc::strstr(h as *const i8, b"world\0".as_ptr() as *const i8) as *const u8; + assert!(!((r).is_null())); + assert!(((r) == (h.offset((6) as isize)))); + assert!((libc::strstr(h as *const i8, b"xyz\0".as_ptr() as *const i8) as *const u8).is_null()); + let mut buf: [u8; 6] = [ + ('h' as u8), + ('e' as u8), + ('l' as u8), + ('l' as u8), + ('o' as u8), + ('\0' as u8), + ]; + assert!( + ((libc::strstr(buf.as_mut_ptr() as *const i8, b"ll\0".as_ptr() as *const i8) as *mut u8) + == (&mut buf[(2) as usize] as *mut u8)) + ); +} +pub unsafe fn test_strpbrk_14() { + let mut s: *const u8 = b"hello world\0".as_ptr(); + let mut r: *const u8 = + libc::strpbrk(s as *const i8, b"wo\0".as_ptr() as *const i8) as *const u8; + assert!(!((r).is_null())); + assert!(((r) == (s.offset((4) as isize)))); + assert!((libc::strpbrk(s as *const i8, b"xyz\0".as_ptr() as *const i8) as *const u8).is_null()); + let mut buf: [u8; 4] = [('a' as u8), ('b' as u8), ('c' as u8), ('\0' as u8)]; + assert!( + ((libc::strpbrk(buf.as_mut_ptr() as *const i8, b"b\0".as_ptr() as *const i8) as *mut u8) + == (&mut buf[(1) as usize] as *mut u8)) + ); +} +pub unsafe fn test_memrchr_15() { + let data: [u8; 5] = [1_u8, 2_u8, 3_u8, 2_u8, 4_u8]; + let mut r: *const ::libc::c_void = libc::memrchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 2, + 5_u64 as usize, + ) as *const ::libc::c_void; + assert!(((r) == ((&data[(3) as usize] as *const u8) as *const u8 as *const ::libc::c_void))); + assert!((libc::memrchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 99, + 5_u64 as usize + ) as *const ::libc::c_void) + .is_null()); + let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); + let mut n: u64 = 5_u64; + assert!( + ((libc::memrchr(p as *const ::libc::c_void, 1, n as usize) as *const ::libc::c_void) + == (p)) + ); +} +pub unsafe fn test_strcasecmp_16() { + assert!( + ((libc::strcasecmp( + b"HELLO\0".as_ptr() as *const i8, + b"hello\0".as_ptr() as *const i8 + )) == (0)) + ); + assert!( + ((libc::strcasecmp( + b"abc\0".as_ptr() as *const i8, + b"abd\0".as_ptr() as *const i8 + )) < (0)) + ); + assert!( + ((libc::strcasecmp( + b"abd\0".as_ptr() as *const i8, + b"abc\0".as_ptr() as *const i8 + )) > (0)) + ); + let mut p: *const u8 = b"FOO\0".as_ptr(); + let mut q: *const u8 = b"foo\0".as_ptr(); + assert!(((libc::strcasecmp(p as *const i8, q as *const i8)) == (0))); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -309,5 +432,11 @@ unsafe fn main_0() -> i32 { (unsafe { test_memchr_8() }); (unsafe { test_strrchr_9() }); (unsafe { test_strdup_10() }); + (unsafe { test_strcspn_11() }); + (unsafe { test_strspn_12() }); + (unsafe { test_strstr_13() }); + (unsafe { test_strpbrk_14() }); + (unsafe { test_memrchr_15() }); + (unsafe { test_strcasecmp_16() }); return 0; } diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs index 7f8bace9..ea13f4f3 100644 --- a/tests/unit/out/unsafe/string_h.rs +++ b/tests/unit/out/unsafe/string_h.rs @@ -373,6 +373,182 @@ pub unsafe fn test_strdup_10() { ); libc::free((d3 as *mut u8 as *mut ::libc::c_void)); } +pub unsafe fn test_strcspn_11() { + assert!( + ((((libc::strcspn( + (b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"el\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (1_u64)) as i32) + != 0) + ); + assert!( + ((((libc::strcspn( + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"xyz\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (3_u64)) as i32) + != 0) + ); + assert!( + ((((libc::strcspn( + (b"\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (0_u64)) as i32) + != 0) + ); + let mut s: *const u8 = (b"hello\0".as_ptr().cast_mut()).cast_const(); + let mut rej: *const u8 = (b"el\0".as_ptr().cast_mut()).cast_const(); + assert!(((((libc::strcspn(s as *const i8, rej as *const i8) as u64) == (1_u64)) as i32) != 0)); +} +pub unsafe fn test_strspn_12() { + assert!( + ((((libc::strspn( + (b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"hel\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (4_u64)) as i32) + != 0) + ); + assert!( + ((((libc::strspn( + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"xyz\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (0_u64)) as i32) + != 0) + ); + assert!( + ((((libc::strspn( + (b"aaa\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"a\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as u64) + == (3_u64)) as i32) + != 0) + ); + let mut s: *const u8 = (b"hello\0".as_ptr().cast_mut()).cast_const(); + let mut acc: *const u8 = (b"hel\0".as_ptr().cast_mut()).cast_const(); + assert!(((((libc::strspn(s as *const i8, acc as *const i8) as u64) == (4_u64)) as i32) != 0)); +} +pub unsafe fn test_strstr_13() { + let mut h: *const u8 = (b"hello world\0".as_ptr().cast_mut()).cast_const(); + let mut r: *mut u8 = libc::strstr( + (h as *mut u8).cast_const() as *const i8, + (b"world\0".as_ptr().cast_mut()).cast_const() as *const i8, + ) as *mut u8; + assert!((((!((r).is_null())) as i32) != 0)); + assert!(((((r) == (h.offset((6) as isize) as *mut u8)) as i32) != 0)); + assert!( + ((((libc::strstr( + (h as *mut u8).cast_const() as *const i8, + (b"xyz\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as *mut u8) + .is_null()) as i32) + != 0) + ); + let mut buf: [u8; 6] = [ + (('h' as i32) as u8), + (('e' as i32) as u8), + (('l' as i32) as u8), + (('l' as i32) as u8), + (('o' as i32) as u8), + (('\0' as i32) as u8), + ]; + assert!( + ((((libc::strstr( + (buf.as_mut_ptr()).cast_const() as *const i8, + (b"ll\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as *mut u8) + == (&mut buf[(2) as usize] as *mut u8)) as i32) + != 0) + ); +} +pub unsafe fn test_strpbrk_14() { + let mut s: *const u8 = (b"hello world\0".as_ptr().cast_mut()).cast_const(); + let mut r: *mut u8 = libc::strpbrk( + (s as *mut u8).cast_const() as *const i8, + (b"wo\0".as_ptr().cast_mut()).cast_const() as *const i8, + ) as *mut u8; + assert!((((!((r).is_null())) as i32) != 0)); + assert!(((((r) == (s.offset((4) as isize) as *mut u8)) as i32) != 0)); + assert!( + ((((libc::strpbrk( + (s as *mut u8).cast_const() as *const i8, + (b"xyz\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as *mut u8) + .is_null()) as i32) + != 0) + ); + let mut buf: [u8; 4] = [ + (('a' as i32) as u8), + (('b' as i32) as u8), + (('c' as i32) as u8), + (('\0' as i32) as u8), + ]; + assert!( + ((((libc::strpbrk( + (buf.as_mut_ptr()).cast_const() as *const i8, + (b"b\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) as *mut u8) + == (&mut buf[(1) as usize] as *mut u8)) as i32) + != 0) + ); +} +pub unsafe fn test_memrchr_15() { + let data: [u8; 5] = [1_u8, 2_u8, 3_u8, 2_u8, 4_u8]; + let mut r: *mut ::libc::c_void = libc::memrchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 2, + 5_u64 as usize, + ); + assert!( + ((((r) == ((&data[(3) as usize] as *const u8) as *mut u8 as *mut ::libc::c_void)) as i32) + != 0) + ); + assert!( + ((((libc::memrchr( + (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, + 99, + 5_u64 as usize + )) + .is_null()) as i32) + != 0) + ); + let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); + let mut n: u64 = 5_u64; + assert!( + ((((libc::memrchr(p as *const ::libc::c_void, 1, n as usize)) + == (p as *mut ::libc::c_void as *mut ::libc::c_void)) as i32) + != 0) + ); +} +pub unsafe fn test_strcasecmp_16() { + assert!( + ((((libc::strcasecmp( + (b"HELLO\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) == (0)) as i32) + != 0) + ); + assert!( + ((((libc::strcasecmp( + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abd\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) < (0)) as i32) + != 0) + ); + assert!( + ((((libc::strcasecmp( + (b"abd\0".as_ptr().cast_mut()).cast_const() as *const i8, + (b"abc\0".as_ptr().cast_mut()).cast_const() as *const i8 + )) > (0)) as i32) + != 0) + ); + let mut p: *const u8 = (b"FOO\0".as_ptr().cast_mut()).cast_const(); + let mut q: *const u8 = (b"foo\0".as_ptr().cast_mut()).cast_const(); + assert!(((((libc::strcasecmp(p as *const i8, q as *const i8)) == (0)) as i32) != 0)); +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -390,5 +566,11 @@ unsafe fn main_0() -> i32 { (unsafe { test_memchr_8() }); (unsafe { test_strrchr_9() }); (unsafe { test_strdup_10() }); + (unsafe { test_strcspn_11() }); + (unsafe { test_strspn_12() }); + (unsafe { test_strstr_13() }); + (unsafe { test_strpbrk_14() }); + (unsafe { test_memrchr_15() }); + (unsafe { test_strcasecmp_16() }); return 0; } diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c index 0da9b90f..61a7e2cf 100644 --- a/tests/unit/string_h.c +++ b/tests/unit/string_h.c @@ -1,7 +1,9 @@ // no-compile: refcount +#define _GNU_SOURCE #include #include #include +#include static void test_memcpy(void) { const char src[] = "hello"; @@ -113,6 +115,63 @@ static void test_strdup(void) { free(d3); } +static void test_strcspn(void) { + assert(strcspn("hello", "el") == 1); + assert(strcspn("abc", "xyz") == 3); + assert(strcspn("", "abc") == 0); + const char *s = "hello"; + const char *rej = "el"; + assert(strcspn(s, rej) == 1); +} + +static void test_strspn(void) { + assert(strspn("hello", "hel") == 4); + assert(strspn("abc", "xyz") == 0); + assert(strspn("aaa", "a") == 3); + const char *s = "hello"; + const char *acc = "hel"; + assert(strspn(s, acc) == 4); +} + +static void test_strstr(void) { + const char *h = "hello world"; + char *r = strstr((char *)h, "world"); + assert(r != NULL); + assert(r == h + 6); + assert(strstr((char *)h, "xyz") == NULL); + char buf[] = {'h', 'e', 'l', 'l', 'o', '\0'}; + assert(strstr(buf, "ll") == &buf[2]); +} + +static void test_strpbrk(void) { + const char *s = "hello world"; + char *r = strpbrk((char *)s, "wo"); + assert(r != NULL); + assert(r == s + 4); + assert(strpbrk((char *)s, "xyz") == NULL); + char buf[] = {'a', 'b', 'c', '\0'}; + assert(strpbrk(buf, "b") == &buf[1]); +} + +static void test_memrchr(void) { + const char data[] = {1, 2, 3, 2, 4}; + void *r = memrchr(data, 2, 5); + assert(r == &data[3]); + assert(memrchr(data, 99, 5) == NULL); + const void *p = data; + size_t n = 5; + assert(memrchr(p, 1, n) == p); +} + +static void test_strcasecmp(void) { + assert(strcasecmp("HELLO", "hello") == 0); + assert(strcasecmp("abc", "abd") < 0); + assert(strcasecmp("abd", "abc") > 0); + const char *p = "FOO"; + const char *q = "foo"; + assert(strcasecmp(p, q) == 0); +} + int main(void) { test_memcpy(); test_memset(); @@ -125,5 +184,11 @@ int main(void) { test_memchr(); test_strrchr(); test_strdup(); + test_strcspn(); + test_strspn(); + test_strstr(); + test_strpbrk(); + test_memrchr(); + test_strcasecmp(); return 0; } From 71d302cb83581381ec489b76003aabe960d148ec Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 14:40:19 +0100 Subject: [PATCH 09/19] Update tests --- tests/unit/out/unsafe/user_defined_same_as_libc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/out/unsafe/user_defined_same_as_libc.rs b/tests/unit/out/unsafe/user_defined_same_as_libc.rs index 0fbe1a05..2e1a7366 100644 --- a/tests/unit/out/unsafe/user_defined_same_as_libc.rs +++ b/tests/unit/out/unsafe/user_defined_same_as_libc.rs @@ -18,8 +18,8 @@ pub fn main() { } unsafe fn main_0() -> i32 { let mut fp: *mut ::std::fs::File = (unsafe { - let _path: *const u8 = b"/etc/passwd\0".as_ptr().cast_mut().cast_const(); - let _mode: *const u8 = b"r\0".as_ptr().cast_mut().cast_const(); + let _path: *const u8 = (b"/etc/passwd\0".as_ptr().cast_mut()).cast_const(); + let _mode: *const u8 = (b"r\0".as_ptr().cast_mut()).cast_const(); fopen_0(_path, _mode) }); assert!(((((fp).is_null()) as i32) != 0)); From 37693c6911d6af069cda10968e90b6aeec25ed9c Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 14:59:51 +0100 Subject: [PATCH 10/19] Gate memrchr on Linux --- rules/cstring/ir_unsafe.json | 9 ++++++--- rules/cstring/src.c | 2 ++ rules/cstring/src.cpp | 2 ++ rules/cstring/tgt_unsafe.rs | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rules/cstring/ir_unsafe.json b/rules/cstring/ir_unsafe.json index 28190efd..62e68909 100644 --- a/rules/cstring/ir_unsafe.json +++ b/rules/cstring/ir_unsafe.json @@ -739,7 +739,8 @@ "return_type": { "type": "*mut ::libc::c_void", "is_unsafe_pointer": true - } + }, + "target_os": "linux" }, "f25": { "body": [ @@ -789,7 +790,8 @@ "return_type": { "type": "*const ::libc::c_void", "is_unsafe_pointer": true - } + }, + "target_os": "linux" }, "f26": { "body": [ @@ -839,7 +841,8 @@ "return_type": { "type": "*mut ::libc::c_void", "is_unsafe_pointer": true - } + }, + "target_os": "linux" }, "f27": { "body": [ diff --git a/rules/cstring/src.c b/rules/cstring/src.c index 2f751e50..be329a8e 100644 --- a/rules/cstring/src.c +++ b/rules/cstring/src.c @@ -35,6 +35,8 @@ char *f18(const char *a0, const char *a1) { return strstr(a0, a1); } char *f21(const char *a0, const char *a1) { return strpbrk(a0, a1); } +#if defined(__linux__) void *f24(const void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } +#endif int f27(const char *a0, const char *a1) { return strcasecmp(a0, a1); } diff --git a/rules/cstring/src.cpp b/rules/cstring/src.cpp index bac269b3..b195ed8b 100644 --- a/rules/cstring/src.cpp +++ b/rules/cstring/src.cpp @@ -20,6 +20,8 @@ const char *f22(const char *a0, const char *a1) { return strpbrk(a0, a1); } char *f23(char *a0, const char *a1) { return strpbrk(a0, a1); } +#if defined(__linux__) const void *f25(const void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } void *f26(void *a0, int a1, size_t a2) { return memrchr(a0, a1, a2); } +#endif diff --git a/rules/cstring/tgt_unsafe.rs b/rules/cstring/tgt_unsafe.rs index 9c6f6e95..192f0de3 100644 --- a/rules/cstring/tgt_unsafe.rs +++ b/rules/cstring/tgt_unsafe.rs @@ -112,14 +112,17 @@ unsafe fn f23(a0: *mut u8, a1: *const u8) -> *mut u8 { libc::strpbrk(a0 as *const i8, a1 as *const i8) as *mut u8 } +#[cfg(target_os = "linux")] unsafe fn f24(a0: *const u8, a1: i32, a2: usize) -> *mut ::libc::c_void { libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) } +#[cfg(target_os = "linux")] unsafe fn f25(a0: *const u8, a1: i32, a2: usize) -> *const ::libc::c_void { libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) as *const ::libc::c_void } +#[cfg(target_os = "linux")] unsafe fn f26(a0: *mut u8, a1: i32, a2: usize) -> *mut ::libc::c_void { libc::memrchr(a0 as *const ::libc::c_void, a1, a2 as usize) } From d650ea318dfd61324946d68822df4b2f2684c751 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 15:12:38 +0100 Subject: [PATCH 11/19] Delete linux specific memrchr from tests --- tests/unit/cstring.cpp | 11 ----------- tests/unit/out/unsafe/cstring.rs | 26 ++---------------------- tests/unit/out/unsafe/string_h.rs | 33 ++----------------------------- tests/unit/string_h.c | 11 ----------- 4 files changed, 4 insertions(+), 77 deletions(-) diff --git a/tests/unit/cstring.cpp b/tests/unit/cstring.cpp index 7a361393..e72c26df 100644 --- a/tests/unit/cstring.cpp +++ b/tests/unit/cstring.cpp @@ -153,16 +153,6 @@ static void test_strpbrk() { assert(std::strpbrk(buf, "b") == &buf[1]); } -static void test_memrchr() { - const char data[] = {1, 2, 3, 2, 4}; - const void *r = memrchr(data, 2, 5); - assert(r == &data[3]); - assert(memrchr(data, 99, 5) == nullptr); - const void *p = data; - std::size_t n = 5; - assert(memrchr(p, 1, n) == p); -} - static void test_strcasecmp() { assert(strcasecmp("HELLO", "hello") == 0); assert(strcasecmp("abc", "abd") < 0); @@ -188,7 +178,6 @@ int main() { test_strspn(); test_strstr(); test_strpbrk(); - test_memrchr(); test_strcasecmp(); return 0; } diff --git a/tests/unit/out/unsafe/cstring.rs b/tests/unit/out/unsafe/cstring.rs index d9feb41c..32afbc91 100644 --- a/tests/unit/out/unsafe/cstring.rs +++ b/tests/unit/out/unsafe/cstring.rs @@ -371,28 +371,7 @@ pub unsafe fn test_strpbrk_14() { == (&mut buf[(1) as usize] as *mut u8)) ); } -pub unsafe fn test_memrchr_15() { - let data: [u8; 5] = [1_u8, 2_u8, 3_u8, 2_u8, 4_u8]; - let mut r: *const ::libc::c_void = libc::memrchr( - (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, - 2, - 5_u64 as usize, - ) as *const ::libc::c_void; - assert!(((r) == ((&data[(3) as usize] as *const u8) as *const u8 as *const ::libc::c_void))); - assert!((libc::memrchr( - (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, - 99, - 5_u64 as usize - ) as *const ::libc::c_void) - .is_null()); - let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); - let mut n: u64 = 5_u64; - assert!( - ((libc::memrchr(p as *const ::libc::c_void, 1, n as usize) as *const ::libc::c_void) - == (p)) - ); -} -pub unsafe fn test_strcasecmp_16() { +pub unsafe fn test_strcasecmp_15() { assert!( ((libc::strcasecmp( b"HELLO\0".as_ptr() as *const i8, @@ -436,7 +415,6 @@ unsafe fn main_0() -> i32 { (unsafe { test_strspn_12() }); (unsafe { test_strstr_13() }); (unsafe { test_strpbrk_14() }); - (unsafe { test_memrchr_15() }); - (unsafe { test_strcasecmp_16() }); + (unsafe { test_strcasecmp_15() }); return 0; } diff --git a/tests/unit/out/unsafe/string_h.rs b/tests/unit/out/unsafe/string_h.rs index ea13f4f3..1efd2498 100644 --- a/tests/unit/out/unsafe/string_h.rs +++ b/tests/unit/out/unsafe/string_h.rs @@ -495,35 +495,7 @@ pub unsafe fn test_strpbrk_14() { != 0) ); } -pub unsafe fn test_memrchr_15() { - let data: [u8; 5] = [1_u8, 2_u8, 3_u8, 2_u8, 4_u8]; - let mut r: *mut ::libc::c_void = libc::memrchr( - (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, - 2, - 5_u64 as usize, - ); - assert!( - ((((r) == ((&data[(3) as usize] as *const u8) as *mut u8 as *mut ::libc::c_void)) as i32) - != 0) - ); - assert!( - ((((libc::memrchr( - (data.as_ptr() as *const u8 as *const ::libc::c_void) as *const ::libc::c_void, - 99, - 5_u64 as usize - )) - .is_null()) as i32) - != 0) - ); - let mut p: *const ::libc::c_void = (data.as_ptr() as *const u8 as *const ::libc::c_void); - let mut n: u64 = 5_u64; - assert!( - ((((libc::memrchr(p as *const ::libc::c_void, 1, n as usize)) - == (p as *mut ::libc::c_void as *mut ::libc::c_void)) as i32) - != 0) - ); -} -pub unsafe fn test_strcasecmp_16() { +pub unsafe fn test_strcasecmp_15() { assert!( ((((libc::strcasecmp( (b"HELLO\0".as_ptr().cast_mut()).cast_const() as *const i8, @@ -570,7 +542,6 @@ unsafe fn main_0() -> i32 { (unsafe { test_strspn_12() }); (unsafe { test_strstr_13() }); (unsafe { test_strpbrk_14() }); - (unsafe { test_memrchr_15() }); - (unsafe { test_strcasecmp_16() }); + (unsafe { test_strcasecmp_15() }); return 0; } diff --git a/tests/unit/string_h.c b/tests/unit/string_h.c index 61a7e2cf..9202874d 100644 --- a/tests/unit/string_h.c +++ b/tests/unit/string_h.c @@ -153,16 +153,6 @@ static void test_strpbrk(void) { assert(strpbrk(buf, "b") == &buf[1]); } -static void test_memrchr(void) { - const char data[] = {1, 2, 3, 2, 4}; - void *r = memrchr(data, 2, 5); - assert(r == &data[3]); - assert(memrchr(data, 99, 5) == NULL); - const void *p = data; - size_t n = 5; - assert(memrchr(p, 1, n) == p); -} - static void test_strcasecmp(void) { assert(strcasecmp("HELLO", "hello") == 0); assert(strcasecmp("abc", "abd") < 0); @@ -188,7 +178,6 @@ int main(void) { test_strspn(); test_strstr(); test_strpbrk(); - test_memrchr(); test_strcasecmp(); return 0; } From 59bcd256dcaf5f77bff2bdabe919a961138b409d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 15:14:26 +0100 Subject: [PATCH 12/19] Delete double define _GNU_SOURCE It's already defined in src.c --- rules/cstring/src.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/rules/cstring/src.cpp b/rules/cstring/src.cpp index b195ed8b..39704958 100644 --- a/rules/cstring/src.cpp +++ b/rules/cstring/src.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2022-present INESC-ID. // Distributed under the MIT license that can be found in the LICENSE file. -#define _GNU_SOURCE #include const char *f6(const char *a0, int a1) { return strchr(a0, a1); } From 22d0eb8cb230fe36da270df54d79bc4c45a311b1 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 12:25:55 +0100 Subject: [PATCH 13/19] Add rules + tests for fputc/fputs --- rules/stdio/ir_unsafe.json | 136 +++++++++++++++++++++++++++++++++ rules/stdio/src.cpp | 4 + rules/stdio/tgt_unsafe.rs | 15 ++++ tests/unit/out/unsafe/stdio.rs | 77 +++++++++++++++++++ tests/unit/stdio.c | 25 ++++++ 5 files changed, 257 insertions(+) create mode 100644 tests/unit/out/unsafe/stdio.rs create mode 100644 tests/unit/stdio.c diff --git a/rules/stdio/ir_unsafe.json b/rules/stdio/ir_unsafe.json index ea38ee60..73b764fb 100644 --- a/rules/stdio/ir_unsafe.json +++ b/rules/stdio/ir_unsafe.json @@ -264,6 +264,142 @@ "is_unsafe_pointer": true } }, + "f11": { + "body": [ + { + "text": "match " + }, + { + "method_call": { + "receiver": [ + { + "text": "(*" + }, + { + "placeholder": { + "arg": 1, + "access": "write" + } + }, + { + "text": ")" + } + ], + "body": [ + { + "text": ".write_all(&[" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as u8])" + } + ] + } + }, + { + "text": " {\n Ok(()) => " + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " & 0xff,\n Err(_) => -1,\n }" + } + ], + "params": { + "a0": { + "type": "i32" + }, + "a1": { + "type": "*mut ::std::fs::File", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, + "f12": { + "body": [ + { + "text": "let bytes = " + }, + { + "method_call": { + "receiver": [ + { + "text": "std::ffi::CStr::from_ptr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "body": [ + { + "text": ".to_bytes()" + } + ] + } + }, + { + "text": ";\n match " + }, + { + "method_call": { + "receiver": [ + { + "text": "(*" + }, + { + "placeholder": { + "arg": 1, + "access": "write" + } + }, + { + "text": ")" + } + ], + "body": [ + { + "text": ".write_all(bytes)" + } + ] + } + }, + { + "text": " {\n Ok(()) => 0,\n Err(_) => -1,\n }" + } + ], + "multi_statement": true, + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "*mut ::std::fs::File", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, "f2": { "body": [ { diff --git a/rules/stdio/src.cpp b/rules/stdio/src.cpp index 77450328..e79fba0c 100644 --- a/rules/stdio/src.cpp +++ b/rules/stdio/src.cpp @@ -32,3 +32,7 @@ FILE *f8() { return stdout; } FILE *f9() { return stderr; } FILE *f10() { return stdin; } + +int f11(int c, FILE *stream) { return fputc(c, stream); } + +int f12(const char *s, FILE *stream) { return fputs(s, stream); } diff --git a/rules/stdio/tgt_unsafe.rs b/rules/stdio/tgt_unsafe.rs index 0b76382b..8ef22331 100644 --- a/rules/stdio/tgt_unsafe.rs +++ b/rules/stdio/tgt_unsafe.rs @@ -105,3 +105,18 @@ unsafe fn f9() -> *mut ::std::fs::File { unsafe fn f10() -> *mut ::std::fs::File { libcc2rs::cin_unsafe() } + +unsafe fn f11(a0: i32, a1: *mut ::std::fs::File) -> i32 { + match (*a1).write_all(&[a0 as u8]) { + Ok(()) => a0 & 0xff, + Err(_) => -1, + } +} + +unsafe fn f12(a0: *const u8, a1: *mut ::std::fs::File) -> i32 { + let bytes = std::ffi::CStr::from_ptr(a0 as *const i8).to_bytes(); + match (*a1).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } +} diff --git a/tests/unit/out/unsafe/stdio.rs b/tests/unit/out/unsafe/stdio.rs new file mode 100644 index 00000000..05ac3fc3 --- /dev/null +++ b/tests/unit/out/unsafe/stdio.rs @@ -0,0 +1,77 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn test_fputc_0() { + match (*libcc2rs::cout_unsafe()).write_all(&[('H' as i32) as u8]) { + Ok(()) => ('H' as i32) & 0xff, + Err(_) => -1, + }; + match (*libcc2rs::cout_unsafe()).write_all(&[('i' as i32) as u8]) { + Ok(()) => ('i' as i32) & 0xff, + Err(_) => -1, + }; + match (*libcc2rs::cout_unsafe()).write_all(&[('\n' as i32) as u8]) { + Ok(()) => ('\n' as i32) & 0xff, + Err(_) => -1, + }; +} +pub unsafe fn test_fputs_1() { + { + let bytes = + std::ffi::CStr::from_ptr((b"hello\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_bytes(); + match (*libcc2rs::cout_unsafe()).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + match (*libcc2rs::cout_unsafe()).write_all(&[('\n' as i32) as u8]) { + Ok(()) => ('\n' as i32) & 0xff, + Err(_) => -1, + }; + let mut s: *const u8 = (b"from variable\0".as_ptr().cast_mut()).cast_const(); + { + let bytes = std::ffi::CStr::from_ptr(s as *const i8).to_bytes(); + match (*libcc2rs::cout_unsafe()).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + match (*libcc2rs::cout_unsafe()).write_all(&[('\n' as i32) as u8]) { + Ok(()) => ('\n' as i32) & 0xff, + Err(_) => -1, + }; + let mut buf: [u8; 4] = [ + (('b' as i32) as u8), + (('u' as i32) as u8), + (('f' as i32) as u8), + (('\0' as i32) as u8), + ]; + { + let bytes = + std::ffi::CStr::from_ptr((buf.as_mut_ptr()).cast_const() as *const i8).to_bytes(); + match (*libcc2rs::cout_unsafe()).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + match (*libcc2rs::cout_unsafe()).write_all(&[('\n' as i32) as u8]) { + Ok(()) => ('\n' as i32) & 0xff, + Err(_) => -1, + }; +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + (unsafe { test_fputc_0() }); + (unsafe { test_fputs_1() }); + return 0; +} diff --git a/tests/unit/stdio.c b/tests/unit/stdio.c new file mode 100644 index 00000000..8f59ef53 --- /dev/null +++ b/tests/unit/stdio.c @@ -0,0 +1,25 @@ +// no-compile: refcount +#include + +static void test_fputc(void) { + fputc('H', stdout); + fputc('i', stdout); + fputc('\n', stdout); +} + +static void test_fputs(void) { + fputs("hello", stdout); + fputc('\n', stdout); + const char *s = "from variable"; + fputs(s, stdout); + fputc('\n', stdout); + char buf[] = {'b', 'u', 'f', '\0'}; + fputs(buf, stdout); + fputc('\n', stdout); +} + +int main(void) { + test_fputc(); + test_fputs(); + return 0; +} From 73f6d2b9117614462299178610f91d1a178c3c04 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 12:38:09 +0100 Subject: [PATCH 14/19] Add rules + tests for puts/fileno --- rules/stdio/ir_unsafe.json | 163 +++++++++++++++++++++++++++++++++ rules/stdio/src.cpp | 4 + rules/stdio/tgt_unsafe.rs | 20 ++++ tests/unit/out/unsafe/stdio.rs | 118 ++++++++++++++++++++++++ tests/unit/stdio.c | 19 ++++ 5 files changed, 324 insertions(+) diff --git a/rules/stdio/ir_unsafe.json b/rules/stdio/ir_unsafe.json index 73b764fb..43661b84 100644 --- a/rules/stdio/ir_unsafe.json +++ b/rules/stdio/ir_unsafe.json @@ -400,6 +400,169 @@ "type": "i32" } }, + "f13": { + "body": [ + { + "text": "let bytes = " + }, + { + "method_call": { + "receiver": [ + { + "text": "std::ffi::CStr::from_ptr(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "body": [ + { + "text": ".to_bytes()" + } + ] + } + }, + { + "text": ";\n let stdout = libcc2rs::cout_unsafe();\n let r1 = " + }, + { + "method_call": { + "receiver": [ + { + "text": "(*stdout)" + } + ], + "body": [ + { + "text": ".write_all(bytes)" + } + ] + } + }, + { + "text": ";\n let r2 = " + }, + { + "method_call": { + "receiver": [ + { + "text": "(*stdout)" + } + ], + "body": [ + { + "text": ".write_all(b\"\\n\")" + } + ] + } + }, + { + "text": ";\n if " + }, + { + "method_call": { + "receiver": [ + { + "text": "r1" + } + ], + "body": [ + { + "text": ".is_ok()" + } + ] + } + }, + { + "text": " && " + }, + { + "method_call": { + "receiver": [ + { + "text": "r2" + } + ], + "body": [ + { + "text": ".is_ok()" + } + ] + } + }, + { + "text": " { 0 } else { -1 }" + } + ], + "multi_statement": true, + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, + "f14": { + "body": [ + { + "text": "if " + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " == libcc2rs::cin_unsafe() {\n 0\n } else if " + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " == libcc2rs::cout_unsafe() {\n 1\n } else if " + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " == libcc2rs::cerr_unsafe() {\n 2\n } else {\n ::std::os::fd::AsRawFd::as_raw_fd(&*" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ")\n }" + } + ], + "params": { + "a0": { + "type": "*mut ::std::fs::File", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, "f2": { "body": [ { diff --git a/rules/stdio/src.cpp b/rules/stdio/src.cpp index e79fba0c..c1ecc453 100644 --- a/rules/stdio/src.cpp +++ b/rules/stdio/src.cpp @@ -36,3 +36,7 @@ FILE *f10() { return stdin; } int f11(int c, FILE *stream) { return fputc(c, stream); } int f12(const char *s, FILE *stream) { return fputs(s, stream); } + +int f13(const char *s) { return puts(s); } + +int f14(FILE *stream) { return fileno(stream); } diff --git a/rules/stdio/tgt_unsafe.rs b/rules/stdio/tgt_unsafe.rs index 8ef22331..5b717f57 100644 --- a/rules/stdio/tgt_unsafe.rs +++ b/rules/stdio/tgt_unsafe.rs @@ -120,3 +120,23 @@ unsafe fn f12(a0: *const u8, a1: *mut ::std::fs::File) -> i32 { Err(_) => -1, } } + +unsafe fn f13(a0: *const u8) -> i32 { + let bytes = std::ffi::CStr::from_ptr(a0 as *const i8).to_bytes(); + let stdout = libcc2rs::cout_unsafe(); + let r1 = (*stdout).write_all(bytes); + let r2 = (*stdout).write_all(b"\n"); + if r1.is_ok() && r2.is_ok() { 0 } else { -1 } +} + +unsafe fn f14(a0: *mut ::std::fs::File) -> i32 { + if a0 == libcc2rs::cin_unsafe() { + 0 + } else if a0 == libcc2rs::cout_unsafe() { + 1 + } else if a0 == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*a0) + } +} diff --git a/tests/unit/out/unsafe/stdio.rs b/tests/unit/out/unsafe/stdio.rs index 05ac3fc3..3f3085ea 100644 --- a/tests/unit/out/unsafe/stdio.rs +++ b/tests/unit/out/unsafe/stdio.rs @@ -65,6 +65,122 @@ pub unsafe fn test_fputs_1() { Err(_) => -1, }; } +pub unsafe fn test_puts_2() { + { + let bytes = std::ffi::CStr::from_ptr( + (b"puts hello\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) + .to_bytes(); + let stdout = libcc2rs::cout_unsafe(); + let r1 = (*stdout).write_all(bytes); + let r2 = (*stdout).write_all(b"\n"); + if r1.is_ok() && r2.is_ok() { + 0 + } else { + -1 + } + }; + let mut s: *const u8 = (b"puts variable\0".as_ptr().cast_mut()).cast_const(); + { + let bytes = std::ffi::CStr::from_ptr(s as *const i8).to_bytes(); + let stdout = libcc2rs::cout_unsafe(); + let r1 = (*stdout).write_all(bytes); + let r2 = (*stdout).write_all(b"\n"); + if r1.is_ok() && r2.is_ok() { + 0 + } else { + -1 + } + }; +} +pub unsafe fn test_fileno_3() { + assert!( + ((((if libcc2rs::cin_unsafe() == libcc2rs::cin_unsafe() { + 0 + } else if libcc2rs::cin_unsafe() == libcc2rs::cout_unsafe() { + 1 + } else if libcc2rs::cin_unsafe() == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*libcc2rs::cin_unsafe()) + }) == (0)) as i32) + != 0) + ); + assert!( + ((((if libcc2rs::cout_unsafe() == libcc2rs::cin_unsafe() { + 0 + } else if libcc2rs::cout_unsafe() == libcc2rs::cout_unsafe() { + 1 + } else if libcc2rs::cout_unsafe() == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*libcc2rs::cout_unsafe()) + }) == (1)) as i32) + != 0) + ); + assert!( + ((((if libcc2rs::cerr_unsafe() == libcc2rs::cin_unsafe() { + 0 + } else if libcc2rs::cerr_unsafe() == libcc2rs::cout_unsafe() { + 1 + } else if libcc2rs::cerr_unsafe() == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*libcc2rs::cerr_unsafe()) + }) == (2)) as i32) + != 0) + ); + let mut fp: *mut ::std::fs::File = + match std::ffi::CStr::from_ptr((b"wb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr( + (b"/tmp/cpp2rust_fileno_test.tmp\0".as_ptr().cast_mut()).cast_const() + as *const i8, + ) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr( + (b"/tmp/cpp2rust_fileno_test.tmp\0".as_ptr().cast_mut()).cast_const() + as *const i8, + ) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + assert!( + ((((if fp == libcc2rs::cin_unsafe() { + 0 + } else if fp == libcc2rs::cout_unsafe() { + 1 + } else if fp == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*fp) + }) > (2)) as i32) + != 0) + ); + { + Box::from_raw(fp); + 0 + }; +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -73,5 +189,7 @@ pub fn main() { unsafe fn main_0() -> i32 { (unsafe { test_fputc_0() }); (unsafe { test_fputs_1() }); + (unsafe { test_puts_2() }); + (unsafe { test_fileno_3() }); return 0; } diff --git a/tests/unit/stdio.c b/tests/unit/stdio.c index 8f59ef53..e62dd4cf 100644 --- a/tests/unit/stdio.c +++ b/tests/unit/stdio.c @@ -1,4 +1,5 @@ // no-compile: refcount +#include #include static void test_fputc(void) { @@ -18,8 +19,26 @@ static void test_fputs(void) { fputc('\n', stdout); } +static void test_puts(void) { + puts("puts hello"); + const char *s = "puts variable"; + puts(s); +} + +static void test_fileno(void) { + assert(fileno(stdin) == 0); + assert(fileno(stdout) == 1); + assert(fileno(stderr) == 2); + FILE *fp = fopen("/tmp/cpp2rust_fileno_test.tmp", "wb"); + assert(fp != NULL); + assert(fileno(fp) > 2); + fclose(fp); +} + int main(void) { test_fputc(); test_fputs(); + test_puts(); + test_fileno(); return 0; } From 1bef431bd20c33678cd0baa4c27c3a1a7171db9e Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 13:50:31 +0100 Subject: [PATCH 15/19] Add unistd rules --- rules/unistd/ir_unsafe.json | 329 ++++++++++++++++++++ rules/unistd/src.cpp | 26 ++ rules/unistd/tgt_unsafe.rs | 42 +++ tests/unit/out/unsafe/unistd.rs | 530 ++++++++++++++++++++++++++++++++ tests/unit/unistd.c | 115 +++++++ 5 files changed, 1042 insertions(+) create mode 100644 rules/unistd/ir_unsafe.json create mode 100644 rules/unistd/src.cpp create mode 100644 rules/unistd/tgt_unsafe.rs create mode 100644 tests/unit/out/unsafe/unistd.rs create mode 100644 tests/unit/unistd.c diff --git a/rules/unistd/ir_unsafe.json b/rules/unistd/ir_unsafe.json new file mode 100644 index 00000000..c72bdd60 --- /dev/null +++ b/rules/unistd/ir_unsafe.json @@ -0,0 +1,329 @@ +{ + "f1": { + "body": [ + { + "text": "libc::close(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ")" + } + ], + "params": { + "a0": { + "type": "i32" + } + }, + "return_type": { + "type": "i32" + } + }, + "f10": { + "body": [ + { + "text": "libc::write(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize) as i64" + } + ], + "params": { + "a0": { + "type": "i32" + }, + "a1": { + "type": "*const ::libc::c_void", + "is_unsafe_pointer": true + }, + "a2": { + "type": "u64" + } + }, + "return_type": { + "type": "i64" + } + }, + "f2": { + "body": [ + { + "text": "libc::lseek(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": ")" + } + ], + "params": { + "a0": { + "type": "i32" + }, + "a1": { + "type": "i64" + }, + "a2": { + "type": "i32" + } + }, + "return_type": { + "type": "i64" + } + }, + "f3": { + "body": [ + { + "text": "libc::read(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 2, + "access": "read" + } + }, + { + "text": " as usize) as i64" + } + ], + "params": { + "a0": { + "type": "i32" + }, + "a1": { + "type": "*mut ::libc::c_void", + "is_unsafe_pointer": true + }, + "a2": { + "type": "u64" + } + }, + "return_type": { + "type": "i64" + } + }, + "f4": { + "body": [ + { + "text": "libc::unlink(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, + "f5": { + "body": [ + { + "text": "libc::pipe(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ")" + } + ], + "params": { + "a0": { + "type": "*mut i32", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, + "f6": { + "body": [ + { + "text": "libc::ftruncate(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ", " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": ")" + } + ], + "params": { + "a0": { + "type": "i32" + }, + "a1": { + "type": "i64" + } + }, + "return_type": { + "type": "i32" + } + }, + "f7": { + "body": [ + { + "text": "libc::isatty(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": ")" + } + ], + "params": { + "a0": { + "type": "i32" + } + }, + "return_type": { + "type": "i32" + } + }, + "f8": { + "body": [ + { + "text": "libc::geteuid()" + } + ], + "return_type": { + "type": "u32" + } + }, + "f9": { + "body": [ + { + "text": "libc::gethostname(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *mut i8, " + }, + { + "placeholder": { + "arg": 1, + "access": "read" + } + }, + { + "text": " as usize)" + } + ], + "params": { + "a0": { + "type": "*mut u8", + "is_unsafe_pointer": true + }, + "a1": { + "type": "u64" + } + }, + "return_type": { + "type": "i32" + } + } +} diff --git a/rules/unistd/src.cpp b/rules/unistd/src.cpp new file mode 100644 index 00000000..bfeda143 --- /dev/null +++ b/rules/unistd/src.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +int f1(int fd) { return close(fd); } + +off_t f2(int fd, off_t offset, int whence) { return lseek(fd, offset, whence); } + +ssize_t f3(int fd, void *buf, size_t count) { return read(fd, buf, count); } + +int f4(const char *pathname) { return unlink(pathname); } + +int f5(int pipefd[2]) { return pipe(pipefd); } + +int f6(int fd, off_t length) { return ftruncate(fd, length); } + +int f7(int fd) { return isatty(fd); } + +uid_t f8(void) { return geteuid(); } + +int f9(char *name, size_t len) { return gethostname(name, len); } + +ssize_t f10(int fd, const void *buf, size_t count) { + return write(fd, buf, count); +} diff --git a/rules/unistd/tgt_unsafe.rs b/rules/unistd/tgt_unsafe.rs new file mode 100644 index 00000000..fae5bf2d --- /dev/null +++ b/rules/unistd/tgt_unsafe.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +unsafe fn f1(a0: i32) -> i32 { + libc::close(a0) +} + +unsafe fn f2(a0: i32, a1: i64, a2: i32) -> i64 { + libc::lseek(a0, a1, a2) +} + +unsafe fn f3(a0: i32, a1: *mut ::libc::c_void, a2: u64) -> i64 { + libc::read(a0, a1, a2 as usize) as i64 +} + +unsafe fn f4(a0: *const u8) -> i32 { + libc::unlink(a0 as *const i8) +} + +unsafe fn f5(a0: *mut i32) -> i32 { + libc::pipe(a0) +} + +unsafe fn f6(a0: i32, a1: i64) -> i32 { + libc::ftruncate(a0, a1) +} + +unsafe fn f7(a0: i32) -> i32 { + libc::isatty(a0) +} + +unsafe fn f8() -> u32 { + libc::geteuid() +} + +unsafe fn f9(a0: *mut u8, a1: u64) -> i32 { + libc::gethostname(a0 as *mut i8, a1 as usize) +} + +unsafe fn f10(a0: i32, a1: *const ::libc::c_void, a2: u64) -> i64 { + libc::write(a0, a1, a2 as usize) as i64 +} diff --git a/tests/unit/out/unsafe/unistd.rs b/tests/unit/out/unsafe/unistd.rs new file mode 100644 index 00000000..f461a788 --- /dev/null +++ b/tests/unit/out/unsafe/unistd.rs @@ -0,0 +1,530 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn test_close_0() { + let mut fds: [i32; 2] = [0_i32; 2]; + assert!(((((libc::pipe(fds.as_mut_ptr())) == (0)) as i32) != 0)); + assert!(((((libc::close(fds[(0) as usize])) == (0)) as i32) != 0)); + let mut buf: [u8; 1] = [0_u8; 1]; + assert!( + ((((libc::read( + fds[(0) as usize], + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 1_u64 as usize + ) as i64) + == (-1_i32 as i64)) as i32) + != 0) + ); + assert!(((((libc::close(fds[(1) as usize])) == (0)) as i32) != 0)); +} +pub unsafe fn test_lseek_1() { + let mut path: *const u8 = (b"/tmp/cpp2rust_lseek_test.tmp\0".as_ptr().cast_mut()).cast_const(); + let mut fp: *mut ::std::fs::File = + match std::ffi::CStr::from_ptr((b"wb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + { + let bytes = std::ffi::CStr::from_ptr( + (b"hello world\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) + .to_bytes(); + match (*fp).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + fp = match std::ffi::CStr::from_ptr((b"rb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + let mut fd: i32 = if fp == libcc2rs::cin_unsafe() { + 0 + } else if fp == libcc2rs::cout_unsafe() { + 1 + } else if fp == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*fp) + }; + assert!(((((libc::lseek(fd, 0_i64, 2)) == (11_i64)) as i32) != 0)); + assert!(((((libc::lseek(fd, 6_i64, 0)) == (6_i64)) as i32) != 0)); + let mut buf: [u8; 8] = [0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8]; + assert!( + ((((libc::read( + fd, + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 5_u64 as usize + ) as i64) + == (5_i64)) as i32) + != 0) + ); + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (buf.as_mut_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 5_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (b"world\0".as_ptr().cast_mut() as *const u8 as *const ::libc::c_void) as *const u8, + 5_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) as i32) + != 0) + ); + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + libc::unlink(path as *const i8); +} +pub unsafe fn test_read_2() { + let mut path: *const u8 = (b"/tmp/cpp2rust_read_test.tmp\0".as_ptr().cast_mut()).cast_const(); + let mut fp: *mut ::std::fs::File = + match std::ffi::CStr::from_ptr((b"wb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + { + let bytes = std::ffi::CStr::from_ptr( + (b"hello world\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) + .to_bytes(); + match (*fp).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + fp = match std::ffi::CStr::from_ptr((b"rb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + let mut fd: i32 = if fp == libcc2rs::cin_unsafe() { + 0 + } else if fp == libcc2rs::cout_unsafe() { + 1 + } else if fp == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*fp) + }; + let mut buf: [u8; 16] = [ + 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, + 0_u8, + ]; + assert!( + ((((libc::read( + fd, + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 16_u64 as usize + ) as i64) + == (11_i64)) as i32) + != 0) + ); + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (buf.as_mut_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 11_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (b"hello world\0".as_ptr().cast_mut() as *const u8 as *const ::libc::c_void) + as *const u8, + 11_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) as i32) + != 0) + ); + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + libc::unlink(path as *const i8); +} +pub unsafe fn test_unlink_3() { + let mut path: *const u8 = (b"/tmp/cpp2rust_unlink_test.tmp\0".as_ptr().cast_mut()).cast_const(); + let mut fp: *mut ::std::fs::File = + match std::ffi::CStr::from_ptr((b"wb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + assert!(((((libc::unlink(path as *const i8)) == (0)) as i32) != 0)); + assert!(((((libc::unlink(path as *const i8)) == (-1_i32)) as i32) != 0)); +} +pub unsafe fn test_pipe_4() { + let mut fds: [i32; 2] = [0_i32; 2]; + assert!(((((libc::pipe(fds.as_mut_ptr())) == (0)) as i32) != 0)); + let mut msg: *const u8 = (b"world\0".as_ptr().cast_mut()).cast_const(); + assert!( + ((((libc::write( + fds[(1) as usize], + (msg as *const u8 as *const ::libc::c_void), + 5_u64 as usize + ) as i64) + == (5_i64)) as i32) + != 0) + ); + let mut buf: [u8; 8] = [0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8]; + assert!( + ((((libc::read( + fds[(0) as usize], + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 8_u64 as usize + ) as i64) + == (5_i64)) as i32) + != 0) + ); + assert!( + (((({ + let sa = core::slice::from_raw_parts( + (buf.as_mut_ptr() as *const u8 as *const ::libc::c_void) as *const u8, + 5_u64 as usize, + ); + let sb = core::slice::from_raw_parts( + (msg as *const u8 as *const ::libc::c_void) as *const u8, + 5_u64 as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) as i32) + != 0) + ); + assert!(((((libc::close(fds[(1) as usize])) == (0)) as i32) != 0)); + assert!( + ((((libc::read( + fds[(0) as usize], + (buf.as_mut_ptr() as *mut u8 as *mut ::libc::c_void), + 8_u64 as usize + ) as i64) + == (0_i64)) as i32) + != 0) + ); + assert!(((((libc::close(fds[(0) as usize])) == (0)) as i32) != 0)); +} +pub unsafe fn test_ftruncate_5() { + let mut path: *const u8 = + (b"/tmp/cpp2rust_ftruncate_test.tmp\0".as_ptr().cast_mut()).cast_const(); + let mut fp: *mut ::std::fs::File = + match std::ffi::CStr::from_ptr((b"wb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + { + let bytes = std::ffi::CStr::from_ptr( + (b"hello world\0".as_ptr().cast_mut()).cast_const() as *const i8 + ) + .to_bytes(); + match (*fp).write_all(bytes) { + Ok(()) => 0, + Err(_) => -1, + } + }; + if !(fp).is_null() { + match (*fp).sync_all() { + Ok(_) => 0, + Err(_) => -1, + } + } else { + ::std::io::stdout().flush().unwrap(); + ::std::io::stderr().flush().unwrap(); + 0 + }; + let mut fd: i32 = if fp == libcc2rs::cin_unsafe() { + 0 + } else if fp == libcc2rs::cout_unsafe() { + 1 + } else if fp == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*fp) + }; + assert!(((((libc::ftruncate(fd, 5_i64)) == (0)) as i32) != 0)); + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + fp = match std::ffi::CStr::from_ptr((b"rb\0".as_ptr().cast_mut()).cast_const() as *const i8) + .to_str() + .expect("invalid c-string") + { + v if v == "rb" => std::fs::OpenOptions::new() + .read(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + v if v == "wb" => std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open( + std::ffi::CStr::from_ptr(path as *const i8) + .to_str() + .expect("invalid c-string"), + ) + .ok() + .map_or(std::ptr::null_mut(), |f| Box::into_raw(Box::new(f))), + _ => panic!("unsupported mode"), + }; + assert!((((!((fp).is_null())) as i32) != 0)); + fd = (if fp == libcc2rs::cin_unsafe() { + 0 + } else if fp == libcc2rs::cout_unsafe() { + 1 + } else if fp == libcc2rs::cerr_unsafe() { + 2 + } else { + ::std::os::fd::AsRawFd::as_raw_fd(&*fp) + }) + .clone(); + assert!(((((libc::lseek(fd, 0_i64, 2)) == (5_i64)) as i32) != 0)); + assert!( + (((({ + Box::from_raw(fp); + 0 + }) == (0)) as i32) + != 0) + ); + libc::unlink(path as *const i8); +} +pub unsafe fn test_isatty_6() { + printf( + (b"%d\n\0".as_ptr().cast_mut()).cast_const() as *const i8, + libc::isatty(0), + ); +} +pub unsafe fn test_geteuid_7() { + printf( + (b"%u\n\0".as_ptr().cast_mut()).cast_const() as *const i8, + libc::geteuid(), + ); +} +pub unsafe fn test_gethostname_8() { + let mut name: [u8; 256] = [0_u8; 256]; + assert!( + ((((libc::gethostname( + name.as_mut_ptr() as *mut i8, + ::std::mem::size_of::<[u8; 256]>() as u64 as usize + )) == (0)) as i32) + != 0) + ); + printf( + (b"%s\n\0".as_ptr().cast_mut()).cast_const() as *const i8, + name.as_mut_ptr(), + ); +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + (unsafe { test_close_0() }); + (unsafe { test_lseek_1() }); + (unsafe { test_read_2() }); + (unsafe { test_unlink_3() }); + (unsafe { test_pipe_4() }); + (unsafe { test_ftruncate_5() }); + (unsafe { test_isatty_6() }); + (unsafe { test_geteuid_7() }); + (unsafe { test_gethostname_8() }); + return 0; +} diff --git a/tests/unit/unistd.c b/tests/unit/unistd.c new file mode 100644 index 00000000..c5798e5b --- /dev/null +++ b/tests/unit/unistd.c @@ -0,0 +1,115 @@ +// no-compile: refcount +#include +#include +#include +#include +#include + +static void test_close(void) { + int fds[2]; + assert(pipe(fds) == 0); + assert(close(fds[0]) == 0); + char buf[1]; + assert(read(fds[0], buf, 1) == -1); + assert(close(fds[1]) == 0); +} + +static void test_lseek(void) { + const char *path = "/tmp/cpp2rust_lseek_test.tmp"; + FILE *fp = fopen(path, "wb"); + assert(fp != NULL); + fputs("hello world", fp); + assert(fclose(fp) == 0); + fp = fopen(path, "rb"); + assert(fp != NULL); + int fd = fileno(fp); + assert(lseek(fd, 0, SEEK_END) == 11); + assert(lseek(fd, 6, SEEK_SET) == 6); + char buf[8] = {0}; + assert(read(fd, buf, 5) == 5); + assert(memcmp(buf, "world", 5) == 0); + assert(fclose(fp) == 0); + unlink(path); +} + +static void test_read(void) { + const char *path = "/tmp/cpp2rust_read_test.tmp"; + FILE *fp = fopen(path, "wb"); + assert(fp != NULL); + fputs("hello world", fp); + assert(fclose(fp) == 0); + fp = fopen(path, "rb"); + assert(fp != NULL); + int fd = fileno(fp); + char buf[16] = {0}; + assert(read(fd, buf, 16) == 11); + assert(memcmp(buf, "hello world", 11) == 0); + assert(fclose(fp) == 0); + unlink(path); +} + +static void test_unlink(void) { + const char *path = "/tmp/cpp2rust_unlink_test.tmp"; + FILE *fp = fopen(path, "wb"); + assert(fp != NULL); + assert(fclose(fp) == 0); + assert(unlink(path) == 0); + assert(unlink(path) == -1); +} + +static void test_pipe(void) { + int fds[2]; + assert(pipe(fds) == 0); + const char *msg = "world"; + assert(write(fds[1], msg, 5) == 5); + char buf[8] = {0}; + assert(read(fds[0], buf, 8) == 5); + assert(memcmp(buf, msg, 5) == 0); + assert(close(fds[1]) == 0); + assert(read(fds[0], buf, 8) == 0); + assert(close(fds[0]) == 0); +} + +static void test_ftruncate(void) { + const char *path = "/tmp/cpp2rust_ftruncate_test.tmp"; + FILE *fp = fopen(path, "wb"); + assert(fp != NULL); + fputs("hello world", fp); + fflush(fp); + int fd = fileno(fp); + assert(ftruncate(fd, 5) == 0); + assert(fclose(fp) == 0); + fp = fopen(path, "rb"); + assert(fp != NULL); + fd = fileno(fp); + assert(lseek(fd, 0, SEEK_END) == 5); + assert(fclose(fp) == 0); + unlink(path); +} + +static void test_isatty(void) { + printf("%d\n", isatty(0)); +} + +static void test_geteuid(void) { + printf("%u\n", geteuid()); +} + +static void test_gethostname(void) { + char name[256]; + assert(gethostname(name, sizeof(name)) == 0); + printf("%s\n", name); +} + +int main(void) { + test_close(); + test_lseek(); + test_read(); + test_unlink(); + test_pipe(); + test_ftruncate(); + test_isatty(); + test_geteuid(); + test_gethostname(); + return 0; +} From a7f98575518a902d085175fc09788528466ea612 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 14:11:13 +0100 Subject: [PATCH 16/19] Add rmdir rule --- rules/unistd/ir_unsafe.json | 25 +++++++++++++++++++++++++ rules/unistd/src.cpp | 2 ++ rules/unistd/tgt_unsafe.rs | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/rules/unistd/ir_unsafe.json b/rules/unistd/ir_unsafe.json index c72bdd60..6d4aaddd 100644 --- a/rules/unistd/ir_unsafe.json +++ b/rules/unistd/ir_unsafe.json @@ -72,6 +72,31 @@ "type": "i64" } }, + "f11": { + "body": [ + { + "text": "libc::rmdir(" + }, + { + "placeholder": { + "arg": 0, + "access": "read" + } + }, + { + "text": " as *const i8)" + } + ], + "params": { + "a0": { + "type": "*const u8", + "is_unsafe_pointer": true + } + }, + "return_type": { + "type": "i32" + } + }, "f2": { "body": [ { diff --git a/rules/unistd/src.cpp b/rules/unistd/src.cpp index bfeda143..7b0bd0f7 100644 --- a/rules/unistd/src.cpp +++ b/rules/unistd/src.cpp @@ -24,3 +24,5 @@ int f9(char *name, size_t len) { return gethostname(name, len); } ssize_t f10(int fd, const void *buf, size_t count) { return write(fd, buf, count); } + +int f11(const char *pathname) { return rmdir(pathname); } diff --git a/rules/unistd/tgt_unsafe.rs b/rules/unistd/tgt_unsafe.rs index fae5bf2d..674bfcf2 100644 --- a/rules/unistd/tgt_unsafe.rs +++ b/rules/unistd/tgt_unsafe.rs @@ -40,3 +40,7 @@ unsafe fn f9(a0: *mut u8, a1: u64) -> i32 { unsafe fn f10(a0: i32, a1: *const ::libc::c_void, a2: u64) -> i64 { libc::write(a0, a1, a2 as usize) as i64 } + +unsafe fn f11(a0: *const u8) -> i32 { + libc::rmdir(a0 as *const i8) +} From 5cdaff829183a59ca85b92aa2e9dae6a723ed224 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 14:54:38 +0100 Subject: [PATCH 17/19] Update modules --- rules/src/modules.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules/src/modules.rs b/rules/src/modules.rs index 6357cfc6..d2e5d0e9 100644 --- a/rules/src/modules.rs +++ b/rules/src/modules.rs @@ -74,6 +74,8 @@ pub mod string_tgt_unsafe; pub mod unique_ptr_tgt_refcount; #[path = r#"../unique_ptr/tgt_unsafe.rs"#] pub mod unique_ptr_tgt_unsafe; +#[path = r#"../unistd/tgt_unsafe.rs"#] +pub mod unistd_tgt_unsafe; #[path = r#"../vector/tgt_refcount.rs"#] pub mod vector_tgt_refcount; #[path = r#"../vector/tgt_unsafe.rs"#] From c783abe01ffd556a8d8d575330023ac7a1f8d7b4 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 19 May 2026 15:23:03 +0100 Subject: [PATCH 18/19] format --- tests/unit/unistd.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/unistd.c b/tests/unit/unistd.c index c5798e5b..2ed49f68 100644 --- a/tests/unit/unistd.c +++ b/tests/unit/unistd.c @@ -87,13 +87,9 @@ static void test_ftruncate(void) { unlink(path); } -static void test_isatty(void) { - printf("%d\n", isatty(0)); -} +static void test_isatty(void) { printf("%d\n", isatty(0)); } -static void test_geteuid(void) { - printf("%u\n", geteuid()); -} +static void test_geteuid(void) { printf("%u\n", geteuid()); } static void test_gethostname(void) { char name[256]; From b2960b9135f00a33ffd83cc3a3fd1d95099113fe Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 20 May 2026 10:26:30 +0100 Subject: [PATCH 19/19] Update tests --- tests/unit/out/unsafe/stdio.rs | 1 + tests/unit/stdio.c | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/unit/out/unsafe/stdio.rs b/tests/unit/out/unsafe/stdio.rs index 484b7cd5..2114c852 100644 --- a/tests/unit/out/unsafe/stdio.rs +++ b/tests/unit/out/unsafe/stdio.rs @@ -175,6 +175,7 @@ pub unsafe fn test_fileno_3() { Box::from_raw(fp); 0 }; + assert!(((((libc::unlink(file as *const i8)) == (0)) as i32) != 0)); } pub fn main() { unsafe { diff --git a/tests/unit/stdio.c b/tests/unit/stdio.c index 4f3f75e5..6552cdf8 100644 --- a/tests/unit/stdio.c +++ b/tests/unit/stdio.c @@ -1,6 +1,7 @@ // no-compile: refcount #include #include +#include static void test_fputc(void) { fputc('H', stdout);