Skip to content

Commit 357ae33

Browse files
committed
check member narrowing in array subscripts and checkExprDeref
1 parent 99e4cf6 commit 357ae33

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

clang/lib/Analysis/FlowNullability.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,19 @@ class TransferFunctions {
589589
if (UO->getOpcode() == UO_AddrOf)
590590
return;
591591

592+
// Check member narrowing: this->member or var.member
593+
if (const auto *ME = dyn_cast<MemberExpr>(Origin)) {
594+
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
595+
const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
596+
if (isa<CXXThisExpr>(Base) && isThisMemberNarrowed(FD))
597+
return;
598+
if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base))
599+
if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl()))
600+
if (isMemberNarrowed(BaseVD, FD))
601+
return;
602+
}
603+
}
604+
592605
QualType CheckTy = FoundCast ? Origin->getType() : PtrExpr->getType();
593606
checkDeref(DerefExpr, CheckTy);
594607
}
@@ -1098,6 +1111,11 @@ class TransferFunctions {
10981111
if (!isNarrowed(VD) && !VD->getType()->isArrayType())
10991112
checkVarDeref(ASE, VD);
11001113
}
1114+
} else if (const auto *ME = dyn_cast<MemberExpr>(Base)) {
1115+
// Member pointer subscript: this->arr[i] or var.arr[i]
1116+
// Check member narrowing before falling through to type check.
1117+
if (!ME->getType()->isArrayType())
1118+
checkMemberExprDeref(ASE, ME);
11011119
} else {
11021120
QualType BaseTy = Base->getType();
11031121
if (!BaseTy->isArrayType())

clang/test/SemaCXX/flow-nullability-adoption.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

287374
void test_fp_sizeof_no_deref(Node * _Nullable p) {

0 commit comments

Comments
 (0)