| // 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}} |
| } |