Skip to content

Commit 536606f

Browse files
authored
[StaticAnalyzer] Fix state update in VisitObjCForCollectionStmt (#124477)
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. (rdar://143280254)
1 parent bce2cc1 commit 536606f

File tree

4 files changed

+119
-14
lines changed

4 files changed

+119
-14
lines changed

clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,24 +124,26 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,
124124

125125
bool isContainerNull = state->isNull(collectionV).isConstrainedTrue();
126126

127-
ExplodedNodeSet dstLocation;
128-
evalLocation(dstLocation, S, elem, Pred, state, elementV, false);
127+
ExplodedNodeSet DstLocation; // states in `DstLocation` may differ from `Pred`
128+
evalLocation(DstLocation, S, elem, Pred, state, elementV, false);
129129

130-
ExplodedNodeSet Tmp;
131-
StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
130+
for (ExplodedNode *dstLocation : DstLocation) {
131+
ExplodedNodeSet DstLocationSingleton{dstLocation}, Tmp;
132+
StmtNodeBuilder Bldr(dstLocation, Tmp, *currBldrCtx);
132133

133-
if (!isContainerNull)
134-
populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
135-
SymMgr, currBldrCtx, Bldr,
136-
/*hasElements=*/true);
134+
if (!isContainerNull)
135+
populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elem,
136+
elementV, SymMgr, currBldrCtx, Bldr,
137+
/*hasElements=*/true);
137138

138-
populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
139-
SymMgr, currBldrCtx, Bldr,
140-
/*hasElements=*/false);
139+
populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elem,
140+
elementV, SymMgr, currBldrCtx, Bldr,
141+
/*hasElements=*/false);
141142

142-
// Finally, run any custom checkers.
143-
// FIXME: Eventually all pre- and post-checks should live in VisitStmt.
144-
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
143+
// Finally, run any custom checkers.
144+
// FIXME: Eventually all pre- and post-checks should live in VisitStmt.
145+
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
146+
}
145147
}
146148

147149
void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling,nullability.NullableDereferenced,nullability.NullabilityBase -x objective-c %s
2+
/*
3+
This test is reduced from a static analyzer crash. The bug causing
4+
the crash is explained in #124477. It can only be triggered in some
5+
rare cases so please do not modify this reproducer.
6+
*/
7+
8+
#pragma clang assume_nonnull begin
9+
# 15 "some-sys-header.h" 1 3
10+
@class NSArray, NSObject;
11+
12+
@interface Base
13+
@property (readonly, copy) NSArray *array;
14+
@end
15+
16+
#pragma clang assume_nonnull end
17+
# 8 "this-file.m" 2
18+
19+
20+
@interface Test : Base
21+
22+
@property (readwrite, copy, nullable) NSObject *label;
23+
@property (readwrite, strong, nullable) Test * field;
24+
25+
- (void)f;
26+
27+
@end
28+
29+
@implementation Test
30+
- (void)f
31+
{
32+
NSObject * X;
33+
34+
for (NSObject *ele in self.field.array) {}
35+
self.label = X;
36+
}
37+
@end
38+
39+

clang/unittests/StaticAnalyzer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_clang_unittest(StaticAnalysisTests
1515
IsCLibraryFunctionTest.cpp
1616
MemRegionDescriptiveNameTest.cpp
1717
NoStateChangeFuncVisitorTest.cpp
18+
ObjcBug-124477.cpp
1819
ParamRegionTest.cpp
1920
RangeSetTest.cpp
2021
RegisterCustomCheckersTest.cpp
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "CheckerRegistration.h"
10+
#include "clang/StaticAnalyzer/Core/Checker.h"
11+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
12+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
13+
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
14+
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
15+
#include "gtest/gtest.h"
16+
17+
using namespace clang;
18+
using namespace ento;
19+
20+
// Some dummy trait that we can mutate back and forth to force a new State.
21+
REGISTER_TRAIT_WITH_PROGRAMSTATE(Flag, bool)
22+
23+
namespace {
24+
class FlipFlagOnCheckLocation : public Checker<check::Location> {
25+
public:
26+
// We make sure we alter the State every time we model a checkLocation event.
27+
void checkLocation(SVal l, bool isLoad, const Stmt *S,
28+
CheckerContext &C) const {
29+
ProgramStateRef State = C.getState();
30+
State = State->set<Flag>(!State->get<Flag>());
31+
C.addTransition(State);
32+
}
33+
};
34+
35+
void addFlagFlipperChecker(AnalysisASTConsumer &AnalysisConsumer,
36+
AnalyzerOptions &AnOpts) {
37+
AnOpts.CheckersAndPackages = {{"test.FlipFlagOnCheckLocation", true}};
38+
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
39+
Registry.addChecker<FlipFlagOnCheckLocation>("test.FlipFlagOnCheckLocation",
40+
"Description", "");
41+
});
42+
}
43+
44+
TEST(ObjCTest, CheckLocationEventsShouldMaterializeInObjCForCollectionStmts) {
45+
// Previously, the `ExprEngine::hasMoreIteration` may fired an assertion
46+
// because we forgot to handle correctly the resulting nodes of the
47+
// check::Location callback for the ObjCForCollectionStmts.
48+
// This caused inconsistencies in the graph and triggering the assertion.
49+
// See #124477 for more details.
50+
std::string Diags;
51+
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFlagFlipperChecker>(
52+
R"(
53+
@class NSArray, NSDictionary, NSString;
54+
extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
55+
void entrypoint(NSArray *bowl) {
56+
for (NSString *fruit in bowl) { // no-crash
57+
NSLog(@"Fruit: %@", fruit);
58+
}
59+
})",
60+
{"-x", "objective-c"}, Diags));
61+
}
62+
63+
} // namespace

0 commit comments

Comments
 (0)