@@ -282,6 +282,93 @@ void test_fp_narrowing_survives_call(Node * _Nullable p) {
282282 (void )p->value ; // OK - function call doesn't invalidate narrowing
283283}
284284
285+ // --- Member narrowing in array subscripts and across calls ---
286+
287+ struct MemberNarrowObj {
288+ int * _Nullable arr;
289+ int * _Nullable other;
290+ int getMember () const ;
291+
292+ void test_member_subscript_after_check () {
293+ if (!arr) return ;
294+ (void )arr[0 ]; // OK - member narrowed by null check
295+ }
296+
297+ void test_member_subscript_across_call () {
298+ if (!arr) return ;
299+ getMember ();
300+ (void )arr[0 ]; // OK - calls don't invalidate member narrowing
301+ }
302+
303+ void test_member_subscript_in_loop () {
304+ if (!arr) return ;
305+ for (int i = 0 ; i < 3 ; i++) {
306+ (void )arr[i]; // OK - member narrowed before loop
307+ }
308+ }
309+
310+ void test_member_deref_across_call () {
311+ if (!arr) return ;
312+ getMember ();
313+ (void )*arr; // OK - calls don't invalidate member narrowing
314+ }
315+
316+ void test_member_subscript_no_check () {
317+ (void )arr[0 ]; // expected-warning {{dereference of nullable pointer}} \
318+ // expected-note {{add a null check}}
319+ }
320+
321+ void test_two_members_narrowed () {
322+ if (!arr || !other) return ;
323+ (void )arr[0 ]; // OK
324+ (void )other[0 ]; // OK
325+ }
326+
327+ // Explicit == nullptr checks, disjunctions, if-body narrowing
328+
329+ void test_member_eq_nullptr_early_return () {
330+ if (arr == nullptr ) return ;
331+ (void )arr[0 ]; // OK - == nullptr style check
332+ }
333+
334+ void test_member_ne_nullptr_if_body () {
335+ // Narrowing inside if-body (not early return)
336+ if (arr != nullptr ) {
337+ (void )arr[0 ]; // OK - narrowed inside if-body
338+ }
339+ }
340+
341+ void test_member_disjunction_guard () {
342+ // Multiple members checked with || and early return
343+ if (arr == nullptr || other == nullptr ) return ;
344+ (void )arr[0 ]; // OK - both narrowed after disjunction
345+ (void )other[0 ]; // OK
346+ }
347+
348+ void test_member_subscript_in_loop_with_calls () {
349+ if (arr == nullptr || other == nullptr ) return ;
350+ for (int i = 0 ; i < 3 ; i++) {
351+ (void )arr[i]; // OK - narrowing survives loop back-edge
352+ other[i] = getMember (); // OK - call + subscript in loop
353+ }
354+ }
355+
356+ void test_member_ne_nullptr_if_body_with_loop () {
357+ // if (ptr != nullptr) { loop { ptr[i] } }
358+ if (arr != nullptr ) {
359+ for (int i = 0 ; i < 3 ; i++) {
360+ (void )arr[i]; // OK - narrowed inside if-body, survives loop
361+ }
362+ }
363+ }
364+
365+ void test_member_subscript_with_fn_index () {
366+ // ptr[getIndex()] after null check
367+ if (arr == nullptr ) return ;
368+ (void )arr[getMember ()]; // OK - function call as index doesn't affect base narrowing
369+ }
370+ };
371+
285372// --- sizeof/alignof don't dereference ---
286373
287374void test_fp_sizeof_no_deref (Node * _Nullable p) {
0 commit comments