Skip to content

Commit 3a5e3db

Browse files
Use explicit storage for ActionReturnStatus::c_str (#34746)
* Use explicit storage for ActionReturnStatus::c_str Allocating some global storage for this is both not thread-safe and wastes RAM. Use the stack for the tiny amount of storage (relatively ... same as 4 integers) that a return status string needs. * Add unit tests and fix concatenation bug * Restyle * Undo header addition that seems wrong * Update comment to justify chosen storage size * Restyle * Fix unit test --------- Co-authored-by: Andrei Litvin <andreilitvin@google.com>
1 parent 8bdc3c2 commit 3a5e3db

6 files changed

+78
-25
lines changed

src/app/data-model-provider/ActionReturnStatus.cpp

+11-14
Original file line numberDiff line numberDiff line change
@@ -149,45 +149,42 @@ bool ActionReturnStatus::IsOutOfSpaceEncodingResponse() const
149149
return false;
150150
}
151151

152-
const char * ActionReturnStatus::c_str() const
152+
const char * ActionReturnStatus::c_str(ActionReturnStatus::StringStorage & storage) const
153153
{
154-
155-
// Generally size should be sufficient.
156-
// len("Status<123>, Code 255") == 21 (and then 22 for null terminator. We have slack.)
157-
static chip::StringBuilder<32> sFormatBuffer;
158-
159154
if (const CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&mReturnStatus))
160155
{
161156
#if CHIP_CONFIG_ERROR_FORMAT_AS_STRING
162157
return err->Format(); // any length
163158
#else
164-
sFormatBuffer.Reset().AddFormat("%" CHIP_ERROR_FORMAT, err->Format());
165-
return sFormatBuffer.c_str();
159+
storage.formatBuffer.Reset().AddFormat("%" CHIP_ERROR_FORMAT, err->Format());
160+
return storage.formatBuffer.c_str();
166161
#endif
167162
}
168163

169164
if (const ClusterStatusCode * status = std::get_if<ClusterStatusCode>(&mReturnStatus))
170165
{
166+
storage.formatBuffer.Reset();
167+
171168
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
172-
sFormatBuffer.AddFormat("%s(%d)", Protocols::InteractionModel::StatusName(status->GetStatus()),
173-
static_cast<int>(status->GetStatus()));
169+
storage.formatBuffer.AddFormat("%s(%d)", Protocols::InteractionModel::StatusName(status->GetStatus()),
170+
static_cast<int>(status->GetStatus()));
174171
#else
175172
if (status->IsSuccess())
176173
{
177-
sFormatBuffer.Add("Success");
174+
storage.formatBuffer.Add("Success");
178175
}
179176
else
180177
{
181-
sFormatBuffer.AddFormat("Status<%d>", static_cast<int>(status->GetStatus()));
178+
storage.formatBuffer.AddFormat("Status<%d>", static_cast<int>(status->GetStatus()));
182179
}
183180
#endif
184181

185182
chip::Optional<ClusterStatus> clusterCode = status->GetClusterSpecificCode();
186183
if (clusterCode.HasValue())
187184
{
188-
sFormatBuffer.AddFormat(", Code %d", static_cast<int>(clusterCode.Value()));
185+
storage.formatBuffer.AddFormat(", Code %d", static_cast<int>(clusterCode.Value()));
189186
}
190-
return sFormatBuffer.c_str();
187+
return storage.formatBuffer.c_str();
191188
}
192189

193190
// all std::variant cases exhausted

src/app/data-model-provider/ActionReturnStatus.h

