-
Notifications
You must be signed in to change notification settings - Fork 14.1k
[Clang] Fix potential null pointer dereferences in Sema::AddInitializerToDecl #94368
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
Conversation
…erToDecl This patch adds null check for 'Init' before dereferencing it to prevent potential null pointer dereferences reported by static Analyzer tool in the function.
@llvm/pr-subscribers-clang Author: None (smanna12) ChangesThis patch adds null check for 'Init' before dereferencing it to prevent potential null pointer dereferences reported by static Analyzer tool in the function. Full diff: https://github.com/llvm/llvm-project/pull/94368.diff 1 Files Affected:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 34e46e12859bb..cd50df646b8b2 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -13728,7 +13728,7 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
// paths through the function. This should be revisited if
// -Wrepeated-use-of-weak is made flow-sensitive.
if (FunctionScopeInfo *FSI = getCurFunction())
- if ((VDecl->getType().getObjCLifetime() == Qualifiers::OCL_Strong ||
+ if (Init && (VDecl->getType().getObjCLifetime() == Qualifiers::OCL_Strong ||
VDecl->getType().isNonWeakInMRRWithObjCWeak(Context)) &&
!Diags.isIgnored(diag::warn_arc_repeated_use_of_weak,
Init->getBeginLoc()))
|
You can test this locally with the following command:git-clang-format --diff 2bc098b8aba089fe8328b3b8a8b6b6816cd5a908 ea34dff36b21d44411aa735aff577c696260c9d6 --extensions cpp -- clang/lib/Sema/SemaDecl.cpp View the diff from clang-format here.diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 3073e754908..d277457327f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -13682,7 +13682,8 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
Init = Result.getAs<Expr>();
assert((Init || InitSeq.steps().empty()) &&
- "Should have a valid initializer or no initialization steps at this point");
+ "Should have a valid initializer or no initialization steps at this "
+ "point");
IsParenListInit = !InitSeq.steps().empty() &&
InitSeq.step_begin()->Kind ==
|
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.
Why add the check here rather than add an assertion at the point of assignation (i.e.
Init = Result.getAs<Expr>();
Since Init is passed into CheckSelfReference on line 13712 where it's dereferenced the same issue binds there as well
Thanks @feg208 for reviews! I agree with you. I missed the case you mentioned and fixed the patch. |
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.
ltgm
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 @smanna12. I'm uncertain about this change; see my additional comment.
clang/lib/Sema/SemaDecl.cpp
Outdated
assert(Init && "Init must not be null"); | ||
|
||
IsParenListInit = !InitSeq.steps().empty() && | ||
InitSeq.step_begin()->Kind == | ||
InitializationSequence::SK_ParenthesizedListInit; | ||
QualType VDeclType = VDecl->getType(); | ||
if (Init && !Init->getType().isNull() && | ||
!Init->getType()->isDependentType() && !VDeclType->isDependentType() && | ||
if (!Init->getType()->isDependentType() && !VDeclType->isDependentType() && |
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'm uncertain about this change. On the one hand, the assignment to Init
looks to me like it must produce a non-null result due to the prior check to Result.isInvalid()
. However, the following uses of Init
were already guarded by a check for a non-null value, so the static analysis tool should not have complained about those.
Was the static analysis tool perhaps complaining about later uses of Init
? Note that the assignment at line 13683 above is conditional (on !VDecl->isInvalidDecl()
) and therefore might not suffice to ensure a definite non-null value. I haven't checked exhaustively if that is the case though.
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.
Was the static analysis tool perhaps complaining about later uses of
Init
?
Yes. FWIW Here is the logic:
if (!VDecl->isInvalidDecl()) {
ExprResult Result = InitSeq.Perform(*this, Entity, Kind, Args, &DclT);
if (Result.isInvalid()) {
return
}
Init = Result.getAs<Expr>();
if (Init && !Init->getType().isNull() && // verifier expects Init can be null.
}
…
if (!VDecl->isInvalidDecl()) {
.. Init->getBeginLoc())) // Deref of Init without check
}
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.
Do you have any test that crashes without this change?
If there are cases where init could in fact be null, and that causes actual issue, we should add a test for it.
An alternative change would be to check for Init
there but then remove subsequent, redundant checks of Init being set (again, assuming it does not regress recovery and diagnostics).
In general we should be careful not to add redundant checks just because a static analyzer gets confused, especially if there is the opportunity to do a more thorough cleanup.
Thanks!
I don't think the currently proposed change is right for a few reasons. The proposed change silently marks the declaration as invalid and performs an early return. I don't think this should be a silent behavior. In most, if not all, of the existing early return cases, a diagnostic is issued (perhaps via another called function) and/or a recovery expression is created and used as the initializer. I think this prior statement of mine is incorrect:
It is possible for an
This indicates that, absent a more complicated analysis that proves that I suggest adding an assert here:
Relatedly, I think the two checks for
I think the null check at line 13507 can just be removed; previous uses of The null check at line 13715 also looks like it was added as a reflexive defensive check (added in https://reviews.llvm.org/D158615 for #37257) Adding the assert and removing the defensive null checks would eliminate the evidence the static analysis tool is using to justify its report of a potential null dereference. If Clang tests pass with these changes (using an asserts enabled build of course!), then I think we would be ok to proceed. If we then see failed assertions or null pointer dereferences at some point, we'll be able to identify test cases to add. |
The removal of the null check on
|
Thanks, @smanna12. That is interesting as it implies that the call to
|
Thanks @tahonermann for reviews!. Yes, it is coming from InitializationSequence::Perform() |
This patch