Skip to content

Commit

Permalink
Add a _span string literal operator for creating constexpr CharSpans (#…
Browse files Browse the repository at this point in the history
…30042)

* Add a _span string literal operator to create constexpr CharSpans

* Adopt _span for literals

* Tweaks from review
  • Loading branch information
ksperling-apple authored Oct 28, 2023
1 parent 506a489 commit 181b0cb
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 50 deletions.
2 changes: 1 addition & 1 deletion src/app/clusters/door-lock-server/door-lock-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,7 @@ EmberAfStatus DoorLockServer::createUser(chip::EndpointId endpointId, chip::Fabr
return static_cast<EmberAfStatus>(DlStatus::kOccupied);
}

const auto & newUserName = !userName.IsNull() ? userName.Value() : chip::CharSpan::fromCharString("");
const auto & newUserName = !userName.IsNull() ? userName.Value() : ""_span;
auto newUserUniqueId = userUniqueId.IsNull() ? 0xFFFFFFFF : userUniqueId.Value();
auto newUserStatus = userStatus.IsNull() ? UserStatusEnum::kOccupiedEnabled : userStatus.Value();
auto newUserType = userType.IsNull() ? UserTypeEnum::kUnrestrictedUser : userType.Value();
Expand Down
2 changes: 1 addition & 1 deletion src/controller/CHIPDeviceController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2543,7 +2543,7 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio
else
{
// Default to "XX", for lack of anything better.
countryCode = CharSpan::fromCharString("XX");
countryCode = "XX"_span;
}

GeneralCommissioning::Commands::SetRegulatoryConfig::Type request;
Expand Down
30 changes: 15 additions & 15 deletions src/credentials/tests/TestFabricTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2261,32 +2261,32 @@ void TestFabricLabelChange(nlTestSuite * inSuite, void * inContext)
// Second scope: set FabricLabel to "acme fabric", make sure it cannot be reverted
{
// Fabric label starts unset from prior scope
CharSpan fabricLabel = CharSpan::fromCharString("placeholder");
CharSpan fabricLabel = "placeholder"_span;

NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.size() == 0);

// Set a valid name
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, CharSpan::fromCharString("acme fabric")));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, "acme fabric"_span));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("acme fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);

// Revert pending fabric data. Should not revert name since nothing pending.
fabricTable.RevertPendingFabricData();

fabricLabel = CharSpan::fromCharString("placeholder");
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("acme fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);

// Verify we fail to set too large a label (> kFabricLabelMaxLengthInBytes)
CharSpan fabricLabelTooBig = CharSpan::fromCharString("012345678901234567890123456789123456");
CharSpan fabricLabelTooBig = "012345678901234567890123456789123456"_span;
NL_TEST_ASSERT(inSuite, fabricLabelTooBig.size() > chip::kFabricLabelMaxLengthInBytes);

NL_TEST_ASSERT(inSuite, fabricTable.SetFabricLabel(1, fabricLabelTooBig) == CHIP_ERROR_INVALID_ARGUMENT);

fabricLabel = CharSpan::fromCharString("placeholder");
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("acme fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}

// Third scope: set fabric label after an update, it sticks, but then goes back after revert
Expand Down Expand Up @@ -2320,23 +2320,23 @@ void TestFabricLabelChange(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);

CharSpan fabricLabel = fabricInfo->GetFabricLabel();
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("acme fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}

// Update fabric label
CharSpan fabricLabel = CharSpan::fromCharString("placeholder");
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, CharSpan::fromCharString("roboto fabric")));
CharSpan fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, "roboto fabric"_span));

fabricLabel = CharSpan::fromCharString("placeholder");
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("roboto fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("roboto fabric"_span) == true);

// Revert pending fabric data. Should revert name to "acme fabric"
fabricTable.RevertPendingFabricData();

fabricLabel = CharSpan::fromCharString("placeholder");
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal(CharSpan::fromCharString("acme fabric")) == true);
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}
}

Expand Down
7 changes: 2 additions & 5 deletions src/crypto/tests/CHIPCryptoPALTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2414,13 +2414,10 @@ static void TestSubject_x509Extraction(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_02.AddAttribute_MatterFabricId(0xFAB000000000001D));
NL_TEST_ASSERT(inSuite,
CHIP_NO_ERROR ==
subjectDN_Node02_02.AddAttribute_CommonName(
chip::CharSpan::fromCharString("TEST CERT COMMON NAME Attr for Node02_02"), false));
subjectDN_Node02_02.AddAttribute_CommonName("TEST CERT COMMON NAME Attr for Node02_02"_span, false));
ChipDN subjectDN_Node02_04;
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_04.AddAttribute_MatterCASEAuthTag(0xABCE1002));
NL_TEST_ASSERT(inSuite,
CHIP_NO_ERROR ==
subjectDN_Node02_04.AddAttribute_CommonName(chip::CharSpan::fromCharString("TestCert02_04"), false));
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_04.AddAttribute_CommonName("TestCert02_04"_span, false));
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_04.AddAttribute_MatterFabricId(0xFAB000000000001D));
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_04.AddAttribute_MatterCASEAuthTag(0xABCD0003));
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == subjectDN_Node02_04.AddAttribute_MatterNodeId(0xDEDEDEDE00020004));
Expand Down
36 changes: 36 additions & 0 deletions src/lib/core/Unchecked.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @file
* This file defines the chip::Optional class to handle values which may
* or may not be present.
*
*/
#pragma once