+17-6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ namespace DataModel {
4444
class ActionReturnStatus
4545
{
4646
public:
47+
/// Provides storage for the c_str() call for the action status.
48+
struct StringStorage
49+
{
50+
// Generally size should be sufficient.
51+
// The longest status code from StatusCodeList is NO_UPSTREAM_SUBSCRIPTION(197)
52+
// so we need space for one of:
53+
// "NO_UPSTREAM_SUBSCRIPTION(197)\0" = 30 // explicit verbose status code
54+
// "FAILURE(1), Code 255\0") // cluster failure, verbose
55+
// "SUCCESS(0), Code 255\0") // cluster success, verbose
56+
// "Status<197>, Code 255\0") // Cluster failure, non-verbose
57+
//
58+
// CHIP_ERROR has its own (global/static!) storage
59+
chip::StringBuilder<32> formatBuffer;
60+
};
61+
4762
ActionReturnStatus(CHIP_ERROR error) : mReturnStatus(error) {}
4863
ActionReturnStatus(Protocols::InteractionModel::Status status) :
4964
mReturnStatus(Protocols::InteractionModel::ClusterStatusCode(status))
@@ -81,12 +96,8 @@ class ActionReturnStatus
8196

8297
/// Get the formatted string of this status.
8398
///
84-
/// NOTE: this is NOT thread safe in the general case, however the safety guarantees
85-
/// are similar to chip::ErrorStr which also assumes a static buffer.
86-
///
87-
/// Use this in the chip main event loop (and since that is a single thread,
88-
/// there should be no races)
89-
const char * c_str() const;
99+
/// May use `storage` for storying the actual underlying character string.
100+
const char * c_str(StringStorage & storage) const;
90101

91102
private:
92103
std::variant<CHIP_ERROR, Protocols::InteractionModel::ClusterStatusCode> mReturnStatus;

src/app/data-model-provider/StringBuilderAdapters.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ template <>
2323
StatusWithSize ToString<chip::app::DataModel::ActionReturnStatus>(const chip::app::DataModel::ActionReturnStatus & status,
2424
pw::span<char> buffer)
2525
{
26-
return pw::string::Format(buffer, "ActionReturnStatus<%s>", status.c_str());
26+
chip::app::DataModel::ActionReturnStatus::StringStorage storage;
27+
return pw::string::Format(buffer, "ActionReturnStatus<%s>", status.c_str(storage));
2728
}
2829

2930
} // namespace pw

src/app/data-model-provider/tests/TestActionReturnStatus.cpp

+42
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,45 @@ TEST(TestActionReturnStatus, TestStatusCode)
111111
ASSERT_EQ(ActionReturnStatus(CHIP_IM_CLUSTER_STATUS(0x12)).GetStatusCode(), ClusterStatusCode::ClusterSpecificFailure(0x12));
112112
ASSERT_EQ(ActionReturnStatus(CHIP_IM_GLOBAL_STATUS(Timeout)).GetStatusCode(), ClusterStatusCode(Status::Timeout));
113113
}
114+
115+
TEST(TestActionReturnStatus, TestCString)
116+
{
117+
/// only tests the strings that we build and NOT the CHIP_ERROR ones which
118+
/// are tested separately. for chip_error we just say it should not be empty.
119+
ActionReturnStatus::StringStorage buffer;
120+
ActionReturnStatus status(Status::Success);
121+
122+
// chip-error returns something non-empty
123+
status = CHIP_ERROR_NOT_FOUND;
124+
ASSERT_STRNE(status.c_str(buffer), "");
125+
126+
status = CHIP_NO_ERROR;
127+
ASSERT_STRNE(status.c_str(buffer), "");
128+
129+
// the items below we control
130+
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
131+
status = Status::Success;
132+
ASSERT_STREQ(status.c_str(buffer), "SUCCESS(0)");
133+
134+
status = Status::UnsupportedCommand;
135+
ASSERT_STREQ(status.c_str(buffer), "UNSUPPORTED_COMMAND(129)");
136+
137+
status = ClusterStatusCode::ClusterSpecificSuccess(31);
138+
ASSERT_STREQ(status.c_str(buffer), "SUCCESS(0), Code 31");
139+
140+
status = ClusterStatusCode::ClusterSpecificFailure(32);
141+
ASSERT_STREQ(status.c_str(buffer), "FAILURE(1), Code 32");
142+
#else
143+
status = Status::Success;
144+
ASSERT_STREQ(status.c_str(buffer), "Success");
145+
146+
status = Status::UnsupportedCommand;
147+
ASSERT_STREQ(status.c_str(buffer), "Status<129>");
148+
149+
status = ClusterStatusCode::ClusterSpecificSuccess(31);
150+
ASSERT_STREQ(status.c_str(buffer), "Success, Code 31");
151+
152+
status = ClusterStatusCode::ClusterSpecificFailure(32);
153+
ASSERT_STREQ(status.c_str(buffer), "Status<1>, Code 32");
154+
#endif
155+
}

src/app/reporting/Read-Checked.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,13 @@ ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, const Ac
8282

8383
if (statusEmber != statusDm)
8484
{
85-
StringBuilder<128> buffer;
85+
ActionReturnStatus::StringStorage buffer;
86+
8687
// Note log + chipDie instead of VerifyOrDie so that breakpoints (and usage of rr)
8788
// is easier to debug.
8889
ChipLogError(Test, "Different return codes between ember and DM");
89-
ChipLogError(Test, " Ember status: %s", statusEmber.c_str());
90-
ChipLogError(Test, " DM status: %s", statusDm.c_str());
90+
ChipLogError(Test, " Ember status: %s", statusEmber.c_str(buffer));
91+
ChipLogError(Test, " DM status: %s", statusDm.c_str(buffer));
9192

9293
// For time-dependent data, we may have size differences here: one data fitting in buffer
9394
// while another not, resulting in different errors (success vs out of space).

src/app/reporting/Read-DataModel.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode
9595
// and will be sent to the client as well).
9696
if (!status.IsOutOfSpaceEncodingResponse())
9797
{
98-
ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str());
98+
DataModel::ActionReturnStatus::StringStorage storage;
99+
ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str(storage));
99100
}
100101
return status;
101102
}

0 commit comments

Comments
 (0)