Clang static analyzer finding impossible use-after-free issues with refcounted data |
|||||
Issue description
The static analyzer is confused by refptrs. The analysis code path object is evaluating a hypothetical case in which the refcounted object is deleted before it is dereferenced. This is impossible; ref counting provides guarantees against that.
Build error:
In file included from ../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.h:9:
../../base/memory/ref_counted.h:485:5: warning: Use of memory after it is freed
return ptr_;
^
../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp:98:7: note: Left side of '&&' is true
if (!current_surface_id_.is_valid() && surface_info.is_valid()) {
^
../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp:98:3: note: Taking true branch
if (!current_surface_id_.is_valid() && surface_info.is_valid()) {
^
../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp:115:3: note: Calling '~scoped_refptr'
} else if (current_surface_id_ != surface_info.id()) {
^
../../base/memory/ref_counted.h:472:5: note: Taking true branch
if (ptr_)
^
../../base/memory/ref_counted.h:473:7: note: Calling 'scoped_refptr::Release'
Release(ptr_);
^~~~~~~~~~~~~
../../base/memory/ref_counted.h:572:3: note: Calling 'RefCounted::Release'
ptr->Release();
^~~~~~~~~~~~~~
../../base/memory/ref_counted.h:252:9: note: Assuming the condition is true
if (subtle::RefCountedBase::Release()) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../base/memory/ref_counted.h:252:5: note: Taking true branch
if (subtle::RefCountedBase::Release()) {
^
../../base/memory/ref_counted.h:253:7: note: Memory is released
delete static_cast<const T*>(this);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../base/memory/ref_counted.h:572:3: note: Returning; memory was released
ptr->Release();
^~~~~~~~~~~~~~
../../base/memory/ref_counted.h:473:7: note: Returning; memory was released via 1st parameter
Release(ptr_);
^~~~~~~~~~~~~
../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp:115:3: note: Returning from '~scoped_refptr'
} else if (current_surface_id_ != surface_info.id()) {
^
../../third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp:126:3: note: Calling 'scoped_refptr::operator->'
cc_layer_->SetBounds(surface_info.size_in_pixels());
^~~~~~~~~
../../base/memory/ref_counted.h:485:5: note: Use of memory after it is freed
return ptr_;
^ ~~~~
1 warning generated.
,
Jun 12 2017
That cl looks unrelated; mispaste?
,
Jun 12 2017
Sorry, probably jumped to a conclusion there. That was the last clang static analysis change that has come to my attention and the issue was filed in this component (which I triage). Perhaps this should be re-routed?
,
Jun 12 2017
kmarshall set up the static analyzer build; I'm guessing he filed this for himself and just applied this label since it looks related. Let's just mark in Available instead of Untriaged.
,
Jun 13 2017
That's correct. Sorry, is there a more appropriate component that I should use instead?
,
Jun 14 2017
Makes sense to put static analysis related things in here, so let's leave it here.
,
Jun 14 2017
(ajwong, dcheng because this might be a fun puzzle.) I think #1 can be cleaned up by putting ANALYZER_ASSUME_TRUE(false) in RefCountedBase's deletion logic. There's no point in following codepaths which simulate the deletion and dereferencing of data. It's impossible, the refcounting system specifically prevents that. See https://chromium-review.googlesource.com/535382 for a WIP fix. What is less clear-cut is how to clean up a similar warning for std::move(). We implement move assignment by swapping the contents of |this| with a temporary scoped_refptr. The static analyzer is unaware that the moved object that receives the movee's ptr_ is deleted post-move, hence the warning that its contents have leaked. ../../base/memory/ref_counted.h:515:5: warning: Potential leak of memory pointed to by field 'ptr_' return *this; ^ ../../base/memory/ref_counted_unittest.cc:390:35: note: Memory is allocated ScopedRefPtrCountBase *raw1 = new ScopedRefPtrCountBase(); ^~~~~~~~~~~~~~~~~~~~~~~~~~~ ../../base/memory/ref_counted_unittest.cc:401:7: note: Calling move assignment operator for 'scoped_refptr' p1 = std::move(p2); ^~~~~~~~~~~~~~~~~~ ../../base/memory/ref_counted.h:515:5: note: Potential leak of memory pointed to by field 'ptr_' return *this; ^
,
Jun 14 2017
huh... is there a reason to do swap() here in liue of having a (possibly private) release function? Also, I have no context for how the static analyzer works...but how is this distinction different from handling normal RAII in static analysis?
,
Jun 15 2017
IIUC ref-counting will mean that the analyzer gets confused in two ways: 1. It can't be sure that we don't delete early, and use-after-free the object. 2. It can't be sure that we *ever* delete the object, since deletion of the container only corresponds to a dec-ref call, not to concrete deletion. (1) is the issue that your WIP patch from #7 addresses, I think? (2) seems to require that the static analyzer can effective confirm that there are as many dec-refs as inc-refs, which isn't possible in general, so perhaps we just need to annotate to disable leak analysis on the move path?
,
Jun 15 2017
Disabling all analysis on the move path would prune a lot of valid paths too. I think we might be able to rewrite the assignment in a slightly more verbose way that allows us to prune just the ptr_ "leak" case?
,
Jun 15 2017
OK I figured out why it's written this way.
This was a clever way of making sure the originally stored pointer's lifetime isn't extended. If it was just a simple pointer swap:
scoped_refptr& operator=(scoped_refptr&& r) {
swap(r);
return *this;
}
then the lifetime would be unexpectedly extended in an example like this:
void MyClass::StealPointer(std::vector<scoped_refptr<T>>* v) {
// With a simple swap, |stolen_ptr_|'s original pointer's refcount
// isn't released until the vector goes out of scope!
stolen_ptr_ = std::move(v[0]);
}
That being said, this feels excessively subtle. We should probably just write it out explicitly like we do for copy assignment.
,
Jun 21 2017
The following revision refers to this bug: https://chromium.googlesource.com/chromium/src.git/+/7273eddad8f51440a56c6305cddc8be484c3328f commit 7273eddad8f51440a56c6305cddc8be484c3328f Author: Kevin Marshall <kmarshall@chromium.org> Date: Wed Jun 21 18:50:40 2017 Fix static analyzer noise in ref_counted.h * Bad use-after-free warning: add static analysis directive to Release(), preventing the analyzer from following the deletion codepath. * Bad pointer warning: swapping with a named variable instead of a temporary object fixes a bogus pointer leak error. Bug: 731917 Change-Id: I2d495efa61b2b2bc30448dc79a4ecdc3a595f721 Reviewed-on: https://chromium-review.googlesource.com/535382 Commit-Queue: Kevin Marshall <kmarshall@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Reviewed-by: Wez <wez@chromium.org> Cr-Commit-Position: refs/heads/master@{#481261} [modify] https://crrev.com/7273eddad8f51440a56c6305cddc8be484c3328f/base/logging.h [modify] https://crrev.com/7273eddad8f51440a56c6305cddc8be484c3328f/base/memory/ref_counted.h [modify] https://crrev.com/7273eddad8f51440a56c6305cddc8be484c3328f/base/memory/ref_counted_unittest.cc
,
Jun 22 2018
This issue has been Available for over a year. If it's no longer important or seems unlikely to be fixed, please consider closing it out. If it is important, please re-triage the issue. Sorry for the inconvenience if the bug really should have been left as Available. For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot |
|||||
►
Sign in to add a comment |
|||||
Comment 1 by emso@chromium.org
, Jun 12 2017Status: Assigned (was: Untriaged)