Skip to content

Commit 72d2cd8

Browse files
authored
Add move operations to Status. (#3773)
1 parent 209c90b commit 72d2cd8

File tree

5 files changed

+167
-14
lines changed

5 files changed

+167
-14
lines changed

Firestore/core/src/firebase/firestore/util/status.cc

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ namespace util {
2828

2929
Status::Status(Error code, absl::string_view msg) {
3030
HARD_ASSERT(code != Error::Ok);
31-
state_ = absl::make_unique<State>();
32-
state_->code = code;
33-
state_->msg = static_cast<std::string>(msg);
31+
state_ = State::MakePtr(code, static_cast<std::string>(msg));
3432
}
3533

3634
void Status::Update(const Status& new_status) {
@@ -40,15 +38,16 @@ void Status::Update(const Status& new_status) {
4038
}
4139

4240
Status& Status::CausedBy(const Status& cause) {
43-
if (cause.ok() || this == &cause) {
41+
if (cause.ok() || this == &cause || cause.IsMovedFrom()) {
4442
return *this;
4543
}
4644

47-
if (ok()) {
45+
if (ok() || IsMovedFrom()) {
4846
*this = cause;
4947
return *this;
5048
}
5149

50+
std::string new_message = error_message();
5251
absl::StrAppend(&state_->msg, ": ", cause.error_message());
5352

5453
// If this Status has no accompanying PlatformError but the cause does, create
@@ -65,15 +64,30 @@ Status& Status::CausedBy(const Status& cause) {
6564

6665
Status& Status::WithPlatformError(std::unique_ptr<PlatformError> error) {
6766
HARD_ASSERT(!ok(), "Platform errors should not be applied to Status::OK()");
67+
if (IsMovedFrom()) {
68+
std::string message = moved_from_message();
69+
state_ = State::MakePtr(Error::Internal, std::move(message));
70+
}
6871
state_->platform_error = std::move(error);
6972
return *this;
7073
}
7174

75+
void Status::State::Deleter::operator()(const State* ptr) const {
76+
if (ptr != State::MovedFromIndicator()) {
77+
delete ptr;
78+
}
79+
}
80+
81+
void Status::SetMovedFrom() {
82+
// Set pointer value to `0x1` as the pointer is no longer useful.
83+
state_ = State::StatePtr{State::MovedFromIndicator()};
84+
}
85+
7286
void Status::SlowCopyFrom(const State* src) {
7387
if (src == nullptr) {
7488
state_ = nullptr;
7589
} else {
76-
state_ = absl::make_unique<State>(*src);
90+
state_ = State::MakePtr(*src);
7791
}
7892
}
7993

@@ -82,6 +96,11 @@ const std::string& Status::empty_string() {
8296
return *empty;
8397
}
8498

99+
const std::string& Status::moved_from_message() {
100+
static std::string* message = new std::string("Status accessed after move.");
101+
return *message;
102+
}
103+
85104
std::string Status::ToString() const {
86105
if (state_ == nullptr) {
87106
return "OK";
@@ -141,7 +160,7 @@ std::string Status::ToString() const {
141160
break;
142161
}
143162
result += ": ";
144-
result += state_->msg;
163+
result += IsMovedFrom() ? moved_from_message() : state_->msg;
145164
return result;
146165
}
147166
}

Firestore/core/src/firebase/firestore/util/status.h

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <iosfwd>
2626
#include <memory>
2727
#include <string>
28+
#include <utility>
2829

2930
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
3031
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
@@ -53,6 +54,10 @@ class ABSL_MUST_USE_RESULT Status {
5354
Status(const Status& s);
5455
void operator=(const Status& s);
5556

57+
/// Move the specified status.
58+
Status(Status&& s) noexcept;
59+
void operator=(Status&& s) noexcept;
60+
5661
static Status OK() {
5762
return Status();
5863
}
@@ -72,15 +77,16 @@ class ABSL_MUST_USE_RESULT Status {
7277

7378
/// Returns true iff the status indicates success.
7479
bool ok() const {
75-
return (state_ == nullptr);
80+
return state_ == nullptr;
7681
}
7782

7883
Error code() const {
79-
return ok() ? Error::Ok : state_->code;
84+
return ok() ? Error::Ok : (IsMovedFrom() ? Error::Internal : state_->code);
8085
}
8186

8287
const std::string& error_message() const {
83-
return ok() ? empty_string() : state_->msg;
88+
return ok() ? empty_string()
89+
: (IsMovedFrom() ? moved_from_message() : state_->msg);
8490
}
8591

8692
bool operator==(const Status& x) const;
@@ -116,9 +122,29 @@ class ABSL_MUST_USE_RESULT Status {
116122

117123
private:
118124
static const std::string& empty_string();
125+
static const std::string& moved_from_message();
126+
119127
struct State {
120128
State() = default;
121129
State(const State& other);
130+
State(Error code, std::string&& msg);
131+
132+
struct Deleter {
133+
void operator()(const State* ptr) const;
134+
};
135+
// A `unique_ptr` with a custom deleter. If the pointer's value has been set
136+
// to a special value (0x01) to indicate it is moved, invoking the custom
137+
// deleter will be a no-op.
138+
using StatePtr = std::unique_ptr<State, Deleter>;
139+
140+
static State* MovedFromIndicator() {
141+
return reinterpret_cast<State*>(0x01);
142+
}
143+
144+
template <typename... Args>
145+
static StatePtr MakePtr(Args&&... args) {
146+
return StatePtr(new State(std::forward<Args>(args)...));
147+
}
122148

123149
Error code;
124150
std::string msg;
@@ -129,9 +155,20 @@ class ABSL_MUST_USE_RESULT Status {
129155
// NSError* to Status and back to NSError* losslessly.
130156
std::unique_ptr<PlatformError> platform_error;
131157
};
132-
// OK status has a `nullptr` state_. Otherwise, `state_` points to
133-
// a `State` structure containing the error code and message(s)
134-
std::unique_ptr<State> state_;
158+
159+
// Asserts if `state_` is a valid pointer, should be used at all places where
160+
// it is used as a pointer, instead of using `state_`.
161+
bool IsMovedFrom() const {
162+
return state_.get() == State::MovedFromIndicator();
163+
}
164+
165+
// OK status has a `nullptr` `state_`. If this instance is moved, state_ has
166+
// the value of `State::MovedFromIndicator()`. Otherwise `state_` points to
167+
// a `State` structure containing the error code and message(s).
168+
State::StatePtr state_;
169+
170+
// Tags this instance as `moved-from`.
171+
void SetMovedFrom();
135172

136173
void SlowCopyFrom(const State* src);
137174
};
@@ -152,7 +189,8 @@ class PlatformError {
152189
};
153190

154191
inline Status::Status(const Status& s)
155-
: state_((s.state_ == nullptr) ? nullptr : new State(*s.state_)) {
192+
: state_{s.state_ == nullptr ? State::StatePtr{}
193+
: State::MakePtr(*s.state_)} {
156194
}
157195

158196
inline Status::State::State(const State& s)
@@ -162,6 +200,10 @@ inline Status::State::State(const State& s)
162200
: s.platform_error->Copy()) {
163201
}
164202

203+
inline Status::State::State(Error code, std::string&& msg)
204+
: code(code), msg(std::move(msg)) {
205+
}
206+
165207
inline void Status::operator=(const Status& s) {
166208
// The following condition catches both aliasing (when this == &s),
167209
// and the common case where both s and *this are ok.
@@ -170,6 +212,18 @@ inline void Status::operator=(const Status& s) {
170212
}
171213
}
172214

215+
inline Status::Status(Status&& s) noexcept : state_(std::move(s.state_)) {
216+
s.SetMovedFrom();
217+
}
218+
219+
inline void Status::operator=(Status&& s) noexcept {
220+
// Moving into self is a no-op.
221+
if (this != &s) {
222+
state_ = std::move(s.state_);
223+
s.SetMovedFrom();
224+
}
225+
}
226+
173227
inline bool Status::operator==(const Status& x) const {
174228
return (this->state_ == x.state_) || (ToString() == x.ToString());
175229
}

Firestore/core/src/firebase/firestore/util/status_apple.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Status FromFirestoreNSError(NSError* error) {
110110

111111
NSError* Status::ToNSError() const {
112112
if (ok()) return nil;
113+
// Early exit because `state_` is moved.
114+
if (IsMovedFrom()) return MakeNSError(code(), error_message());
113115

114116
NSError* error = UnderlyingNSError::Recover(state_->platform_error);
115117
if (error) return error;

Firestore/core/test/firebase/firestore/util/status_apple_test.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@
8686
EXPECT_TRUE([not_found_nserror isEqual:cause]);
8787
}
8888

89+
TEST(Status, MovedFromToNSError) {
90+
Status not_found(Error::NotFound, "Some file not found");
91+
Status unused = std::move(not_found);
92+
93+
EXPECT_EQ(not_found.ToNSError().domain, FIRFirestoreErrorDomain);
94+
EXPECT_EQ(not_found.ToNSError().code, Internal);
95+
}
96+
8997
} // namespace util
9098
} // namespace firestore
9199
} // namespace firebase

Firestore/core/test/firebase/firestore/util/status_test.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,55 @@ TEST(Status, Copy) {
5555
ASSERT_EQ(a.ToString(), b.ToString());
5656
}
5757

58+
TEST(Status, Move) {
59+
Status s(Status(Error::InvalidArgument, "Invalid"));
60+
ASSERT_EQ(Error::InvalidArgument, s.code());
61+
62+
Status new_s = std::move(s);
63+
ASSERT_EQ(Error::InvalidArgument, new_s.code());
64+
65+
Status ok = Status::OK();
66+
Status new_ok = std::move(ok);
67+
ASSERT_TRUE(new_ok.ok());
68+
// Moved OK is not OK anymore.
69+
ASSERT_FALSE(ok.ok());
70+
}
71+
5872
TEST(Status, Assign) {
5973
Status a(Error::InvalidArgument, "Invalid");
6074
Status b;
6175
b = a;
6276
ASSERT_EQ(a.ToString(), b.ToString());
6377
}
6478

79+
TEST(Status, MoveAssign) {
80+
Status ok;
81+
Status reassigned{Error::InvalidArgument, "Foo"};
82+
reassigned = std::move(ok);
83+
ASSERT_EQ(reassigned, Status::OK());
84+
85+
Status bad{Error::InvalidArgument, "Foo"};
86+
reassigned = std::move(bad);
87+
ASSERT_EQ(reassigned, Status(Error::InvalidArgument, "Foo"));
88+
}
89+
90+
TEST(Status, CanAccessMovedFrom) {
91+
Status ok = Status::OK();
92+
Status assigned = std::move(ok);
93+
94+
ASSERT_FALSE(ok.ok());
95+
ASSERT_EQ(ok.error_message(), "Status accessed after move.");
96+
ASSERT_EQ(ok.code(), Error::Internal);
97+
}
98+
99+
TEST(Status, CanAssignToMovedFromStatus) {
100+
Status a(Error::InvalidArgument, "Invalid");
101+
Status b = std::move(a);
102+
103+
EXPECT_NO_THROW({ a = Status(Error::Internal, "Internal"); });
104+
ASSERT_EQ(a.ToString(), "Internal: Internal");
105+
}
106+
65107
TEST(Status, Update) {
66108
Status s;
67109
s.Update(Status::OK());
@@ -77,6 +119,14 @@ TEST(Status, Update) {
77119
ASSERT_FALSE(s.ok());
78120
}
79121

122+
TEST(Status, CanUpdateMovedFrom) {
123+
Status a(Error::InvalidArgument, "Invalid");
124+
Status b = std::move(a);
125+
126+
a.Update(b);
127+
ASSERT_EQ(a.ToString(), "Internal: Status accessed after move.");
128+
}
129+
80130
TEST(Status, EqualsOK) {
81131
ASSERT_EQ(Status::OK(), Status());
82132
}
@@ -105,6 +155,17 @@ TEST(Status, EqualsDifferentMessage) {
105155
ASSERT_NE(a, b);
106156
}
107157

158+
TEST(Status, EqualsApplyToMovedFrom) {
159+
Status a(Error::InvalidArgument, "message");
160+
Status unused = std::move(a);
161+
Status b(Error::InvalidArgument, "message");
162+
163+
ASSERT_NE(a, b);
164+
165+
unused = std::move(b);
166+
ASSERT_EQ(a, b);
167+
}
168+
108169
TEST(Status, FromErrno) {
109170
Status a = Status::FromErrno(EEXIST, "Cannot write file");
110171
ASSERT_THAT(
@@ -159,6 +220,15 @@ TEST(Status, CauseBy_Self) {
159220
EXPECT_EQ(not_found, result);
160221
}
161222

223+
TEST(Status, CauseBy_OnMovedFrom) {
224+
Status not_ready(Error::FailedPrecondition, "DB not ready");
225+
Status not_found(Error::NotFound, "file not found");
226+
Status unused = std::move(not_ready);
227+
228+
Status result = not_ready.CausedBy(not_found);
229+
EXPECT_EQ(not_found, result);
230+
}
231+
162232
} // namespace util
163233
} // namespace firestore
164234
} // namespace firebase

0 commit comments

Comments
 (0)