Commit 72d7b437 authored by Jay Civelli's avatar Jay Civelli Committed by Commit Bot

Making ImageSanitizer less memory leak prone.

ImageSanitizer now clears its callbacks when finished to prevent holding
on to other objects and causing memory leaks.

Bug: 800540
Change-Id: Ifcc4f403ee32df6819d906744c07a1f6bd0684ae
Reviewed-on: https://chromium-review.googlesource.com/887104
Commit-Queue: Jay Civelli <jcivelli@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Cr-Commit-Position: refs/heads/master@{#531970}
parent 7413e93c
...@@ -215,6 +215,10 @@ void ImageSanitizer::ReportError(Status status, const base::FilePath& path) { ...@@ -215,6 +215,10 @@ void ImageSanitizer::ReportError(Status status, const base::FilePath& path) {
void ImageSanitizer::CleanUp() { void ImageSanitizer::CleanUp() {
image_decoder_ptr_.reset(); image_decoder_ptr_.reset();
// It's important to clear the repeating callback as it may cause a circular
// reference (the callback holds a ref to an object that has a ref to |this|)
// that would cause a leak.
image_decoded_callback_.Reset();
} }
} // namespace extensions } // namespace extensions
\ No newline at end of file
...@@ -11,8 +11,10 @@ ...@@ -11,8 +11,10 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/test_browser_thread_bundle.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h" #include "services/data_decoder/public/cpp/test_data_decoder_service.h"
...@@ -30,6 +32,36 @@ constexpr char kBase64edInvalidPng[] = ...@@ -30,6 +32,36 @@ constexpr char kBase64edInvalidPng[] =
"Rw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd" "Rw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd"
"1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"; "1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC";
class RefCountedSanitizerCallback
: public base::RefCountedThreadSafe<RefCountedSanitizerCallback> {
public:
explicit RefCountedSanitizerCallback(bool* deleted) : deleted_(deleted) {}
void ImageSanitizationDone(ImageSanitizer::Status status,
const base::FilePath& path) {
if (done_callback_)
std::move(done_callback_).Run();
}
void ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) {}
void WaitForSanitizationDone() {
ASSERT_FALSE(done_callback_);
base::RunLoop run_loop;
done_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
friend class base::RefCountedThreadSafe<RefCountedSanitizerCallback>;
~RefCountedSanitizerCallback() {
if (deleted_)
*deleted_ = true;
}
base::OnceClosure done_callback_;
bool* deleted_;
};
class ImageSanitizerTest : public testing::Test { class ImageSanitizerTest : public testing::Test {
public: public:
ImageSanitizerTest() ImageSanitizerTest()
...@@ -55,15 +87,33 @@ class ImageSanitizerTest : public testing::Test { ...@@ -55,15 +87,33 @@ class ImageSanitizerTest : public testing::Test {
void CreateAndStartSanitizer( void CreateAndStartSanitizer(
const std::set<base::FilePath>& image_relative_paths) { const std::set<base::FilePath>& image_relative_paths) {
sanitizer_ = ImageSanitizer::CreateAndStart( CreateAndStartSanitizerWithCallbacks(
test_data_decoder_service_.connector(), service_manager::Identity(), image_relative_paths,
temp_dir_.GetPath(), image_relative_paths,
base::BindRepeating(&ImageSanitizerTest::ImageSanitizerDecodedImage, base::BindRepeating(&ImageSanitizerTest::ImageSanitizerDecodedImage,
base::Unretained(this)), base::Unretained(this)),
base::BindOnce(&ImageSanitizerTest::ImageSanitizationDone, base::BindOnce(&ImageSanitizerTest::ImageSanitizationDone,
base::Unretained(this))); base::Unretained(this)));
} }
void TestDontHoldOnToCallback(const base::FilePath& image_path) {
bool callback_deleted = false;
scoped_refptr<RefCountedSanitizerCallback> sanitizer_callback =
base::MakeRefCounted<RefCountedSanitizerCallback>(&callback_deleted);
CreateAndStartSanitizerWithCallbacks(
{image_path},
base::BindRepeating(
&RefCountedSanitizerCallback::ImageSanitizerDecodedImage,
sanitizer_callback),
base::BindOnce(&RefCountedSanitizerCallback::ImageSanitizationDone,
sanitizer_callback));
sanitizer_callback->WaitForSanitizationDone();
sanitizer_callback = nullptr;
// The sanitizer should have cleared its reference to |sanitizer_callback|.
EXPECT_TRUE(callback_deleted);
}
ImageSanitizer::Status last_reported_status() const { return last_status_; } ImageSanitizer::Status last_reported_status() const { return last_status_; }
const base::FilePath& last_reported_path() const { const base::FilePath& last_reported_path() const {
...@@ -74,7 +124,25 @@ class ImageSanitizerTest : public testing::Test { ...@@ -74,7 +124,25 @@ class ImageSanitizerTest : public testing::Test {
return &decoded_images_; return &decoded_images_;
} }
void ClearSanitizer() { sanitizer_.reset(); }
bool done_callback_called() const { return done_callback_called_; }
bool decoded_image_callback_called() const {
return decoded_image_callback_called_;
}
private: private:
void CreateAndStartSanitizerWithCallbacks(
const std::set<base::FilePath>& image_relative_paths,
ImageSanitizer::ImageDecodedCallback image_decoded_callback,
ImageSanitizer::SanitizationDoneCallback done_callback) {
sanitizer_ = ImageSanitizer::CreateAndStart(
test_data_decoder_service_.connector(), service_manager::Identity(),
temp_dir_.GetPath(), image_relative_paths,
std::move(image_decoded_callback), std::move(done_callback));
}
bool WriteBase64DataToFile(const std::string& base64_data, bool WriteBase64DataToFile(const std::string& base64_data,
const base::FilePath::StringPieceType& file_name) { const base::FilePath::StringPieceType& file_name) {
std::string binary; std::string binary;
...@@ -89,6 +157,7 @@ class ImageSanitizerTest : public testing::Test { ...@@ -89,6 +157,7 @@ class ImageSanitizerTest : public testing::Test {
void ImageSanitizationDone(ImageSanitizer::Status status, void ImageSanitizationDone(ImageSanitizer::Status status,
const base::FilePath& path) { const base::FilePath& path) {
done_callback_called_ = true;
last_status_ = status; last_status_ = status;
last_reported_path_ = path; last_reported_path_ = path;
if (done_callback_) if (done_callback_)
...@@ -98,16 +167,19 @@ class ImageSanitizerTest : public testing::Test { ...@@ -98,16 +167,19 @@ class ImageSanitizerTest : public testing::Test {
void ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) { void ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) {
EXPECT_EQ(decoded_images()->find(path), decoded_images()->end()); EXPECT_EQ(decoded_images()->find(path), decoded_images()->end());
(*decoded_images())[path] = image; (*decoded_images())[path] = image;
decoded_image_callback_called_ = true;
} }
content::TestBrowserThreadBundle thread_bundle_; content::TestBrowserThreadBundle thread_bundle_;
data_decoder::TestDataDecoderService test_data_decoder_service_; data_decoder::TestDataDecoderService test_data_decoder_service_;
ImageSanitizer::Status last_status_; ImageSanitizer::Status last_status_ = ImageSanitizer::Status::kSuccess;
base::FilePath last_reported_path_; base::FilePath last_reported_path_;
base::OnceClosure done_callback_; base::OnceClosure done_callback_;
std::unique_ptr<ImageSanitizer> sanitizer_; std::unique_ptr<ImageSanitizer> sanitizer_;
base::ScopedTempDir temp_dir_; base::ScopedTempDir temp_dir_;
std::map<base::FilePath, SkBitmap> decoded_images_; std::map<base::FilePath, SkBitmap> decoded_images_;
bool done_callback_called_ = false;
bool decoded_image_callback_called_ = false;
DISALLOW_COPY_AND_ASSIGN(ImageSanitizerTest); DISALLOW_COPY_AND_ASSIGN(ImageSanitizerTest);
}; };
...@@ -117,7 +189,9 @@ class ImageSanitizerTest : public testing::Test { ...@@ -117,7 +189,9 @@ class ImageSanitizerTest : public testing::Test {
TEST_F(ImageSanitizerTest, NoImagesProvided) { TEST_F(ImageSanitizerTest, NoImagesProvided) {
CreateAndStartSanitizer(std::set<base::FilePath>()); CreateAndStartSanitizer(std::set<base::FilePath>());
WaitForSanitizationDone(); WaitForSanitizationDone();
EXPECT_TRUE(done_callback_called());
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess); EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess);
EXPECT_FALSE(decoded_image_callback_called());
EXPECT_TRUE(last_reported_path().empty()); EXPECT_TRUE(last_reported_path().empty());
} }
...@@ -162,6 +236,7 @@ TEST_F(ImageSanitizerTest, ValidCase) { ...@@ -162,6 +236,7 @@ TEST_F(ImageSanitizerTest, ValidCase) {
} }
CreateAndStartSanitizer(paths); CreateAndStartSanitizer(paths);
WaitForSanitizationDone(); WaitForSanitizationDone();
EXPECT_TRUE(done_callback_called());
EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess); EXPECT_EQ(last_reported_status(), ImageSanitizer::Status::kSuccess);
EXPECT_TRUE(last_reported_path().empty()); EXPECT_TRUE(last_reported_path().empty());
// Make sure the image files are there and non empty, and that the // Make sure the image files are there and non empty, and that the
...@@ -208,4 +283,39 @@ TEST_F(ImageSanitizerTest, InvalidImage) { ...@@ -208,4 +283,39 @@ TEST_F(ImageSanitizerTest, InvalidImage) {
EXPECT_EQ(last_reported_path(), bad_png); EXPECT_EQ(last_reported_path(), bad_png);
} }
TEST_F(ImageSanitizerTest, NoCallbackAfterDelete) {
constexpr base::FilePath::CharType kBadPngName[] =
FILE_PATH_LITERAL("bad.png");
CreateInvalidImage(kBadPngName);
base::FilePath bad_png(kBadPngName);
CreateAndStartSanitizer({bad_png});
// Delete the sanitizer before we have received the callback.
ClearSanitizer();
// Wait a bit and ensure no callback has been called.
base::RunLoop run_loop;
base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(200));
run_loop.Run();
EXPECT_FALSE(done_callback_called());
EXPECT_FALSE(decoded_image_callback_called());
}
// Ensures the sanitizer does not keep a reference to the callbacks to prevent
// memory leaks. (it's typical to have a ref counted object A own an
// ImageSanitizer which is given callbacks bound to A, creating a circular
// reference)
TEST_F(ImageSanitizerTest, DontHoldOnToCallbacksOnFailure) {
constexpr base::FilePath::CharType kBadPngName[] =
FILE_PATH_LITERAL("bad.png");
CreateInvalidImage(kBadPngName);
TestDontHoldOnToCallback(base::FilePath(kBadPngName));
}
TEST_F(ImageSanitizerTest, DontHoldOnToCallbacksOnSuccess) {
constexpr base::FilePath::CharType kGoodPngName[] =
FILE_PATH_LITERAL("good.png");
CreateValidImage(kGoodPngName);
TestDontHoldOnToCallback(base::FilePath(kGoodPngName));
}
} // namespace extensions } // namespace extensions
...@@ -549,12 +549,6 @@ void SandboxedUnpacker::ImageSanitizationDone( ...@@ -549,12 +549,6 @@ void SandboxedUnpacker::ImageSanitizationDone(
std::unique_ptr<base::DictionaryValue> manifest, std::unique_ptr<base::DictionaryValue> manifest,
ImageSanitizer::Status status, ImageSanitizer::Status status,
const base::FilePath& file_path_for_error) { const base::FilePath& file_path_for_error) {
// Release |image_sanitizer_|, it is not useful anymore and reference |this|.
// (keep a local ref to this so if |image_sanitizer_| was the last ref to
// |this| it stays valid for the duration of the call).
scoped_refptr<SandboxedUnpacker> keep_this_alive = this;
image_sanitizer_ = nullptr;
if (status == ImageSanitizer::Status::kSuccess) { if (status == ImageSanitizer::Status::kSuccess) {
// Next step is to sanitize the message catalogs. // Next step is to sanitize the message catalogs.
ReadMessageCatalogs(std::move(manifest)); ReadMessageCatalogs(std::move(manifest));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment