blob: a43a836a4bf44d07eb4a6687f3284ee8487d5083 [file] [edit]
// The u"" and U"" string literals below need C11 or later. Pin the standard
// because targets such as PS4 default to gnu99.
//
// Deliberately no -triple here: the expectations below must hold for every
// target and host, including big-endian ones. See test_strchr_wide_string_global.
// RUN: %clang_analyze_cc1 -std=c17 -verify %s \
// RUN: -analyzer-checker=core,unix \
// RUN: -analyzer-checker=debug.ExprInspection \
// RUN: -analyzer-config eagerly-assume=false
//
// Also check a target whose default C standard is gnu99, so that dropping the
// -std= above is caught everywhere instead of only on the SIE buildbots.
// RUN: %clang_analyze_cc1 -triple x86_64-scei-ps4 -std=c17 -verify %s \
// RUN: -analyzer-checker=core,unix \
// RUN: -analyzer-checker=debug.ExprInspection \
// RUN: -analyzer-config eagerly-assume=false
// REQUIRES: x86-registered-target
typedef __SIZE_TYPE__ size_t;
void *malloc(size_t size);
void free(void *p);
void *memcpy(void *dest, const void *src, size_t n);
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
char *strstr(const char *haystack, const char *needle);
char *strpbrk(const char *s, const char *accept);
void *memchr(const void *s, int c, size_t n);
char *strchrnul(const char *s, int c);
void clang_analyzer_eval(int);
//===----------------------------------------------------------------------===//
// Check for stack address escapes.
//===----------------------------------------------------------------------===//
char *returns_stack_strchr(void) {
char buf[8] = "abc";
return strchr(buf, 'b');
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
char *returns_stack_strrchr(void) {
char buf[8] = "abc";
return strrchr(buf, 'b');
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
char *returns_stack_strstr(void) {
char buf[8] = "abc";
return strstr(buf, "b");
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
char *returns_stack_strpbrk(void) {
char buf[8] = "abc";
return strpbrk(buf, "b");
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
void *returns_stack_memchr(void) {
char buf[8] = "abc";
return memchr(buf, 'b', sizeof buf);
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
char *returns_stack_strchrnul(void) {
char buf[8] = "abc";
return strchrnul(buf, 'b');
// expected-warning@-1 {{Address of stack memory associated with local variable 'buf' returned to caller}}
}
char *forwards_param(char *p) {
return strchr(p, 'b'); // no-warning
}
char *returns_local_static(void) {
extern char g[8];
return strchr(g, 'b'); // no-warning
}
//===----------------------------------------------------------------------===//
// unix.cstring.NullArg: the source pointer must be non-null.
//===----------------------------------------------------------------------===//
void null_source_strchr(int c) {
strchr(0, c);
// expected-warning@-1 {{Null pointer passed as 1st argument to strchr()}}
}
void null_source_strrchr(int c) {
strrchr(0, c);
// expected-warning@-1 {{Null pointer passed as 1st argument to strrchr()}}
}
void null_source_memchr(int c) {
memchr(0, c, 4);
// expected-warning@-1 {{Null pointer passed as 1st argument to memchr()}}
}
void null_source_strstr(void) {
strstr(0, "x");
// expected-warning@-1 {{Null pointer passed as 1st argument to strstr()}}
}
void null_source_strpbrk(void) {
strpbrk(0, "x");
// expected-warning@-1 {{Null pointer passed as 1st argument to strpbrk()}}
}
void null_source_strchrnul(int c) {
strchrnul(0, c);
// expected-warning@-1 {{Null pointer passed as 1st argument to strchrnul()}}
}
//===----------------------------------------------------------------------===//
// State split: result == NULL on one branch, in the source on the other.
//===----------------------------------------------------------------------===//
// Both branches are reachable; the verifier matches the two values set-wise.
void state_split(const char *p) {
clang_analyzer_eval(strchr(p, 'b') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
clang_analyzer_eval(strrchr(p, 'b') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
clang_analyzer_eval(strstr(p, "x") == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
clang_analyzer_eval(strpbrk(p, "x") == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
clang_analyzer_eval(memchr(p, 'b', 4) == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// strchrnul does not split: it never returns NULL at runtime.
void strchrnul_is_nonnull(const char *p) {
clang_analyzer_eval(strchrnul(p, 'b') == 0); // expected-warning {{FALSE}}
}
// On the "found" branch the result aliases the source region, but the offset
// is opaque, so equality with in-source pointers is UNKNOWN.
void found_branch_offset_is_opaque(const char *p) {
char *q = strchr(p, 'b');
if (!q) return; // constrain to "found" branch
clang_analyzer_eval(q == p); // expected-warning {{UNKNOWN}}
clang_analyzer_eval(q == p + 1); // expected-warning {{UNKNOWN}}
}
void resulting_ptr_shares_provenance_with_src(int rng, char *opaque) {
if (rng == 10) {
char *q = strchr("abcd", 'b');
free(q); // expected-warning {{Argument to 'free()' is the address of a global variable, which is not memory allocated by 'malloc()'}}
return;
}
if (rng == 20) {
char *q = strchr(opaque, 'b');
free(q); // ok
return;
}
if (rng == 30) {
char *q = strchr(opaque, 'b');
free(q); // Notionally releases 'opaque'.
free(opaque); // expected-warning {{Attempt to release already released memory}}
return;
}
}
//===----------------------------------------------------------------------===//
// core.NullDereference:
// A returned pointer used without a NULL check is flagged on the NULL branch.
//===----------------------------------------------------------------------===//
void deref_unchecked(const char *s) {
char *p = strchr(s, 'b');
*p = 'X'; // expected-warning {{Dereference of null pointer}}
}
void deref_after_check(const char *s) {
char *p = strchr(s, 'b');
if (p) {
*p = 'X'; // no-warning
}
}
//===----------------------------------------------------------------------===//
// Calling these functions does not invalidate unrelated memory.
//===----------------------------------------------------------------------===//
int global_unmodified;
void no_invalidation_of_globals(const char *p) {
int local_unmodified = 10;
global_unmodified = 20;
(void)strchr(p, 'b');
clang_analyzer_eval(local_unmodified == 10); // expected-warning {{TRUE}}
clang_analyzer_eval(global_unmodified == 20); // expected-warning {{TRUE}}
}
//===----------------------------------------------------------------------===//
// When both arguments are compile-time constants, only the correct branch is
// taken: found when the target exists, null when it does not.
// See: https://github.com/llvm/llvm-project/issues/209905
//===----------------------------------------------------------------------===//
// --- strchr / strrchr: target character IS in the literal ---
const char *test_strrchr_const_no_fp(void) {
// This is the original reproducer from #209905.
return strrchr("/foo/bar.c", '/') ? strrchr("/foo/bar.c", '/') + 1 : "/foo/bar.c"; // no-warning
}
void test_strchr_const_found(void) {
clang_analyzer_eval(strchr("/foo/bar.c", '/') == 0); // expected-warning {{FALSE}}
}
void test_strrchr_const_found(void) {
clang_analyzer_eval(strrchr("/foo/bar.c", '/') == 0); // expected-warning {{FALSE}}
}
// --- strchr / strrchr: target character is NOT in the literal ---
void test_strchr_const_not_found(void) {
clang_analyzer_eval(strchr("hello", 'z') == 0); // expected-warning {{TRUE}}
}
void test_strrchr_const_not_found(void) {
clang_analyzer_eval(strrchr("hello", 'z') == 0); // expected-warning {{TRUE}}
}
// --- memchr: character within bounds ---
void test_memchr_const_found(void) {
clang_analyzer_eval(memchr("abcdef", 'c', 6) == 0); // expected-warning {{FALSE}}
}
// --- memchr: character beyond the specified length ---
void test_memchr_const_not_in_range(void) {
clang_analyzer_eval(memchr("abcdef", 'f', 3) == 0); // expected-warning {{TRUE}}
}
// --- strstr: needle IS a substring ---
void test_strstr_const_found(void) {
clang_analyzer_eval(strstr("hello world", "world") == 0); // expected-warning {{FALSE}}
}
// --- strstr: needle is NOT a substring ---
void test_strstr_const_not_found(void) {
clang_analyzer_eval(strstr("hello world", "xyz") == 0); // expected-warning {{TRUE}}
}
// --- strpbrk: accept set has a match ---
void test_strpbrk_const_found(void) {
clang_analyzer_eval(strpbrk("hello", "aeiou") == 0); // expected-warning {{FALSE}}
}
// --- strpbrk: no character from accept set in source ---
void test_strpbrk_const_not_found(void) {
clang_analyzer_eval(strpbrk("hello", "xyz") == 0); // expected-warning {{TRUE}}
}
// --- Non-constant source: both branches must still exist ---
void test_strchr_non_const_source(const char *p) {
clang_analyzer_eval(strchr(p, '/') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// --- Various constant source forms: const, static const, #define, __FILE__ ---
static const char static_const_path[] = "/usr/local/bin/tool";
void test_strchr_static_const(void) {
clang_analyzer_eval(strchr(static_const_path, '/') == 0); // expected-warning {{FALSE}}
}
const char global_const_path[] = "/etc/config";
void test_strrchr_global_const(void) {
clang_analyzer_eval(strrchr(global_const_path, '/') == 0); // expected-warning {{FALSE}}
}
#define FIXED_PATH "/home/user/project/file.c"
void test_strchr_define(void) {
clang_analyzer_eval(strchr(FIXED_PATH, '/') == 0); // expected-warning {{FALSE}}
}
#define MY_FILE_BASENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
void test_file_basename_macro(void) {
const char *base = MY_FILE_BASENAME; // no-warning
(void)base;
}
#define PREFIX "module"
#define SUFFIX "_handler"
// Adjacent string literal concatenation (the realistic preprocessor pattern):
#define MODULE_PATH "/opt/" PREFIX "/" SUFFIX ".so"
void test_strchr_concatenated_define(void) {
clang_analyzer_eval(strchr(MODULE_PATH, '/') == 0); // expected-warning {{FALSE}}
}
// --- Character argument via #define ---
#define SEPARATOR '/'
void test_strchr_define_char(void) {
clang_analyzer_eval(strchr("/foo/bar", SEPARATOR) == 0); // expected-warning {{FALSE}}
}
#define SEARCH_CHAR 'x'
void test_strchr_define_char_not_found(void) {
clang_analyzer_eval(strchr("/foo/bar", SEARCH_CHAR) == 0); // expected-warning {{TRUE}}
}
// --- Edge cases: null character '\0' ---
void test_strchr_null_char_always_found(void) {
// strchr(s, '\0') always finds the terminator.
clang_analyzer_eval(strchr("hello", '\0') == 0); // expected-warning {{FALSE}}
}
void test_strrchr_null_char_always_found(void) {
clang_analyzer_eval(strrchr("hello", '\0') == 0); // expected-warning {{FALSE}}
}
void test_memchr_null_char_within_bounds(void) {
// "abc" has terminator at index 3; searching 4 bytes includes it.
clang_analyzer_eval(memchr("abc", '\0', 4) == 0); // expected-warning {{FALSE}}
}
void test_memchr_null_char_out_of_bounds(void) {
// "abc" has terminator at index 3; searching only 3 bytes misses it.
clang_analyzer_eval(memchr("abc", '\0', 3) == 0); // expected-warning {{TRUE}}
}
// --- Edge cases: empty strings ---
void test_strchr_empty_haystack(void) {
// Empty string only contains '\0'; '/' is not there.
clang_analyzer_eval(strchr("", '/') == 0); // expected-warning {{TRUE}}
}
void test_strchr_empty_haystack_null_char(void) {
// strchr("", '\0') finds the terminator.
clang_analyzer_eval(strchr("", '\0') == 0); // expected-warning {{FALSE}}
}
void test_strstr_empty_needle(void) {
// strstr(s, "") always returns s.
clang_analyzer_eval(strstr("hello", "") == 0); // expected-warning {{FALSE}}
}
void test_strpbrk_empty_accept(void) {
// strpbrk(s, "") never matches.
clang_analyzer_eval(strpbrk("hello", "") == 0); // expected-warning {{TRUE}}
}
//===----------------------------------------------------------------------===//
// Verify exact pointer offsets when both arguments are compile-time constants.
// The enhanced modeling returns Src + concrete_offset rather than a symbolic
// offset, enabling precise pointer arithmetic downstream.
//===----------------------------------------------------------------------===//
// --- strchr: returns pointer to first occurrence ---
void test_strchr_exact_offset(void) {
const char *s = "/foo/bar.c";
// '/' first appears at index 0.
clang_analyzer_eval(strchr(s, '/') == s); // expected-warning {{TRUE}}
}
// --- strrchr: returns pointer to last occurrence ---
void test_strrchr_exact_offset(void) {
const char *s = "/foo/bar.c";
// '/' last appears at index 4.
clang_analyzer_eval(strrchr(s, '/') == s + 4); // expected-warning {{TRUE}}
}
// --- strrchr + 1: the FILE_BASENAME pattern ---
void test_strrchr_plus_one(void) {
const char *s = "/foo/bar.c";
const char *base = strrchr(s, '/') + 1;
// Should point to 'b' at index 5.
clang_analyzer_eval(base == s + 5); // expected-warning {{TRUE}}
}
// --- strchr with null terminator: points to end of string ---
void test_strchr_null_terminator_offset(void) {
const char *s = "hello";
// strchr(s, '\0') returns pointer to the null terminator at index 5.
clang_analyzer_eval(strchr(s, '\0') == s + 5); // expected-warning {{TRUE}}
}
// --- strstr: returns pointer to first substring match ---
void test_strstr_exact_offset(void) {
const char *s = "hello world";
// "world" starts at index 6.
clang_analyzer_eval(strstr(s, "world") == s + 6); // expected-warning {{TRUE}}
}
// --- strstr with empty needle: returns the source pointer ---
void test_strstr_empty_needle_offset(void) {
const char *s = "hello";
clang_analyzer_eval(strstr(s, "") == s); // expected-warning {{TRUE}}
}
// --- strpbrk: returns pointer to first matching character ---
void test_strpbrk_exact_offset(void) {
const char *s = "hello";
// First vowel 'e' is at index 1.
clang_analyzer_eval(strpbrk(s, "aeiou") == s + 1); // expected-warning {{TRUE}}
}
// --- memchr: returns pointer to character within bounds ---
void test_memchr_exact_offset(void) {
const char *s = "abcdef";
// 'c' is at index 2.
clang_analyzer_eval(memchr(s, 'c', 6) == s + 2); // expected-warning {{TRUE}}
}
// --- memchr with embedded null characters ---
void test_memchr_embedded_null(void) {
// String literal "ab\0cd" has a null at index 2, then 'c' at 3, 'd' at 4,
// and the implicit terminator at index 5.
const char *s = "ab\0cd";
// memchr searching 5 bytes finds the first '\0' at index 2.
clang_analyzer_eval(memchr(s, '\0', 5) == s + 2); // expected-warning {{TRUE}}
}
void test_memchr_second_segment_after_null(void) {
const char *s = "ab\0cd";
// 'c' is at index 3; searching 5 bytes should find it.
clang_analyzer_eval(memchr(s, 'c', 5) == s + 3); // expected-warning {{TRUE}}
}
void test_memchr_char_before_null_boundary(void) {
const char *s = "ab\0cd";
// 'b' is at index 1; searching only 2 bytes still finds it.
clang_analyzer_eval(memchr(s, 'b', 2) == s + 1); // expected-warning {{TRUE}}
}
void test_memchr_char_hidden_by_short_len(void) {
const char *s = "ab\0cd";
// 'c' is at index 3 but searching only 3 bytes (indices 0-2) misses it.
clang_analyzer_eval(memchr(s, 'c', 3) == 0); // expected-warning {{TRUE}}
}
// --- strstr/strpbrk with embedded null characters ---
void test_strstr_hidden_by_null(void) {
const char *s = "ab\0cd";
// C strstr stops at the first null; "cd" is unreachable.
clang_analyzer_eval(strstr(s, "cd") == 0); // expected-warning {{TRUE}}
}
void test_strpbrk_hidden_by_null(void) {
const char *s = "ab\0cd";
// C strpbrk stops at the first null; 'c' is unreachable.
clang_analyzer_eval(strpbrk(s, "cd") == 0); // expected-warning {{TRUE}}
}
void test_strstr_before_null(void) {
const char *s = "1ab\0cd";
// "ab" is before the null, so strstr finds it at offset 1.
clang_analyzer_eval(strstr(s, "ab") == s + 1); // expected-warning {{TRUE}}
}
void test_strpbrk_before_null(void) {
const char *s = "ab\0cd";
// 'a' is before the null, so strpbrk finds it.
clang_analyzer_eval(strpbrk(s, "a") == s); // expected-warning {{TRUE}}
}
// --- memchr with pointer past embedded null ---
void test_memchr_pointer_past_null(void) {
const char *s = "1ab\0cdf\0qwrt";
// Starting from s+3 ("\0cdf\0qwrt"), search for 'd' in 4 bytes.
clang_analyzer_eval(memchr(s + 3, 'd', 4) == s + 5); // expected-warning {{TRUE}}
}
// --- Second argument with pointer offset ---
void test_strstr_needle_with_offset(void) {
const char *needles = "xxworld";
// needles + 2 is "world"; strstr finds it at index 6.
clang_analyzer_eval(strstr("hello world", needles + 2) == 0); // expected-warning {{FALSE}}
}
void test_strpbrk_accept_with_offset(void) {
const char *chars = "xxaeiou";
// chars + 2 is "aeiou"; first vowel 'e' in "hello" is at index 1.
clang_analyzer_eval(strpbrk("hello", chars + 2) == 0); // expected-warning {{FALSE}}
}
// --- Both arguments with pointer offsets ---
void test_strstr_both_offsets(void) {
const char *s = "XXhello world";
const char *needles = "xxworld";
// s+2 is "hello world", needles+2 is "world"; found at offset 6 from s+2.
clang_analyzer_eval(strstr(s + 2, needles + 2) == s + 8); // expected-warning {{TRUE}}
}
void test_strpbrk_both_offsets(void) {
const char *s = "XXhello";
const char *chars = "xxaeiou";
// s+2 is "hello", chars+2 is "aeiou"; first vowel 'e' at offset 1 from s+2.
clang_analyzer_eval(strpbrk(s + 2, chars + 2) == s + 3); // expected-warning {{TRUE}}
}
// --- strchrnul: always returns non-null (pointer to found char or terminator)
void test_strchrnul_not_found_points_to_terminator(void) {
const char *s = "hello";
// strchrnul(s, 'z') should return s + 5 (the null terminator).
clang_analyzer_eval(strchrnul(s, 'z') == s + 5); // expected-warning {{TRUE}}
}
void test_strchrnul_found_negative(void) {
const char *s = "xxuhello";
const char *ss = s + 3;
// 'u' is at index 1 in "xuhello", so strchrnul(ss-2, 'u') == s + 1 + 1 == s + 2.
clang_analyzer_eval(strchrnul(ss - 2, 'u') == s + 2); // expected-warning {{TRUE}}
}
void test_strchrnul_found_with_offset(void) {
const char *s = "xxxhello";
const char *ss = s + 1;
// ss is "xxhello", 'l' is at index 4 in "xxhello", so ss + 4 == s + 5.
clang_analyzer_eval(strchrnul(ss, 'l') == s + 5); // expected-warning {{TRUE}}
}
// --- Embedded null in the needle/accept set argument ---
void test_strstr_needle_embedded_null(void) {
// Needle "cd\0e" is truncated to "cd" by C semantics; "cd" is in "abcd".
clang_analyzer_eval(strstr("abcd", "cd\0e") == 0); // expected-warning {{FALSE}}
}
void test_strpbrk_accept_embedded_null(void) {
// Accept "a\0b" is truncated to "a"; 'a' is not in "xb", so result is null.
clang_analyzer_eval(strpbrk("xb", "a\0b") == 0); // expected-warning {{TRUE}}
}
// --- Wide string cast to char*: not folded ---
// A wide literal's AST storage holds its code units in host byte order, so its
// raw bytes must not be interpreted as a target byte string. The checker bails
// out on these and stays conservative, keeping both branches.
const __CHAR16_TYPE__ wide_str_global[] = u"abc";
void test_strchr_wide_string_global(void) {
clang_analyzer_eval(strchr((const char *)wide_str_global, 'a') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
void test_strchr_wide_string_local(void) {
const __CHAR16_TYPE__ w[] = u"abc";
clang_analyzer_eval(strchr((const char *)w, 'a') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// --- Wide string with 4-byte characters (UTF-32) ---
const __CHAR32_TYPE__ wide32_global[] = U"abc";
void test_strchr_wide32_string(void) {
clang_analyzer_eval(strchr((const char *)wide32_global, 'a') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// --- Wide string as needle argument ---
const __CHAR16_TYPE__ wide_needle[] = u"lo";
void test_strstr_wide_needle(void) {
const char *s = "hello";
clang_analyzer_eval(strstr(s, (const char *)wide_needle) == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// --- Struct cast to char* ---
struct FourChars {
char a, b, c, d;
};
void test_strchr_struct_cast(void) {
// TODO: resolve const struct initializers (no padding between char members).
struct FourChars s = {'h', 'e', 'l', 'l'};
clang_analyzer_eval(strchr((const char *)&s, 'e') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}
// --- Union with wide and narrow access ---
union CharUnion {
__CHAR16_TYPE__ w[4];
char c[8];
};
const union CharUnion cu = { .w = u"abc" };
void test_strchr_union_narrow_access(void) {
// TODO: resolve union members with known initializers.
clang_analyzer_eval(strchr(cu.c, 'a') == 0); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}