Files
zig/lib/c/string.zig
Alex Rønne Petersen 7ea8f842bc libzigc: move all unit tests from lib/c/ to test/c/
Before:

* test-zigc: run libzigc unit tests (part of test-modules)
* test-libc: run libc-test cases

Now:

* test-libc: run libc API unit tests (part of test-modules)
* test-libc-nsz: run libc-test cases

libc API unit tests (previously referred to as libzigc unit tests) now run for
all supported targets, even those we don't provide libzigc for. The idea is that
this will help us catch bad assumptions in the unit tests, as well as bugs in
other libcs.

I considered this setup:

* test-c: run libc API unit tests (part of test-modules)
* test-libc-nsz: run libc-test cases
* test-libc: both of the above

However, I do not like it because it gives a false sense of security; the full
module and C ABI test suites are still liable to catch libzigc bugs that test-c
and test-libc-nsz might not. So contributors should just run the test steps
outlined in https://codeberg.org/ziglang/zig/issues/30978.

Co-authored-by: rpkak <rpkak@noreply.codeberg.org>
2026-04-17 12:10:37 +02:00

293 lines
11 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const symbol = @import("../c.zig").symbol;
comptime {
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
// memcpy implemented in compiler_rt
// memmove implemented in compiler_rt
// memset implemented in compiler_rt
// memcmp implemented in compiler_rt
symbol(&memchr, "memchr");
symbol(&strcpy, "strcpy");
symbol(&strncpy, "strncpy");
symbol(&strcat, "strcat");
symbol(&strncat, "strncat");
symbol(&strcmp, "strcmp");
symbol(&strncmp, "strncmp");
symbol(&strcoll, "strcoll");
symbol(&strxfrm, "strxfrm");
symbol(&strchr, "strchr");
symbol(&strrchr, "strrchr");
symbol(&strcspn, "strcspn");
symbol(&strspn, "strspn");
symbol(&strpbrk, "strpbrk");
symbol(&strstr, "strstr");
symbol(&strtok, "strtok");
// strlen is in compiler_rt
symbol(&strtok_r, "strtok_r");
symbol(&stpcpy, "stpcpy");
symbol(&stpncpy, "stpncpy");
symbol(&strnlen, "strnlen");
symbol(&memmem, "memmem");
symbol(&memccpy, "memccpy");
symbol(&strsep, "strsep");
symbol(&strlcat, "strlcat");
symbol(&strlcpy, "strlcpy");
symbol(&explicit_bzero, "explicit_bzero");
symbol(&strchrnul, "strchrnul");
symbol(&strcasestr, "strcasestr");
symbol(&memrchr, "memrchr");
symbol(&mempcpy, "mempcpy");
symbol(&__strcoll_l, "__strcoll_l");
symbol(&__strxfrm_l, "__strxfrm_l");
symbol(&__strcoll_l, "strcoll_l");
symbol(&__strxfrm_l, "strxfrm_l");
// These symbols are not in the public ABI of musl/wasi. However they depend on these exports internally.
symbol(&stpcpy, "__stpcpy");
symbol(&stpncpy, "__stpncpy");
symbol(&strchrnul, "__strchrnul");
symbol(&memrchr, "__memrchr");
}
if (builtin.target.isMinGW()) {
symbol(&strnlen, "strnlen");
symbol(&mempcpy, "mempcpy");
symbol(&strtok_r, "strtok_r");
}
}
fn memchr(ptr: *const anyopaque, value: c_int, len: usize) callconv(.c) ?*anyopaque {
const bytes: [*]const u8 = @ptrCast(ptr);
return @constCast(bytes[std.mem.findScalar(u8, bytes[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}
fn strcpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char) callconv(.c) [*]c_char {
_ = stpcpy(dst, src);
return dst;
}
fn strncpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*]c_char {
_ = stpncpy(dst, src, max);
return dst;
}
fn strcat(noalias dst: [*:0]c_char, noalias src: [*:0]const c_char) callconv(.c) [*:0]c_char {
return strncat(dst, src, std.math.maxInt(usize));
}
fn strncat(noalias dst: [*:0]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*:0]c_char {
const dst_len = std.mem.len(@as([*:0]u8, @ptrCast(dst)));
const src_len = strnlen(src, max);
@memcpy(dst[dst_len..][0..src_len], src[0..src_len]);
dst[dst_len + src_len] = 0;
return dst[0..(dst_len + src_len) :0].ptr;
}
fn strcmp(a: [*:0]const c_char, b: [*:0]const c_char) callconv(.c) c_int {
return strncmp(a, b, std.math.maxInt(usize));
}
fn strncmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) callconv(.c) c_int {
return switch (std.mem.boundedOrderZ(u8, @ptrCast(a), @ptrCast(b), max)) {
.eq => 0,
.gt => 1,
.lt => -1,
};
}
fn strcoll(a: [*:0]const c_char, b: [*:0]const c_char) callconv(.c) c_int {
return strcmp(a, b);
}
fn __strcoll_l(a: [*:0]const c_char, b: [*:0]const c_char, locale: *anyopaque) callconv(.c) c_int {
_ = locale;
return strcoll(a, b);
}
// NOTE: If 'max' is 0, 'dst' is allowed to be a null pointer
fn strxfrm(noalias dst: ?[*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) usize {
const src_len = std.mem.len(@as([*:0]const u8, @ptrCast(src)));
if (src_len < max) @memcpy(dst.?[0 .. src_len + 1], src[0 .. src_len + 1]);
return src_len;
}
fn __strxfrm_l(noalias dst: ?[*]c_char, noalias src: [*:0]const c_char, max: usize, locale: *anyopaque) callconv(.c) usize {
_ = locale;
return strxfrm(dst, src, max);
}
fn strchr(str: [*:0]const c_char, value: c_int) callconv(.c) ?[*:0]c_char {
const str_u8: [*:0]const u8 = @ptrCast(str);
const len = std.mem.len(str_u8);
if (value == 0) return @constCast(str + len);
return @constCast(str[std.mem.findScalar(u8, str_u8[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}
fn strrchr(str: [*:0]const c_char, value: c_int) callconv(.c) ?[*:0]c_char {
const str_u8: [*:0]const u8 = @ptrCast(str);
// std.mem.len(str) + 1 to not special case '\0'
return @constCast(str[std.mem.findScalarLast(u8, str_u8[0 .. std.mem.len(str_u8) + 1], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}
fn strcspn(dst: [*:0]const c_char, values: [*:0]const c_char) callconv(.c) usize {
const dst_slice = std.mem.span(@as([*:0]const u8, @ptrCast(dst)));
return std.mem.findAny(u8, dst_slice, std.mem.span(@as([*:0]const u8, @ptrCast(values)))) orelse dst_slice.len;
}
fn strspn(dst: [*:0]const c_char, values: [*:0]const c_char) callconv(.c) usize {
const dst_slice = std.mem.span(@as([*:0]const u8, @ptrCast(dst)));
return std.mem.findNone(u8, dst_slice, std.mem.span(@as([*:0]const u8, @ptrCast(values)))) orelse dst_slice.len;
}
fn strpbrk(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
return @constCast(haystack[std.mem.findAny(u8, std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}
fn strstr(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
return @constCast(haystack[std.mem.find(u8, std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}
fn strtok(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
const state = struct {
var str: ?[*:0]c_char = null;
};
return strtok_r(maybe_str, values, &state.str);
}
// strlen is in compiler_rt
fn strtok_r(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char, noalias state: *?[*:0]c_char) callconv(.c) ?[*:0]c_char {
const str = if (maybe_str) |str|
str
else if (state.*) |state_str|
state_str
else
return null;
const str_bytes = std.mem.span(@as([*:0]u8, @ptrCast(str)));
const values_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(values)));
const tok_start = std.mem.findNone(u8, str_bytes, values_bytes) orelse return null;
if (std.mem.findAnyPos(u8, str_bytes, tok_start, values_bytes)) |tok_end| {
str[tok_end] = 0;
state.* = str[tok_end + 1 ..];
} else {
state.* = str[str_bytes.len..];
}
return str[tok_start..];
}
fn stpcpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char) callconv(.c) [*]c_char {
const src_len = std.mem.len(@as([*:0]const u8, @ptrCast(src)));
@memcpy(dst[0 .. src_len + 1], src[0 .. src_len + 1]);
return dst + src_len;
}
fn stpncpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*]c_char {
const src_len = strnlen(src, max);
const copying_len = @min(max, src_len);
@memcpy(dst[0..copying_len], src[0..copying_len]);
@memset(dst[copying_len..][0 .. max - copying_len], 0x00);
return dst + copying_len;
}
fn strnlen(str: [*:0]const c_char, max: usize) callconv(.c) usize {
return std.mem.findScalar(u8, @ptrCast(str[0..max]), 0) orelse max;
}
fn memmem(haystack: *const anyopaque, haystack_len: usize, needle: *const anyopaque, needle_len: usize) callconv(.c) ?*anyopaque {
const haystack_bytes: [*:0]const u8 = @ptrCast(haystack);
const needle_bytes: [*:0]const u8 = @ptrCast(needle);
return @constCast(haystack_bytes[std.mem.find(u8, haystack_bytes[0..haystack_len], needle_bytes[0..needle_len]) orelse return null ..]);
}
fn strsep(maybe_str: *?[*:0]c_char, values: [*:0]const c_char) callconv(.c) ?[*]c_char {
if (maybe_str.*) |str| {
const values_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(values)));
const str_bytes = std.mem.span(@as([*:0]u8, @ptrCast(str)));
const found = std.mem.findAny(u8, str_bytes, values_bytes) orelse {
maybe_str.* = null;
return str;
};
str[found] = 0;
maybe_str.* = str[found + 1 ..];
return str;
}
return null;
}
fn strlcat(dst: [*:0]c_char, src: [*:0]const c_char, dst_total_len: usize) callconv(.c) usize {
const dst_len = strnlen(dst, dst_total_len);
const src_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(src)));
if (dst_total_len == dst_len) return dst_len + src_bytes.len;
const copying_len = @min(dst_total_len - (dst_len + 1), src_bytes.len);
@memcpy(dst[dst_len..][0..copying_len], src[0..copying_len]);
dst[dst_len + copying_len] = 0;
return dst_len + src_bytes.len;
}
fn strlcpy(dst: [*]c_char, src: [*:0]const c_char, dst_total_len: usize) callconv(.c) usize {
const src_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(src)));
if (dst_total_len != 0) {
const copying_len = @min(src_bytes.len, dst_total_len - 1);
@memcpy(dst[0..copying_len], src[0..copying_len]);
dst[copying_len] = 0;
}
return src_bytes.len;
}
fn memccpy(noalias dst: *anyopaque, noalias src: *const anyopaque, value: c_int, len: usize) callconv(.c) *anyopaque {
const dst_bytes: [*]u8 = @ptrCast(dst);
const src_bytes: [*]const u8 = @ptrCast(src);
const value_u8: u8 = @truncate(@as(c_uint, @bitCast(value)));
const copying_len = std.mem.findScalar(u8, src_bytes[0..len], value_u8) orelse len;
@memcpy(dst_bytes[0..copying_len], src_bytes[0..copying_len]);
return dst_bytes + copying_len;
}
fn explicit_bzero(ptr: *anyopaque, len: usize) callconv(.c) void {
const bytes: [*]u8 = @ptrCast(ptr);
std.crypto.secureZero(u8, bytes[0..len]);
}
fn strchrnul(str: [*:0]const c_char, value: c_int) callconv(.c) [*:0]c_char {
const str_u8: [*:0]const u8 = @ptrCast(str);
const len = std.mem.len(str_u8);
if (value == 0) return @constCast(str + len);
return @constCast(str[std.mem.findScalar(u8, str_u8[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse len ..]);
}
fn strcasestr(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
return @constCast(haystack[std.ascii.findIgnoreCase(std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}
fn memrchr(ptr: *const anyopaque, value: c_int, len: usize) callconv(.c) ?*anyopaque {
const bytes: [*]const u8 = @ptrCast(ptr);
return @constCast(bytes[std.mem.findScalarLast(u8, bytes[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}
fn mempcpy(noalias dst: *anyopaque, noalias src: *const anyopaque, len: usize) callconv(.c) *anyopaque {
const dst_bytes: [*]u8 = @ptrCast(dst);
const src_bytes: [*]const u8 = @ptrCast(src);
@memcpy(dst_bytes[0..len], src_bytes[0..len]);
return dst_bytes + len;
}