-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[StaticAnalyzer] Fix state update in VisitObjCForCollectionStmt #124477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| @class NSArray, NSObject; | ||
|
|
||
| @interface Base | ||
| @property (readonly, copy) NSArray *array; | ||
| @end | ||
|
|
||
| #pragma clang assume_nonnull end | ||
| # 8 "this-file.m" 2 | ||
|
|
||
|
|
||
| @interface Test : Base | ||
|
|
||
| @property (readwrite, copy, nullable) NSObject *label; | ||
| @property (readwrite, strong, nullable) Test * field; | ||
|
|
||
| - (void)f; | ||
|
|
||
| @end | ||
|
|
||
| @implementation Test | ||
| - (void)f | ||
| { | ||
| NSObject * X; | ||
|
|
||
| for (NSObject *ele in self.field.array) {} | ||
| self.label = X; | ||
| } | ||
| @end | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To elaborate the issue, suppose
evalLocationalters thePredstate and populates the new states indstLocation. (This namedstLocationis a bit confusing IMO, let's call those new statesNewPredsinstead.) However, theStmtNodeBuilderbelow still takesPredas the source state (old line 131) while the next transitions computed inpopulateObjCForDestinationSetare computed fromNewPreds(old line 134 & 138).The confusion here is that there suppose to be transitions:
Pred --evalLocation--> NewPreds --populateObjCForDestinationSet--> Destbut the
StmtNodeBuilderused for the second transition above assumes the source state isPredinstead ofNewPreds. This is what's wrong with this functionVisitObjCForCollectionStmt.Then how does the bug lead to a crash?
The way
StmtNodeBuilderworks is that it maintains aFrontierset---the set of states to be explored next. After initialization (old line 131),Frontier = {Pred}. InpopulateObjCForDestinationSet,StmtNodeBuilderis used to generateDestand remove source state from the set. Note thatNewPredsare passed topopulateObjCForDestinationSetas source states. SoStmtNodeBuilderattempts to removeNewPredsfrom the set, which does not change the set. Finally,Frontier = {Pred, Dest}.Starting from this ill-formed
Frontierset, the engine executeshasMoreIterationson them. The assertion fails on the statePredbecause it has never been set theObjCForHasMoreIterationsmap.Desthas the map set. This is whatpopulateObjCForDestinationSetdoes. (If assertions are disabled, there is a segmentation fault.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest keeping
dstLocation(or perhaps changing it toDstLocationto align with the global coding style instead of the local tradition), because it specifies that these are the nodes produced by the "Location" step which is is more informative thanNewPreds.In general, these exploded graph node manipulating functions have a nice pattern for naming the node sets:
Functions like this take nodes from the node set
Src(or instead start from a single node, which is usually calledPred) and place the resulting nodes in the outparameterDst. If an operation is implemented as the combination of several lower-level steps (Foo,BarandBazin the example), then it's natural to introduce e.g.DstFooas the set which will act as theDstparameter ofperformFoo(and later as theSrcparameter ofperformBar).If there are multiple intermediate node sets, then it's important to give them descriptive names (and not just
Pred->NewPred->NewNewPredetc.) and as the intermediate set first appears as a destination, it's easier to name it as the "destination of" a certain step. In this functionNewPredscould be acceptable (as it is the only intermediate node set), but I would still prefer using the more informative name.By the way, thanks for the detailed explanation of the issue! Your conclusions and significant changes seem to be correct, so my review is limited to bikeshedding ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. Now the name
dstLocationmakes sense to me.