namespace chip {

/// Unchecked is a disambiguation tag that can be used to provide and select a variant of a
/// constructor or other method that omits the runtime checks performed by the default variant.
struct UncheckedType
{
explicit UncheckedType() = default;
};
inline constexpr UncheckedType Unchecked{};

} // namespace chip
8 changes: 4 additions & 4 deletions src/lib/core/tests/TestOTAImageHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ void TestHappyPath(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, header.mVendorId == 0xDEAD);
NL_TEST_ASSERT(inSuite, header.mProductId == 0xBEEF);
NL_TEST_ASSERT(inSuite, header.mSoftwareVersion == 0xFFFFFFFF);
NL_TEST_ASSERT(inSuite, header.mSoftwareVersionString.data_equal(CharSpan::fromCharString("1.0")));
NL_TEST_ASSERT(inSuite, header.mSoftwareVersionString.data_equal("1.0"_span));
NL_TEST_ASSERT(inSuite, header.mPayloadSize == strlen("test payload"));
NL_TEST_ASSERT(inSuite, header.mMinApplicableVersion.HasValue());
NL_TEST_ASSERT(inSuite, header.mMinApplicableVersion.Value() == 1);
NL_TEST_ASSERT(inSuite, header.mMaxApplicableVersion.HasValue());
NL_TEST_ASSERT(inSuite, header.mMaxApplicableVersion.Value() == 2);
NL_TEST_ASSERT(inSuite, header.mReleaseNotesURL.data_equal(CharSpan::fromCharString("https://rn")));
NL_TEST_ASSERT(inSuite, header.mReleaseNotesURL.data_equal("https://rn"_span));
NL_TEST_ASSERT(inSuite, header.mImageDigestType == OTAImageDigestType::kSha256);
NL_TEST_ASSERT(inSuite, header.mImageDigest.size() == 256 / 8);
}
Expand Down Expand Up @@ -169,13 +169,13 @@ void TestSmallBlocks(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, header.mVendorId == 0xDEAD);
NL_TEST_ASSERT(inSuite, header.mProductId == 0xBEEF);
NL_TEST_ASSERT(inSuite, header.mSoftwareVersion == 0xFFFFFFFF);
NL_TEST_ASSERT(inSuite, header.mSoftwareVersionString.data_equal(CharSpan::fromCharString("1.0")));
NL_TEST_ASSERT(inSuite, header.mSoftwareVersionString.data_equal("1.0"_span));
NL_TEST_ASSERT(inSuite, header.mPayloadSize == strlen("test payload"));
NL_TEST_ASSERT(inSuite, header.mMinApplicableVersion.HasValue());
NL_TEST_ASSERT(inSuite, header.mMinApplicableVersion.Value() == 1);
NL_TEST_ASSERT(inSuite, header.mMaxApplicableVersion.HasValue());
NL_TEST_ASSERT(inSuite, header.mMaxApplicableVersion.Value() == 2);
NL_TEST_ASSERT(inSuite, header.mReleaseNotesURL.data_equal(CharSpan::fromCharString("https://rn")));
NL_TEST_ASSERT(inSuite, header.mReleaseNotesURL.data_equal("https://rn"_span));
NL_TEST_ASSERT(inSuite, header.mImageDigestType == OTAImageDigestType::kSha256);
NL_TEST_ASSERT(inSuite, header.mImageDigest.size() == 256 / 8);
}
Expand Down
29 changes: 27 additions & 2 deletions src/lib/support/Span.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
#pragma once

#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <string.h>
#include <cstring>
#include <type_traits>

#include <lib/core/Unchecked.h>
#include <lib/support/CodeUtils.h>

namespace chip {
Expand Down Expand Up @@ -149,7 +151,10 @@ class Span
return Span(reinterpret_cast<T *>(&bytes[1]), length);
}

// Allow creating CharSpans from a character string.
// Creates a CharSpan from a null-terminated C character string.
//
// Note that for string literals, the user-defined `_span` string
// literal operator should be used instead, e.g. `"Hello"_span`.
template <class U, typename = std::enable_if_t<std::is_same<T, const U>::value && std::is_same<const char, T>::value>>
static Span fromCharString(U * chars)
{
Expand All @@ -162,11 +167,31 @@ class Span
template <typename U>
bool operator==(const Span<U> & other) const = delete;

// Creates a Span without checking whether databuf is a null pointer.
//
// Note: The normal (checked) constructor should be used for general use;
// this overload exists for special use cases where databuf is guaranteed
// to be valid (not null) and a constexpr constructor is required.
//
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61648 prevents making
// operator""_span a friend (and this constructor private).

constexpr Span(UncheckedType tag, pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen) {}

private:
pointer mDataBuf;
size_t mDataLen;
};

inline namespace literals {

inline constexpr Span<const char> operator"" _span(const char * literal, size_t size)
{
return Span<const char>(Unchecked, literal, size);
}

} // namespace literals

namespace detail {

// To make FixedSpan (specifically various FixedByteSpan types) default constructible
Expand Down
4 changes: 2 additions & 2 deletions src/lib/support/tests/TestJsonToTlv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void TestConverter(nlTestSuite * inSuite, void * inContext)
jsonString = "{\n"
" \"1:STRING\" : \"hello\"\n"
"}\n";
ConvertJsonToTlvAndValidate(CharSpan::fromCharString("hello"), jsonString);
ConvertJsonToTlvAndValidate("hello"_span, jsonString);

// Validated using https://base64.guru/converter/encode/hex
const uint8_t byteBuf[] = { 0x01, 0x02, 0x03, 0x04, 0xff, 0xfe, 0x99, 0x88, 0xdd, 0xcd };
Expand All @@ -161,7 +161,7 @@ void TestConverter(nlTestSuite * inSuite, void * inContext)
structVal.a = 20;
structVal.b = true;
structVal.d = byteBuf;
structVal.e = CharSpan::fromCharString("hello");
structVal.e = "hello"_span;
structVal.g = static_cast<float>(1.0);
structVal.h = static_cast<double>(1.0);

Expand Down
9 changes: 9 additions & 0 deletions src/lib/support/tests/TestSpan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ static void TestFromCharString(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, s1.data_equal(CharSpan(str, 3)));
}

static void TestLiteral(nlTestSuite * inSuite, void * inContext)
{
constexpr CharSpan literal = "HI!"_span;
NL_TEST_ASSERT(inSuite, literal.size() == 3);
NL_TEST_ASSERT(inSuite, literal.data_equal(CharSpan::fromCharString("HI!")));
NL_TEST_ASSERT(inSuite, ""_span.size() == 0);
}

static void TestConversionConstructors(nlTestSuite * inSuite, void * inContext)
{
struct Foo
Expand Down Expand Up @@ -330,6 +338,7 @@ static const nlTest sTests[] = {
NL_TEST_DEF_FN(TestSubSpan),
NL_TEST_DEF_FN(TestFromZclString),
NL_TEST_DEF_FN(TestFromCharString),
NL_TEST_DEF_FN(TestLiteral),
NL_TEST_DEF_FN(TestConversionConstructors),
NL_TEST_SENTINEL(),
};
Expand Down
38 changes: 19 additions & 19 deletions src/lib/support/tests/TestStringSplitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void TestStrdupSplitter(nlTestSuite * inSuite, void * inContext)
StringSplitter splitter("single", ',');

NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("single")));
NL_TEST_ASSERT(inSuite, out.data_equal("single"_span));

// next stays at nullptr also after valid data
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
Expand All @@ -59,11 +59,11 @@ void TestStrdupSplitter(nlTestSuite * inSuite, void * inContext)
StringSplitter splitter("one,two,three", ',');

NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("one")));
NL_TEST_ASSERT(inSuite, out.data_equal("one"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("two")));
NL_TEST_ASSERT(inSuite, out.data_equal("two"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("three")));
NL_TEST_ASSERT(inSuite, out.data_equal("three"_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data() == nullptr);
}
Expand All @@ -73,15 +73,15 @@ void TestStrdupSplitter(nlTestSuite * inSuite, void * inContext)
StringSplitter splitter("a**bc*d,e*f", '*');

NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("a")));
NL_TEST_ASSERT(inSuite, out.data_equal("a"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("bc")));
NL_TEST_ASSERT(inSuite, out.data_equal("bc"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("d,e")));
NL_TEST_ASSERT(inSuite, out.data_equal("d,e"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("f")));
NL_TEST_ASSERT(inSuite, out.data_equal("f"_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
}

Expand All @@ -90,37 +90,37 @@ void TestStrdupSplitter(nlTestSuite * inSuite, void * inContext)
StringSplitter splitter(",", ',');
// Note that even though "" is nullptr right away, "," becomes two empty strings
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
}
{
StringSplitter splitter("log,", ',');
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("log")));
NL_TEST_ASSERT(inSuite, out.data_equal("log"_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
}
{
StringSplitter splitter(",log", ',');
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("log")));
NL_TEST_ASSERT(inSuite, out.data_equal("log"_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
}
{
StringSplitter splitter(",,,", ',');
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, splitter.Next(out));
NL_TEST_ASSERT(inSuite, out.data_equal(CharSpan::fromCharString("")));
NL_TEST_ASSERT(inSuite, out.data_equal(""_span));
NL_TEST_ASSERT(inSuite, !splitter.Next(out));
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/support/tests/TestTlvToJson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ void TestConverter(nlTestSuite * inSuite, void * inContext)
"}\n";
EncodeAndValidate(static_cast<double>(1.0), jsonString);

CharSpan charSpan = CharSpan::fromCharString("hello");
CharSpan charSpan = "hello"_span;
jsonString = "{\n"
" \"1:STRING\" : \"hello\"\n"
"}\n";
Expand Down

0 comments on commit 181b0cb

Please sign in to comment.