[clang] [StaticAnalyzer] Fix state update in VisitObjCForCollectionStmt (PR #124477)
Ziqing Luo via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 30 15:18:25 PST 2025
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/124477
>From 1f67a396d917428ff35ca54ecb2d9124c14342de Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 30 Jan 2025 12:24:45 -0800
Subject: [PATCH 1/2] [StaticAnalyzer] Add reproducers for a bug in
VisitObjCForCollectionStmt
The bug (see #124477) in VisitObjCForCollectionStmt seems have been
there for a long time and can be very rarely triggered. Now adding
tests that reproduce it.
The integrated test is reduced from a crash observed downstream that
reproduces the bug.
The unit test is a more straightforward way of showing how was the bug
triggered.
(rdar://143280254)
---
clang/test/Analysis/bugfix-124477.m | 39 ++++++++++++
clang/unittests/StaticAnalyzer/CMakeLists.txt | 1 +
.../StaticAnalyzer/ObjcBug-124477.cpp | 63 +++++++++++++++++++
3 files changed, 103 insertions(+)
create mode 100644 clang/test/Analysis/bugfix-124477.m
create mode 100644 clang/unittests/StaticAnalyzer/ObjcBug-124477.cpp
diff --git a/clang/test/Analysis/bugfix-124477.m b/clang/test/Analysis/bugfix-124477.m
new file mode 100644
index 000000000000000..80820f4c934443d
--- /dev/null
+++ b/clang/test/Analysis/bugfix-124477.m
@@ -0,0 +1,39 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling,nullability.NullableDereferenced,nullability.NullabilityBase -x objective-c %s
+/*
+ This test is reduced from a static analyzer crash. The bug causing
+ the crash is explained in #124477. It can only be triggered in some
+ rare cases so please do not modify this reproducer.
+*/
+
+#pragma clang assume_nonnull begin
+# 15 "some-sys-header.h" 1 3
+ at class NSArray, NSObject;
+
+ at interface Base
+ at property (readonly, copy) NSArray *array;
+ at end
+
+#pragma clang assume_nonnull end
+# 8 "this-file.m" 2
+
+
+ at interface Test : Base
+
+ at property (readwrite, copy, nullable) NSObject *label;
+ at property (readwrite, strong, nullable) Test * field;
+
+- (void)f;
+
+ at end
+
+ at implementation Test
+- (void)f
+{
+ NSObject * X;
+
+ for (NSObject *ele in self.field.array) {}
+ self.label = X;
+}
+ at end
+
+
diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt
index f5da86e5456030f..3b01a4e9e532766 100644
--- a/clang/unittests/StaticAnalyzer/CMakeLists.txt
+++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt
@@ -15,6 +15,7 @@ add_clang_unittest(StaticAnalysisTests
IsCLibraryFunctionTest.cpp
MemRegionDescriptiveNameTest.cpp
NoStateChangeFuncVisitorTest.cpp
+ ObjcBug-124477.cpp
ParamRegionTest.cpp
RangeSetTest.cpp
RegisterCustomCheckersTest.cpp
diff --git a/clang/unittests/StaticAnalyzer/ObjcBug-124477.cpp b/clang/unittests/StaticAnalyzer/ObjcBug-124477.cpp
new file mode 100644
index 000000000000000..51bd33210032c70
--- /dev/null
+++ b/clang/unittests/StaticAnalyzer/ObjcBug-124477.cpp
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
+#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace ento;
+
+// Some dummy trait that we can mutate back and forth to force a new State.
+REGISTER_TRAIT_WITH_PROGRAMSTATE(Flag, bool)
+
+namespace {
+class FlipFlagOnCheckLocation : public Checker<check::Location> {
+public:
+ // We make sure we alter the State every time we model a checkLocation event.
+ void checkLocation(SVal l, bool isLoad, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ State = State->set<Flag>(!State->get<Flag>());
+ C.addTransition(State);
+ }
+};
+
+void addFlagFlipperChecker(AnalysisASTConsumer &AnalysisConsumer,
+ AnalyzerOptions &AnOpts) {
+ AnOpts.CheckersAndPackages = {{"test.FlipFlagOnCheckLocation", true}};
+ AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
+ Registry.addChecker<FlipFlagOnCheckLocation>("test.FlipFlagOnCheckLocation",
+ "Description", "");
+ });
+}
+
+TEST(ObjCTest, CheckLocationEventsShouldMaterializeInObjCForCollectionStmts) {
+ // Previously, the `ExprEngine::hasMoreIteration` may fired an assertion
+ // because we forgot to handle correctly the resulting nodes of the
+ // check::Location callback for the ObjCForCollectionStmts.
+ // This caused inconsistencies in the graph and triggering the assertion.
+ // See #124477 for more details.
+ std::string Diags;
+ EXPECT_TRUE(runCheckerOnCodeWithArgs<addFlagFlipperChecker>(
+ R"(
+ @class NSArray, NSDictionary, NSString;
+ extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
+ void entrypoint(NSArray *bowl) {
+ for (NSString *fruit in bowl) { // no-crash
+ NSLog(@"Fruit: %@", fruit);
+ }
+ })",
+ {"-x", "objective-c"}, Diags));
+}
+
+} // namespace
>From 730082e31af715196f4e0b1358489c665aa3a207 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Sun, 26 Jan 2025 10:54:49 -0800
Subject: [PATCH 2/2] [StaticAnalyzer] Fix state update in
VisitObjCForCollectionStmt
In `VisitObjCForCollectionStmt`, the function does `evalLocation` for
the current element at the original source state `Pred`. The
evaluation may result in a new state, say `PredNew`. I.e., there is a
transition: `Pred -> PredNew`, though it is a very rare case that
`Pred` is NOT identical to `PredNew`. (This explains why the bug
exists for many years but no one noticed until recently a crash
observed downstream.) Later, the original code does NOT use `PredNew`
as the new source state in `StmtNodeBuilder` for next transitions. In
cases `Pred != PredNew`, the program ill behaves.
(#124477)
(rdar://143280254)
---
.../StaticAnalyzer/Core/ExprEngineObjC.cpp | 30 ++++++++++---------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index f075df3ab5e4d60..9426e0afd65a046 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -124,24 +124,26 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,
bool isContainerNull = state->isNull(collectionV).isConstrainedTrue();
- ExplodedNodeSet dstLocation;
- evalLocation(dstLocation, S, elem, Pred, state, elementV, false);
+ ExplodedNodeSet DstLocation; // states in `DstLocation` may differ from `Pred`
+ evalLocation(DstLocation, S, elem, Pred, state, elementV, false);
- ExplodedNodeSet Tmp;
- StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
+ for (ExplodedNode *dstLocation : DstLocation) {
+ ExplodedNodeSet DstLocationSingleton{dstLocation}, Tmp;
+ StmtNodeBuilder Bldr(dstLocation, Tmp, *currBldrCtx);
- if (!isContainerNull)
- populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
- SymMgr, currBldrCtx, Bldr,
- /*hasElements=*/true);
+ if (!isContainerNull)
+ populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elem,
+ elementV, SymMgr, currBldrCtx, Bldr,
+ /*hasElements=*/true);
- populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
- SymMgr, currBldrCtx, Bldr,
- /*hasElements=*/false);
+ populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elem,
+ elementV, SymMgr, currBldrCtx, Bldr,
+ /*hasElements=*/false);
- // Finally, run any custom checkers.
- // FIXME: Eventually all pre- and post-checks should live in VisitStmt.
- getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
+ // Finally, run any custom checkers.
+ // FIXME: Eventually all pre- and post-checks should live in VisitStmt.
+ getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
+ }
}
void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
More information about the cfe-commits
mailing list