buffet: Fix crash when CommandInstance::SetResults() is invoked The crash was a result of use-after-delete in CloudCommandProxy::OnResultsChanged() where TypedValueToJson returned std::unique_ptr while the JSON value was set to Dictionary by using unique_ptr::get() on the temporary. Should have called release() instead to transfer the ownership to the dictionary. Added a unit test to buffet/device_registration_info_unittest.cc to test this specific scenario. BUG=brillo:770 TEST=`FEATURES=test emerge-link buffet` Change-Id: I34a28ce610a06f2b77af1cc75e358b3fb3562a13 Reviewed-on: https://chromium-review.googlesource.com/264465 Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org> Tested-by: Alex Vakulenko <avakulenko@chromium.org> Reviewed-by: Vitaly Buka <vitalybuka@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc index 91dc8b1..6136eeb 100644 --- a/buffet/device_registration_info_unittest.cc +++ b/buffet/device_registration_info_unittest.cc
@@ -5,6 +5,7 @@ #include "buffet/device_registration_info.h" #include <base/json/json_reader.h> +#include <base/json/json_writer.h> #include <base/values.h> #include <chromeos/bind_lambda.h> #include <chromeos/http/http_request.h> @@ -179,6 +180,10 @@ static bool Save(DeviceRegistrationInfo* info) { return info->Save(); } + static void PublishCommands(DeviceRegistrationInfo* info, + const base::ListValue& commands) { + return info->PublishCommands(commands); + } }; class DeviceRegistrationInfoTest : public ::testing::Test { @@ -188,7 +193,7 @@ storage_ = std::make_shared<MemStorage>(); storage_->Save(&data_); transport_ = std::make_shared<chromeos::http::fake::Transport>(); - command_manager_ = std::make_shared<CommandManager>(); + command_manager_ = std::make_shared<CommandManager>(nullptr); state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_); chromeos::KeyValueStore config_store; config_store.SetString("client_id", test_data::kClientId); @@ -213,6 +218,8 @@ transport_, storage_, true, mock_callback)); + EXPECT_CALL(*this, OnRegistrationStatusChange()) + .Times(testing::AnyNumber()); } MOCK_METHOD0(OnRegistrationStatusChange, void()); @@ -503,4 +510,52 @@ EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); } +TEST_F(DeviceRegistrationInfoTest, UpdateCommand) { + EXPECT_TRUE(dev_reg_->Load()); + auto json_cmds = buffet::unittests::CreateDictionaryValue(R"({ + 'robot': { + '_jump': { + 'parameters': {'_height': 'integer'}, + 'results': {'status': 'string'} + } + } + })"); + EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, "", nullptr)); + + auto commands_json = buffet::unittests::CreateValue(R"([{ + 'name':'robot._jump', + 'id':'1234', + 'parameters': {'_height': 100} + }])"); + ASSERT_NE(nullptr, commands_json.get()); + const base::ListValue* command_list = nullptr; + ASSERT_TRUE(commands_json->GetAsList(&command_list)); + DeviceRegistrationInfo::TestHelper::PublishCommands(dev_reg_.get(), + *command_list); + auto command = command_manager_->FindCommand("1234"); + ASSERT_NE(nullptr, command); + StringPropType string_type; + native_types::Object results{ + {"status", string_type.CreateValue(std::string{"Ok"}, nullptr)} + }; + + auto update_command = [](const ServerRequest& request, + ServerResponse* response) { + // Make sure we serialize the JSON back without any pretty print so + // the string comparison works correctly. + auto json = request.GetDataAsJson(); + EXPECT_NE(nullptr, json.get()); + std::string value; + ASSERT_TRUE(base::JSONWriter::Write(json.get(), &value)); + EXPECT_EQ(R"({"results":{"status":"Ok"}})", value); + }; + + transport_->AddHandler(dev_reg_->GetServiceURL("commands/1234"), + chromeos::http::request_type::kPatch, + base::Bind(update_command)); + + command->SetResults(results); +} + + } // namespace buffet