Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions ctest/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,16 @@ pub fn __compile_test(

// Pass in a different target, linker or flags if set, useful for cross compilation.

let target = env::var("TARGET_PLATFORM").unwrap_or_default();
// Fall back to TARGET if TARGET_PLATFORM is not set (e.g. during local cargo test).
let target = env::var("TARGET_PLATFORM")
.or_else(|_| env::var("TARGET"))
.unwrap_or_default();
Comment on lines +145 to +147

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

if !target.is_empty() {
cmd.arg("--target").arg(target);
cmd.arg("--target").arg(&target);
}

if target.contains("windows") && target.contains("gnu") {
cmd.arg("-lws2_32");
}

let linker = env::var("LINKER").unwrap_or_default();
Expand Down
13 changes: 12 additions & 1 deletion ctest/templates/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
/* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'.
* This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`)
* to check its signedness, since volatile is ignored on rvalue expressions.
*/
#pragma warning(disable:4197)
#endif

{%- for (header, defines) in self.headers +%}
{%- for define in defines +%}

Expand Down Expand Up @@ -89,7 +97,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}(v
}

CTEST_EXTERN uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) {
return sizeof((({{ item.c_ty }}){}).{{ item.c_field }});
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof((({{ item.c_ty }} *)0)->{{ item.c_field }});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

}
{%- endfor +%}

Expand Down
49 changes: 48 additions & 1 deletion ctest/templates/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,53 @@ mod generated_tests {
}
}

fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
return;
}

#[cfg(target_os = "windows")]
let (rust, c) = {
let resolve = |addr: u64| -> u64 {
if addr == 0 {
return 0;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
let bytes = addr as *const u8;
// Check for a jump instruction (0xff 0x25), which is used for import thunks.
if *bytes == 0xff && *bytes.add(1) == 0x25 {
if cfg!(target_arch = "x86_64") {
// On x86_64, the jump is RIP-relative: jmp [RIP + displacement]
// bytes[2..6] holds the 32-bit signed displacement.
// bytes.add(6) is the instruction pointer (RIP) after the instruction.
let displacement = (bytes.add(2) as *const i32).read_unaligned();
let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const ();
return *slot_addr as u64;
} else if cfg!(target_arch = "x86") {
// On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address]
// bytes[2..6] holds the 32-bit absolute address.
let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned();
let slot_addr = slot_addr_val as *const *const ();
return *slot_addr as u64;
}
}
}
addr
};

(resolve(rust), resolve(c))
};

if rust != c {
eprintln!("bad {attr}: rust: {rust:?} != c {c:?}");
FAILED.store(true, Ordering::Relaxed);
} else {
NTESTS.fetch_add(1, Ordering::Relaxed);
}
}

fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
Expand Down Expand Up @@ -360,7 +407,7 @@ mod generated_tests {
}
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() } as u64;
let expected = {{ item.id }} as *const () as u64;
check_same(actual, expected, "`{{ item.id }}` function pointer");
check_same_fn_ptr(actual, expected, "`{{ item.id }}` function pointer");
}
{%- endfor +%}

Expand Down
8 changes: 8 additions & 0 deletions ctest/tests/input/hierarchy.out.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
/* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'.
* This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`)
* to check its signedness, since volatile is ignored on rvalue expressions.
*/
#pragma warning(disable:4197)
#endif
#include <hierarchy.h>

#if defined(__cplusplus)
Expand Down
49 changes: 48 additions & 1 deletion ctest/tests/input/hierarchy.out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,53 @@ mod generated_tests {
}
}

fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
return;
}

#[cfg(target_os = "windows")]
let (rust, c) = {
let resolve = |addr: u64| -> u64 {
if addr == 0 {
return 0;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
let bytes = addr as *const u8;
// Check for a jump instruction (0xff 0x25), which is used for import thunks.
if *bytes == 0xff && *bytes.add(1) == 0x25 {
if cfg!(target_arch = "x86_64") {
// On x86_64, the jump is RIP-relative: jmp [RIP + displacement]
// bytes[2..6] holds the 32-bit signed displacement.
// bytes.add(6) is the instruction pointer (RIP) after the instruction.
let displacement = (bytes.add(2) as *const i32).read_unaligned();
let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const ();
return *slot_addr as u64;
} else if cfg!(target_arch = "x86") {
// On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address]
// bytes[2..6] holds the 32-bit absolute address.
let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned();
let slot_addr = slot_addr_val as *const *const ();
return *slot_addr as u64;
}
}
}
addr
};

(resolve(rust), resolve(c))
};

if rust != c {
eprintln!("bad {attr}: rust: {rust:?} != c {c:?}");
FAILED.store(true, Ordering::Relaxed);
} else {
NTESTS.fetch_add(1, Ordering::Relaxed);
}
}

fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
Expand Down Expand Up @@ -255,7 +302,7 @@ mod generated_tests {
}
let actual = unsafe { ctest_foreign_fn__malloc() } as u64;
let expected = malloc as *const () as u64;
check_same(actual, expected, "`malloc` function pointer");
check_same_fn_ptr(actual, expected, "`malloc` function pointer");
}

/* Tests if the pointer to the static variable matches in both Rust and C. */
Expand Down
28 changes: 24 additions & 4 deletions ctest/tests/input/macro.out.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
/* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'.
* This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`)
* to check its signedness, since volatile is ignored on rvalue expressions.
*/
#pragma warning(disable:4197)
#endif

#define SUPPRESS_ERROR
#include <macro.h>
#undef SUPPRESS_ERROR
Expand Down Expand Up @@ -56,31 +64,43 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) {
}

CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) {
return sizeof(((struct VecU8){}).x);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU8 *)0)->x);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) {
return offsetof(struct VecU8, y);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) {
return sizeof(((struct VecU8){}).y);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU8 *)0)->y);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) {
return offsetof(struct VecU16, x);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) {
return sizeof(((struct VecU16){}).x);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU16 *)0)->x);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) {
return offsetof(struct VecU16, y);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) {
return sizeof(((struct VecU16){}).y);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU16 *)0)->y);
}


Expand Down
28 changes: 24 additions & 4 deletions ctest/tests/input/macro.out.edition-2024.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
/* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'.
* This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`)
* to check its signedness, since volatile is ignored on rvalue expressions.
*/
#pragma warning(disable:4197)
#endif

#define SUPPRESS_ERROR
#include <macro.h>
#undef SUPPRESS_ERROR
Expand Down Expand Up @@ -56,31 +64,43 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) {
}

CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) {
return sizeof(((struct VecU8){}).x);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU8 *)0)->x);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) {
return offsetof(struct VecU8, y);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) {
return sizeof(((struct VecU8){}).y);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU8 *)0)->y);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) {
return offsetof(struct VecU16, x);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) {
return sizeof(((struct VecU16){}).x);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU16 *)0)->x);
}

CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) {
return offsetof(struct VecU16, y);
}

CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) {
return sizeof(((struct VecU16){}).y);
/* MSVC C compiler does not support empty compound literals like `(Type){}`.
* Using the standard pointer-to-null dereference is portable across all compilers.
*/
return sizeof(((struct VecU16 *)0)->y);
}


Expand Down
47 changes: 47 additions & 0 deletions ctest/tests/input/macro.out.edition-2024.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,53 @@ mod generated_tests {
}
}

fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
return;
}

#[cfg(target_os = "windows")]
let (rust, c) = {
let resolve = |addr: u64| -> u64 {
if addr == 0 {
return 0;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
let bytes = addr as *const u8;
// Check for a jump instruction (0xff 0x25), which is used for import thunks.
if *bytes == 0xff && *bytes.add(1) == 0x25 {
if cfg!(target_arch = "x86_64") {
// On x86_64, the jump is RIP-relative: jmp [RIP + displacement]
// bytes[2..6] holds the 32-bit signed displacement.
// bytes.add(6) is the instruction pointer (RIP) after the instruction.
let displacement = (bytes.add(2) as *const i32).read_unaligned();
let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const ();
return *slot_addr as u64;
} else if cfg!(target_arch = "x86") {
// On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address]
// bytes[2..6] holds the 32-bit absolute address.
let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned();
let slot_addr = slot_addr_val as *const *const ();
return *slot_addr as u64;
}
}
}
addr
};

(resolve(rust), resolve(c))
};

if rust != c {
eprintln!("bad {attr}: rust: {rust:?} != c {c:?}");
FAILED.store(true, Ordering::Relaxed);
} else {
NTESTS.fetch_add(1, Ordering::Relaxed);
}
}

fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) {
if rust == c {
NTESTS.fetch_add(1, Ordering::Relaxed);
Expand Down
Loading
Loading