summaryrefslogtreecommitdiffstats
path: root/source/core
diff options
context:
space:
mode:
Diffstat (limited to 'source/core')
-rw-r--r--source/core/slang-char-encode.h2
-rw-r--r--source/core/slang-common.h136
-rw-r--r--source/core/slang-riff-file-system.cpp100
-rw-r--r--source/core/slang-riff-file-system.h6
-rw-r--r--source/core/slang-riff.cpp1492
-rw-r--r--source/core/slang-riff.h1294
-rw-r--r--source/core/slang-semantic-version.cpp22
-rw-r--r--source/core/slang-semantic-version.h43
-rw-r--r--source/core/slang-stream.h6
-rw-r--r--source/core/slang-text-io.cpp2
-rw-r--r--source/core/slang-text-io.h12
-rw-r--r--source/core/slang-writer.h2
12 files changed, 1846 insertions, 1271 deletions
diff --git a/source/core/slang-char-encode.h b/source/core/slang-char-encode.h
index 74968a6ab..2ba8ba992 100644
--- a/source/core/slang-char-encode.h
+++ b/source/core/slang-char-encode.h
@@ -21,7 +21,7 @@ template<typename ReadByteFunc>
Char32 getUnicodePointFromUTF8(const ReadByteFunc& readByte)
{
Char32 codePoint = 0;
- uint32_t leading = Byte(readByte());
+ uint32_t leading = uint32_t(readByte());
uint32_t mask = 0x80;
Index count = 0;
while (leading & mask)
diff --git a/source/core/slang-common.h b/source/core/slang-common.h
index 2aec6f0ce..9248e250f 100644
--- a/source/core/slang-common.h
+++ b/source/core/slang-common.h
@@ -4,6 +4,7 @@
#include "slang.h"
#include <assert.h>
+#include <cstddef>
#include <stdint.h>
#define VARIADIC_TEMPLATE
@@ -11,35 +12,150 @@
namespace Slang
{
+/// Signed 32-bit integer.
+///
+/// This type should be used when the exact size
+/// in bits is important (e.g., when dealing with
+/// explicit binary file formats, etc.). Otherwise
+/// prefer the plain `Int` type or a semantically
+/// richer type like `Count` or `Index`.
+///
typedef int32_t Int32;
+
+/// Unsigned 32-bit integer.
+///
+/// This type should be used when the exact size
+/// in bits is important (e.g., when dealing with
+/// explicit binary file formats, etc.). Otherwise
+/// prefer the plain `Int` type or a semantically
+/// richer type like `Count` or `Index`.
+///
typedef uint32_t UInt32;
+/// Signed 64-bit integer.
+///
+/// This type should be used when the exact size
+/// in bits is important (e.g., when dealing with
+/// explicit binary file formats, etc.). Otherwise
+/// prefer the plain `Int` type or a semantically
+/// richer type like `Count` or `Index`.
+///
typedef int64_t Int64;
+
+/// Unsigned 64-bit integer.
+///
+/// This type should be used when the exact size
+/// in bits is important (e.g., when dealing with
+/// explicit binary file formats, etc.). Otherwise
+/// prefer the plain `Int` type or a semantically
+/// richer type like `Count` or `Index`.
+///
typedef uint64_t UInt64;
-// Define
-typedef SlangUInt UInt;
+/// "Default" integer type for the Slang codebase.
+///
+/// When there is not a clear reason to another
+/// integer type, use this one.
+///
+/// Note that this type is currently defined to be
+/// the same as the `SlangInt` type exposed through
+/// the public Slang API, but this may not be the
+/// case forever.
+///
typedef SlangInt Int;
+/// "Default" unsigned integer type for the Slang codebase.
+///
+/// Only use this type when you explicitly need
+/// an unsigned type that's the same size as `Int`.
+/// Otherwise you should probably just be using `Int`.
+///
+/// Note that this type is currently defined to be
+/// the same as the `SlangUInt` type exposed through
+/// the public Slang API, but this may not be the
+/// case forever.
+///
+typedef SlangUInt UInt;
+
static const UInt kMaxUInt = ~UInt(0);
static const Int kMaxInt = Int(kMaxUInt >> 1);
-// typedef unsigned short Word;
-
typedef intptr_t PtrInt;
-// TODO(JS): It looks like Index is actually 64 bit on 64 bit targets(!)
-// Previous discussions landed on Index being int32_t.
-
-// Type used for indexing, in arrays/views etc. Signed.
+/// Default type for indices.
+///
+/// This is (and should always be) an alias for `Int`.
+///
+/// Use this type to document the intention that an
+/// integer parameter/variable/etc. represents an
+/// index into some kind of sequence, or any other
+/// kind of ordinal number.
+///
typedef Int Index;
+
+static const Index kMaxIndex = kMaxInt;
+
+/// Unsigned equivalent of `Index`.
+///
+/// Please don't use this unless you have a good reason.
+///
typedef UInt UIndex;
+
+/// Default type for counts.
+///
+/// This is (and should always be) an alias for `Int`.
+///
+/// Use this type to document the intention that an
+/// integer parameter/variable/etc. represents a
+/// count of the number of elements in some container,
+/// or any other kind of cardinal number.
+///
typedef Int Count;
+
+/// Unsigned equivalent of `Count`.
+///
+/// Please don't use this unless you have a good reason.
+///
typedef UInt UCount;
-static const Index kMaxIndex = kMaxInt;
-typedef uint8_t Byte;
+/// Explicit type for when manipulating bytes.
+///
+/// Use this type to document the intention that a
+/// parameter/variable/etc. represents an 8-bit byte, with
+/// no particular interpretation of that byte as
+/// any higher-level type.
+///
+/// Note that the `char` types have special semantics
+/// when it comes to "type punning" that are not shared
+/// with other types like `uint8_t`. Using a variation
+/// of `char` here helps avoid the possibility of undefined
+/// behavior when code reads other types to/from arrays
+/// of `Byte`s.
+///
+/// We are not using `std::byte` here because that is
+/// defined as an `enum class` and does not support
+/// mathematical or bitwise operations, which a lot
+/// of the Slang codebase does on `Byte`s.
+///
+typedef unsigned char Byte;
+
+/// Preferred integer type for sizes measured in bytes.
+///
+/// Use this type to document the intention that an
+/// integer parameter/variable/etc. represents the
+/// size of something, in bytes, rather than being
+/// some other kind of integer.
+///
+/// Note that this type is unsigned, despite the stated
+/// default in the Slang codebase being signed integer
+/// types. The reason for this is that variables
+/// holding sizes are often compared against the
+/// result of the `sizeof` operator, which yields
+/// a `size_t`. Our hands are, to some extent, tied
+/// on this matter.
+///
+using Size = size_t;
// TODO(JS):
// Perhaps these should be named Utf8, Utf16 and UnicodePoint/Rune/etc? For now, just keep it simple
diff --git a/source/core/slang-riff-file-system.cpp b/source/core/slang-riff-file-system.cpp
index 7da076974..fc377cd4c 100644
--- a/source/core/slang-riff-file-system.cpp
+++ b/source/core/slang-riff-file-system.cpp
@@ -117,14 +117,10 @@ SlangResult RiffFileSystem::saveFileBlob(const char* path, ISlangBlob* dataBlob)
SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeInBytes)
{
// Load the riff
- RiffContainer container;
+ auto rootList = RIFF::RootChunk::getFromBlob(archive, archiveSizeInBytes);
- MemoryStreamBase stream(FileAccess::Read, archive, archiveSizeInBytes);
- SLANG_RETURN_ON_FAIL(RiffUtil::read(&stream, container));
-
- RiffContainer::ListChunk* rootList = container.getRoot();
// Make sure it's the right type
- if (rootList == nullptr || rootList->m_fourCC != RiffFileSystemBinary::kContainerFourCC)
+ if (rootList == nullptr || rootList->getType() != RiffFileSystemBinary::kContainerFourCC)
{
return SLANG_FAIL;
}
@@ -133,10 +129,11 @@ SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeI
_clear();
// Find the header
- const auto header = rootList->findContainedData<RiffFileSystemBinary::Header>(
- RiffFileSystemBinary::kHeaderFourCC);
+ auto headerChunk = rootList->findDataChunk(RiffFileSystemBinary::kHeaderFourCC);
+
+ const auto header = headerChunk->readPayloadAs<RiffFileSystemBinary::Header>();
- CompressionSystemType compressionType = CompressionSystemType(header->compressionSystemType);
+ CompressionSystemType compressionType = CompressionSystemType(header.compressionSystemType);
switch (compressionType)
{
case CompressionSystemType::None:
@@ -162,52 +159,56 @@ SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeI
// Read all of the contained data
{
- List<RiffContainer::DataChunk*> srcEntries;
- rootList->findContained(RiffFileSystemBinary::kEntryFourCC, srcEntries);
-
- for (auto chunk : srcEntries)
+ for (auto chunk : rootList->getChildren())
{
- auto data = chunk->getSingleData();
+ auto dataChunk = as<RIFF::DataChunk>(chunk);
+ if (!dataChunk)
+ continue;
+
+ if (dataChunk->getType() != RiffFileSystemBinary::kEntryFourCC)
+ continue;
- const uint8_t* srcData = (const uint8_t*)data->getPayload();
- const size_t dataSize = data->getSize();
+ auto payloadData = (const uint8_t*)dataChunk->getPayload();
+ auto payloadSize = dataChunk->getPayloadSize();
- if (dataSize < sizeof(RiffFileSystemBinary::Entry))
+ if (payloadSize < sizeof(RiffFileSystemBinary::Entry))
{
return SLANG_FAIL;
}
- auto srcEntry = (const RiffFileSystemBinary::Entry*)srcData;
- srcData += sizeof(*srcEntry);
+ MemoryReader reader(payloadData, payloadSize);
+
+ RiffFileSystemBinary::Entry srcEntry;
+ reader.read(srcEntry);
// Check if seems plausible
- if (sizeof(RiffFileSystemBinary::Entry) + srcEntry->compressedSize +
- srcEntry->pathSize !=
- dataSize)
+ if (sizeof(RiffFileSystemBinary::Entry) + srcEntry.compressedSize + srcEntry.pathSize !=
+ payloadSize)
{
return SLANG_FAIL;
}
Entry dstEntry;
- const char* path = (const char*)srcData;
- srcData += srcEntry->pathSize;
+ const char* path = (const char*)reader.getRemainingData();
+ reader.skip(srcEntry.pathSize);
- dstEntry.m_canonicalPath = UnownedStringSlice(path, srcEntry->pathSize - 1);
- dstEntry.m_type = (SlangPathType)srcEntry->pathType;
- dstEntry.m_uncompressedSizeInBytes = srcEntry->uncompressedSize;
+ dstEntry.m_canonicalPath = UnownedStringSlice(path, srcEntry.pathSize - 1);
+ dstEntry.m_type = (SlangPathType)srcEntry.pathType;
+ dstEntry.m_uncompressedSizeInBytes = srcEntry.uncompressedSize;
switch (dstEntry.m_type)
{
case SLANG_PATH_TYPE_FILE:
{
- if (srcData + srcEntry->compressedSize != data->getPayloadEnd())
+ if (reader.getRemainingSize() != srcEntry.compressedSize)
{
return SLANG_FAIL;
}
// Get the compressed data
- dstEntry.m_contents = RawBlob::create(srcData, srcEntry->compressedSize);
+ dstEntry.m_contents =
+ RawBlob::create(reader.getRemainingData(), srcEntry.compressedSize);
break;
}
case SLANG_PATH_TYPE_DIRECTORY:
@@ -235,11 +236,9 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
// All blobs are owned in this style
SLANG_UNUSED(blobOwnsContent)
- RiffContainer container;
- RiffContainer::ScopeChunk scopeContainer(
- &container,
- RiffContainer::Chunk::Kind::List,
- RiffFileSystemBinary::kContainerFourCC);
+ RIFF::Builder builder;
+ RIFF::BuildCursor cursor(builder);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, RiffFileSystemBinary::kContainerFourCC);
{
RiffFileSystemBinary::Header header;
@@ -247,7 +246,7 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
? m_compressionSystem->getSystemType()
: CompressionSystemType::None;
header.compressionSystemType = uint32_t(compressionSystemType);
- container.addDataChunk(RiffFileSystemBinary::kHeaderFourCC, &header, sizeof(header));
+ cursor.addDataChunk(RiffFileSystemBinary::kHeaderFourCC, &header, sizeof(header));
}
for (const auto& [_, srcEntry] : m_entries)
@@ -258,10 +257,7 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
continue;
}
- RiffContainer::ScopeChunk scopeData(
- &container,
- RiffContainer::Chunk::Kind::Data,
- RiffFileSystemBinary::kEntryFourCC);
+ SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, RiffFileSystemBinary::kEntryFourCC);
RiffFileSystemBinary::Entry dstEntry;
dstEntry.uncompressedSize = 0;
@@ -278,41 +274,33 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
}
// Entry header
- container.write(&dstEntry, sizeof(dstEntry));
+ cursor.addData(&dstEntry, sizeof(dstEntry));
// Path
- container.write(
+ cursor.addData(
srcEntry.m_canonicalPath.getBuffer(),
srcEntry.m_canonicalPath.getLength() + 1);
// Add the contained data without copying
if (blob)
{
- RiffContainer::Data* data = container.addData();
- container.setUnowned(
- data,
+ cursor.addUnownedData(
const_cast<void*>(blob->getBufferPointer()),
blob->getBufferSize());
}
}
- OwnedMemoryStream stream(FileAccess::Write);
- // We now write the RiffContainer to the stream
- SLANG_RETURN_ON_FAIL(RiffUtil::write(container.getRoot(), true, &stream));
-
- List<uint8_t> data;
- stream.swapContents(data);
-
- *outBlob = ListBlob::moveCreate(data).detach();
+ SLANG_RETURN_ON_FAIL(builder.writeToBlob(outBlob));
return SLANG_OK;
}
/* static */ bool RiffFileSystem::isArchive(const void* data, size_t sizeInBytes)
{
- MemoryStreamBase stream(FileAccess::Read, data, sizeInBytes);
- RiffListHeader header;
- return SLANG_SUCCEEDED(RiffUtil::readHeader(&stream, header)) &&
- header.subType == RiffFileSystemBinary::kContainerFourCC;
+ auto rootList = RIFF::RootChunk::getFromBlob(data, sizeInBytes);
+ if (!rootList)
+ return false;
+
+ return rootList->getType() == RiffFileSystemBinary::kContainerFourCC;
}
} // namespace Slang
diff --git a/source/core/slang-riff-file-system.h b/source/core/slang-riff-file-system.h
index 6eda1a371..f3eab9c14 100644
--- a/source/core/slang-riff-file-system.h
+++ b/source/core/slang-riff-file-system.h
@@ -11,9 +11,9 @@ namespace Slang
// The riff information used for RiffArchiveFileSystem
struct RiffFileSystemBinary
{
- static const FourCC kContainerFourCC = SLANG_FOUR_CC('S', 'c', 'o', 'n');
- static const FourCC kEntryFourCC = SLANG_FOUR_CC('S', 'f', 'i', 'l');
- static const FourCC kHeaderFourCC = SLANG_FOUR_CC('S', 'h', 'e', 'a');
+ static const FourCC::RawValue kContainerFourCC = SLANG_FOUR_CC('S', 'c', 'o', 'n');
+ static const FourCC::RawValue kEntryFourCC = SLANG_FOUR_CC('S', 'f', 'i', 'l');
+ static const FourCC::RawValue kHeaderFourCC = SLANG_FOUR_CC('S', 'h', 'e', 'a');
struct Header
{
diff --git a/source/core/slang-riff.cpp b/source/core/slang-riff.cpp
index c1e3e81c3..be15497f3 100644
--- a/source/core/slang-riff.cpp
+++ b/source/core/slang-riff.cpp
@@ -1,1024 +1,878 @@
+// slang-riff.cpp
#include "slang-riff.h"
+#include "slang-blob.h"
#include "slang-com-helper.h"
-#include "slang-hex-dump-util.h"
namespace Slang
{
+namespace RIFF
+{
-/* static */ int64_t RiffUtil::calcChunkTotalSize(const RiffHeader& chunk)
+Size _roundUpToChunkAlignment(Size size)
{
- size_t size = chunk.size + sizeof(RiffHeader);
- return getPadSize(size);
+ auto alignmentMask = Size(Chunk::kChunkAlignment) - 1;
+ return (size + alignmentMask) & ~alignmentMask;
}
-/* static */ SlangResult RiffUtil::skip(
- const RiffHeader& chunk,
- Stream* stream,
- int64_t* remainingBytesInOut)
-{
- int64_t chunkSize = calcChunkTotalSize(chunk);
- if (remainingBytesInOut)
- {
- *remainingBytesInOut -= chunkSize;
- }
+//
+// RIFF::Chunk
+//
- // Skip the payload (we don't need to skip the Chunk because that was already read
- SLANG_RETURN_ON_FAIL(stream->seek(SeekOrigin::Current, chunkSize - sizeof(RiffHeader)));
- return SLANG_OK;
-}
+//
+// RIFF::DataChunk
+//
-/* static */ SlangResult RiffUtil::readChunk(Stream* stream, RiffHeader& outChunk)
+void DataChunk::writePayloadInto(void* outData, Size size) const
{
- size_t readBytes;
- SLANG_RETURN_ON_FAIL(stream->read(&outChunk, sizeof(RiffHeader), readBytes));
- // TODO(JS): Could handle endianness issues here...
- return (readBytes == sizeof(RiffHeader)) ? SLANG_OK : SLANG_FAIL;
+ SLANG_ASSERT(size <= getPayloadSize());
+ ::memcpy(outData, getPayload(), size);
}
-/* static */ SlangResult RiffUtil::writeData(
- const RiffHeader* header,
- size_t headerSize,
- const void* payload,
- size_t payloadSize,
- Stream* out)
-{
- SLANG_ASSERT(uint64_t(payloadSize) <= uint64_t(0xfffffffff));
- SLANG_ASSERT(headerSize >= sizeof(RiffHeader));
-
- // TODO(JS): Could handle endianness here
+//
+// RIFF::BoundsCheckedChunkPtr
+//
- RiffHeader chunk;
- chunk.type = header->type;
- chunk.size = uint32_t(headerSize - sizeof(RiffHeader) + payloadSize);
-
- // The chunk
- SLANG_RETURN_ON_FAIL(out->write(&chunk, sizeof(RiffHeader)));
+void BoundsCheckedChunkPtr::_set(Chunk const* chunk, Size sizeLimit)
+{
+ // We start by clearing out the state of this
+ // pointer, so that we can early-out if any
+ // validation checks fail, and be sure we
+ // have a null pointer.
+ //
+ _ptr = nullptr;
+ _sizeLimit = 0;
- // Remainder of header
- if (headerSize > sizeof(RiffHeader))
+ // If there's nothing to point to, then the pointer
+ // should be null anyway.
+ //
+ if (!chunk || !sizeLimit)
{
- // The rest of the header
- SLANG_RETURN_ON_FAIL(out->write(header + 1, headerSize - sizeof(RiffHeader)));
+ return;
}
- // Write the payload
- SLANG_RETURN_ON_FAIL(out->write(payload, payloadSize));
+ // Because this type can be used to traverse RIFF
+ // chunks that were loaded into memory from in-theory
+ // untrusted sources, we try to provide some validation
+ // checks to make sure that access to the chunk will
+ // be safe (or as safe as we can easily ensure).
- // The riff spec requires all chunks are 4 byte aligned (even if size is not)
- size_t padSize = getPadSize(payloadSize);
- if (padSize - payloadSize)
+ // If the available size isn't even enough for the
+ // header of a RIFF chunk, then something is wrong.
+ //
+ if (sizeLimit < sizeof(Chunk::Header))
{
- uint8_t end[kRiffPadSize] = {0};
- SLANG_RETURN_ON_FAIL(out->write(end, padSize - payloadSize));
+ SLANG_UNEXPECTED("invalid RIFF");
+ return;
}
- return SLANG_OK;
-}
-
-/* static */ SlangResult RiffUtil::readPayload(
- Stream* stream,
- size_t size,
- void* outData,
- size_t& outReadSize)
-{
- outReadSize = 0;
-
- SLANG_RETURN_ON_FAIL(stream->readExactly(outData, size));
+ // Once we've checked that there is enough space
+ // for a valid RIFF header, we can read the
+ // size that the `chunk` reports itself as having.
+ //
+ auto reportedSize = chunk->getTotalSize();
- const size_t alignedSize = getPadSize(size);
- // Skip to the alignment
- if (alignedSize > size)
+ // If the reported size is too small, then something
+ // is wrong.
+ //
+ if (reportedSize < sizeof(Chunk::Header))
{
- SLANG_RETURN_ON_FAIL(stream->seek(SeekOrigin::Current, alignedSize - size));
+ SLANG_UNEXPECTED("invalid RIFF");
+ return;
}
- outReadSize = alignedSize;
- return SLANG_OK;
-}
-/* static */ SlangResult RiffUtil::readData(
- Stream* stream,
- RiffHeader* outHeader,
- size_t headerSize,
- List<uint8_t>& data)
-{
- RiffHeader chunk;
- SLANG_RETURN_ON_FAIL(readChunk(stream, chunk));
- if (chunk.size < headerSize)
+ // If the reported size is bigger than the size limit,
+ // then it must be invalid (it is reporting itself as
+ // bigger than the region of memory that is supposed
+ // to contain it).
+ //
+ if (reportedSize > sizeLimit)
{
- return SLANG_FAIL;
+ SLANG_UNEXPECTED("invalid RIFF");
+ return;
}
- *outHeader = chunk;
-
- // Read the header
- if (headerSize > sizeof(RiffHeader))
+ // If the chunk claims to be a list chunk, then it
+ // must be big enough to hold the larger header
+ // that list chunks use.
+ //
+ if (as<ListChunk>(chunk))
{
- SLANG_RETURN_ON_FAIL(stream->readExactly(outHeader + 1, headerSize - sizeof(RiffHeader)));
+ if (reportedSize < sizeof(ListChunk::Header))
+ {
+ SLANG_UNEXPECTED("invalid RIFF");
+ return;
+ }
}
- const size_t payloadSize = chunk.size - (headerSize - sizeof(RiffHeader));
- size_t readSize;
- data.setCount(payloadSize);
- return readPayload(stream, payloadSize, data.getBuffer(), readSize);
+ // At this point we've performed some basic validation
+ // telling us that the chunk header appears plausible.
+ // This does not mean that we've fully validated the
+ // hierarchy of child chunks under it (in the case of
+ // a list chunk), but that validation can be performed
+ // on-demand while descending the hierarchy.
+
+ _ptr = chunk;
+ _sizeLimit = sizeLimit;
}
-/* static */ SlangResult RiffUtil::readHeader(Stream* stream, RiffListHeader& outHeader)
+void BoundsCheckedChunkPtr::_set(Chunk const* chunk)
{
- // Need to read the chunk header
- SLANG_RETURN_ON_FAIL(readChunk(stream, outHeader.chunk));
- outHeader.subType = 0;
+ // In the case where we are being set to point to a
+ // single chunk, we have to assume that whatever
+ // code derived the `chunk` pointer has validated
+ // that it is safe to access its header.
+ //
+ // We will simply set up a pointer that can reference
+ // the `chunk` itself, as well as any of its children
+ // (if it has any), but that cannot be used to access
+ // further sibling chunks under the same parent.
+ //
+ _set(chunk, chunk->getTotalSize());
+}
- if (isListType(outHeader.chunk.type))
- {
- // Read the sub type
- SLANG_RETURN_ON_FAIL(
- stream->readExactly(&outHeader.subType, sizeof(RiffListHeader) - sizeof(RiffHeader)));
- }
+BoundsCheckedChunkPtr BoundsCheckedChunkPtr::getNextSibling() const
+{
+ SLANG_ASSERT(_ptr != nullptr);
+ if (!_ptr)
+ return nullptr;
- return SLANG_OK;
+ // The RIFF chunk reports its own size, and when navigating
+ // the children of a list chunk, each child chunk starts
+ // at the next (aligned) offset after the previous one.
+ //
+ auto chunkSize = _ptr->getTotalSize();
+
+ // As a simple validation check, we check for a chunk that
+ // reports its size as something bigger than the available
+ // size; that would represent an invalid input.
+ //
+ if (chunkSize > _sizeLimit)
+ {
+ SLANG_UNEXPECTED("invalid RIFF chunk size");
+ UNREACHABLE_RETURN(nullptr);
+ }
+
+ // The next chunk (if there is one) would start at the
+ // next offset after this chunk, rounded up to the minimum
+ // alignment for a chunk. Thus, we round up the reported
+ // size of this chunk to compute the offset to the next
+ // chunk.
+ //
+ auto offsetToNextChunk = _roundUpToChunkAlignment(chunkSize);
+
+ // If stepping forward by the given number of bytes would
+ // cause us to exceed our size limit, then we have reached
+ // the end of the list of sibling chunks, and should
+ // return a null pointer.
+ //
+ if (offsetToNextChunk >= _sizeLimit)
+ return nullptr;
+
+ auto nextChunk = (RIFF::Chunk const*)(offsetToNextChunk + (Byte const*)_ptr);
+ auto nextSizeLimit = _sizeLimit - offsetToNextChunk;
+
+ return BoundsCheckedChunkPtr(nextChunk, nextSizeLimit);
}
-namespace
-{ // anonymous
-struct DumpVisitor : public RiffContainer::Visitor
+//
+// RIFF::ListChunk
+//
+
+BoundsCheckedChunkPtr ListChunk::getFirstChild() const
{
- typedef RiffContainer::Chunk Chunk;
- typedef RiffContainer::ListChunk ListChunk;
- typedef RiffContainer::DataChunk DataChunk;
+ // Because this type could be used to navigate
+ // an untrusted RIFF that has been loaded into memory,
+ // we make some efforts to validate that things
+ // seem okay as we navigate it.
+ // The first child of a list chunk (if it has any)
+ // comes right after the list header.
+ //
+ Size firstChildOffset = sizeof(ListChunk::Header);
- // Visitor
- virtual SlangResult enterList(ListChunk* list) SLANG_OVERRIDE
+ // The size that the parent chunk reports should
+ // be appropriate to store the header.
+ //
+ // Note that in order to compute the reported size
+ // we are *accessing* the header, so if there really
+ // are too few bytes available, it is up to whatever
+ // code computed this `ListChunk*` to have done
+ // their own validation checks.
+ //
+ Size reportedParentSize = getTotalSize();
+ if (reportedParentSize < firstChildOffset)
{
- _dumpIndent();
- // If it's the root it's 'riff'
- _dumpRiffType(list == m_rootChunk ? RiffFourCC::kRiff : RiffFourCC::kList);
- m_writer.put(" ");
- _dumpRiffType(list->getSubType());
- m_writer.put("\n");
- m_indent++;
- return SLANG_OK;
+ SLANG_UNEXPECTED("invalid RIFF");
+ UNREACHABLE_RETURN(nullptr);
}
- virtual SlangResult handleData(DataChunk* data) SLANG_OVERRIDE
- {
- _dumpIndent();
- // Write out the name
- _dumpRiffType(data->m_fourCC);
- m_writer.put(" ");
- const RiffHashCode hash = data->calcHash();
+ // The total size that the childen of this chunk can
+ // consume is all of the reported size of the parent,
+ // after the `ListChunk::Header`.
+ //
+ Size availableSizeForChildren = reportedParentSize - firstChildOffset;
- // We don't know in general what the contents is or means... but we can display a hash
- HexDumpUtil::dump(uint32_t(hash), m_writer.getWriter());
- m_writer.put(" ");
+ // The available size can be zero, in the case where
+ // the parent chunk has no children.
+ //
+ if (availableSizeForChildren == 0)
+ return nullptr;
- m_writer.put("\n");
- return SLANG_OK;
- }
- virtual SlangResult leaveList(ListChunk* list) SLANG_OVERRIDE
+ // If the parent chunk has a non-zero size, then it should
+ // have at least one child, and the available size had better
+ // be big enough to at least hold the *header* of that first
+ // child.
+ //
+ if (availableSizeForChildren < sizeof(Chunk::Header))
{
- SLANG_UNUSED(list);
- m_indent--;
- return SLANG_OK;
+ SLANG_UNEXPECTED("invalid RIFF");
+ UNREACHABLE_RETURN(nullptr);
}
- DumpVisitor(WriterHelper writer, Chunk* rootChunk)
- : m_writer(writer), m_indent(0), m_rootChunk(rootChunk)
- {
- }
+ // At this point we've convinced ourselves that there is
+ // conceivably enough space for at least one child chunk,
+ // so we will form a `BoundsCheckedChunkPtr` to it, which
+ // will trigger further validity checks on that child chunk.
+ //
+ auto firstChild = (Chunk const*)(firstChildOffset + (Byte const*)this);
+ return BoundsCheckedChunkPtr(firstChild, availableSizeForChildren);
+}
- void _dumpIndent()
- {
- for (int i = 0; i < m_indent; ++i)
- {
- m_writer.put(" ");
- }
- }
- void _dumpRiffType(FourCC fourCC)
+DataChunk const* ListChunk::findDataChunk(Chunk::Type type) const
+{
+ for (auto chunk : getChildren())
{
- char c[5];
- for (int i = 0; i < 4; ++i)
- {
- c[i] = char(fourCC);
- fourCC >>= 8;
- }
- c[4] = 0;
- m_writer.put(c);
- }
-
- Chunk* m_rootChunk;
+ auto dataChunk = as<DataChunk>(chunk);
+ if (!dataChunk)
+ continue;
- int m_indent;
- WriterHelper m_writer;
-};
+ if (dataChunk->getType() != type)
+ continue;
-} // namespace
-
-/* static */ void RiffUtil::dump(RiffContainer::Chunk* chunk, WriterHelper writer)
-{
- DumpVisitor visitor(writer, chunk);
- chunk->visit(&visitor);
+ return dataChunk;
+ }
+ return nullptr;
}
-/* static */ SlangResult RiffUtil::write(
- RiffContainer::ListChunk* list,
- bool isRoot,
- Stream* stream)
+ListChunk const* ListChunk::findListChunk(Chunk::Type type) const
{
- RiffListHeader listHeader;
-
- listHeader.chunk.type = isRoot ? RiffFourCC::kRiff : RiffFourCC::kList;
- listHeader.chunk.size = uint32_t(list->m_payloadSize);
- listHeader.subType = list->getSubType();
-
- // Write the header
- SLANG_RETURN_ON_FAIL(stream->write(&listHeader, sizeof(listHeader)));
-
- // Write the contained chunks
- Chunk* chunk = list->m_containedChunks;
- while (chunk)
+ for (auto chunk : getChildren())
{
- switch (chunk->m_kind)
- {
- case Chunk::Kind::List:
- {
- auto listChunk = static_cast<ListChunk*>(chunk);
- // It's a container
- SLANG_RETURN_ON_FAIL(write(listChunk, false, stream));
- break;
- }
- case Chunk::Kind::Data:
- {
- auto dataChunk = static_cast<DataChunk*>(chunk);
-
- // Must be a regular chunk with data
- RiffHeader chunkHeader;
- chunkHeader.type = dataChunk->m_fourCC;
- chunkHeader.size = uint32_t(dataChunk->m_payloadSize);
-
- SLANG_RETURN_ON_FAIL(stream->write(&chunkHeader, sizeof(chunkHeader)));
-
- RiffContainer::Data* data = dataChunk->m_dataList;
- while (data)
- {
- SLANG_RETURN_ON_FAIL(stream->write(data->getPayload(), data->getSize()));
-
- // Next but of data
- data = data->m_next;
- }
+ auto listChunk = as<ListChunk>(chunk);
+ if (!listChunk)
+ continue;
- // Need to write for alignment
- const size_t remainingSize =
- getPadSize(dataChunk->m_payloadSize) - dataChunk->m_payloadSize;
+ if (listChunk->getType() != type)
+ continue;
- if (remainingSize)
- {
- static const uint8_t trailing[kRiffPadSize] = {0};
- SLANG_RETURN_ON_FAIL(stream->write(trailing, remainingSize));
- }
- }
- default:
- break;
- }
-
- // Next
- chunk = chunk->m_next;
+ return listChunk;
}
-
- return SLANG_OK;
+ return nullptr;
}
-/* static */ SlangResult RiffUtil::write(RiffContainer* container, Stream* stream)
+ListChunk const* ListChunk::findListChunkRec(Chunk::Type type) const
{
- return write(container->getRoot(), true, stream);
-}
+ // Note: The search being performed here could
+ // be implemented without any need for recursion
+ // (or a stack), by taking advantage of the way
+ // that RIFF chunks are laid out. If we have some
+ // chunk C, then the next aligned offset in memory
+ // after C is either at the end of the hierarchy,
+ // or it is the next sibling of one of C's ancestors
+ // (where C is being counted as its own ancestor).
+ //
+ // However, it's not really clear if there's enough
+ // of a benefit to justify that more subtle implementation.
-/* static */ SlangResult RiffUtil::read(Stream* stream, RiffContainer& outContainer)
-{
- typedef RiffContainer::ScopeChunk ScopeChunk;
- outContainer.reset();
+ if (getType() == type)
+ return this;
- size_t remaining;
+ for (auto chunk : getChildren())
{
- RiffListHeader header;
+ auto listChunk = as<ListChunk>(chunk);
+ if (!listChunk)
+ continue;
- SLANG_RETURN_ON_FAIL(readHeader(stream, header));
- if (!isListType(header.chunk.type))
- {
- return SLANG_FAIL;
- }
+ auto found = listChunk->findListChunkRec(type);
+ if (!found)
+ continue;
- remaining = getPadSize(header.chunk.size) - (sizeof(RiffListHeader) - sizeof(RiffHeader));
- outContainer.startChunk(Chunk::Kind::List, header.subType);
+ return found;
}
+ return nullptr;
+}
- List<size_t> remainingStack;
- while (true)
- {
- // It must be the end
- if (remaining == 0)
- {
- // If it's a container then we pop container
- outContainer.endChunk();
- if (remainingStack.getCount() <= 0)
- {
- break;
- }
-
- remaining = remainingStack.getLast();
- remainingStack.removeLast();
- }
- else
- {
- RiffListHeader header;
- SLANG_RETURN_ON_FAIL(readHeader(stream, header));
-
- // The amount of data can't be larger than what remains
- if (header.chunk.size > remaining)
- {
- return SLANG_FAIL;
- }
- if (header.chunk.type == RiffFourCC::kList)
- {
- if (header.chunk.size & kRiffPadMask)
- {
- SLANG_ASSERT(!"A list chunk can only have divisible by 2 size");
- return SLANG_FAIL;
- }
+//
+// RIFF::RootChunk
+//
- // Work out the pad size
- const size_t padSize = getPadSize(header.chunk.size);
+RootChunk const* RootChunk::getFromBlob(void const* data, size_t dataSize)
+{
+ // Our goal is to determine whether the given
+ // blob superficially looks like a RIFF.
- // Subtract the size of this chunk from remaining of the current chunk
- remaining -= sizeof(RiffHeader) + padSize;
- // Push it, for when we hit the end
- remainingStack.add(remaining);
+ // The data pointer should be non-null if there
+ // was any data passed in.
+ //
+ SLANG_ASSERT(data || !dataSize);
- // Work out how much remains in this container
- remaining = padSize - (sizeof(RiffListHeader) - sizeof(RiffHeader));
+ // If there's no data, then it's obvious not usable.
+ //
+ if (!data)
+ return nullptr;
- // Start a container
- outContainer.startChunk(Chunk::Kind::List, header.subType);
- }
- else
- {
- ScopeChunk scopeChunk(&outContainer, Chunk::Kind::Data, header.chunk.type);
- RiffContainer::Data* data = outContainer.addData();
+ // If there isn't even enough data to store the header
+ // for a root, chunk, then the blob is too small.
+ //
+ if (dataSize < sizeof(RootChunk::Header))
+ return nullptr;
- outContainer.setPayload(data, nullptr, header.chunk.size);
+ // We cast the data pointer to a root chunk here, so that
+ // we can access the fields in the header, but we are not
+ // yet convinced it is actually a valid RIFF, so we may
+ // still return `nullptr`.
+ //
+ auto rootChunk = reinterpret_cast<RootChunk const*>(data);
+
+ // The root chunk of a valid RIFF should have the `"RIFF"`
+ // tag. This acts as a kind of "magic number" to mark the
+ // start of a RIFF.
+ //
+ if (rootChunk->getTag() != RootChunk::kTag)
+ return nullptr;
- size_t readSize;
- SLANG_RETURN_ON_FAIL(
- readPayload(stream, header.chunk.size, data->getPayload(), readSize));
+ // By reading the size field from the root chunk, we can
+ // determine how big of a file the root chunk claims that
+ // we have.
+ //
+ auto reportedSize = rootChunk->getTotalSize();
+
+ // If the size implied by the RIFF header is larger than the
+ // blob, then we do not have a properly structured RIFF,
+ // and we would be at risk of reading past the end of the
+ // buffer if we attempted to use it.
+ //
+ if (reportedSize > dataSize)
+ return nullptr;
- // All read sizes must end up aligned
- SLANG_ASSERT((readSize & kRiffPadMask) == 0);
+ // Note: It is possible that the `reportedSize` is strictly
+ // *less than* the `dataSize` that was passed in, and there
+ // is a policy choice to be made about how to handle that case.
+ //
+ // We err on the side of leniency here, because the client who
+ // is calling this function might intentionally be storing
+ // additional data in the same blob, after the RIFF, and could
+ // use the RIFF's ability to report its own size as a way to
+ // locate that data.
- // Correct remaining
- remaining -= sizeof(RiffHeader) + readSize;
- }
- }
- }
+ // Note: At this point we could recursively walk the hierarchy
+ // of the RIFF and validate that all the contained chunks appear
+ // valid in terms of the sizes they report, but doing so would
+ // take an amount of time that scales with the size of the RIFF,
+ // and our goal here is to be efficient.
+ //
+ // Access to the data through the `RIFF::Chunk` API will do its
+ // best to validate the information in chunks as they are
+ // accessed. The code is attempting to be able to catch
+ // corrupted or accidentally malformed input, but is not aspiring
+ // to anything like proper security.
- return outContainer.isFullyConstructed() ? SLANG_OK : SLANG_FAIL;
+ return rootChunk;
}
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::Chunk !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-SlangResult RiffContainer::Chunk::visit(Visitor* visitor)
+RootChunk const* RootChunk::getFromBlob(ISlangBlob* blob)
{
- switch (m_kind)
- {
- case Kind::Data:
- {
- return visitor->handleData(static_cast<DataChunk*>(this));
- }
- case Kind::List:
- {
- auto list = static_cast<ListChunk*>(this);
- SLANG_RETURN_ON_FAIL(visitor->enterList(list));
-
- Chunk* chunk = list->m_containedChunks;
- while (chunk)
- {
- SLANG_RETURN_ON_FAIL(chunk->visit(visitor));
-
- chunk = chunk->m_next;
- }
-
- SLANG_RETURN_ON_FAIL(visitor->leaveList(list));
- return SLANG_OK;
- }
- default:
- return SLANG_FAIL;
- }
+ SLANG_ASSERT(blob);
+ return getFromBlob(blob->getBufferPointer(), blob->getBufferSize());
}
-SlangResult RiffContainer::Chunk::visitPreOrder(VisitorCallback callback, void* data)
+//
+// RIFF::ChunkBuilder
+//
+
+Size ChunkBuilder::_updateCachedTotalSize() const
{
- switch (m_kind)
+ Size totalSize = 0;
+ if (auto dataChunk = as<DataChunkBuilder>(this))
{
- case Kind::Data:
- {
- return callback(this, data);
- }
- case Kind::List:
- {
- auto list = static_cast<ListChunk*>(this);
- // Do this containing node first
- SLANG_RETURN_ON_FAIL(callback(this, data));
+ // Every chunk starts with a header.
+ //
+ totalSize += sizeof(DataChunk::Header);
- // Do the contents next
- Chunk* chunk = list->m_containedChunks;
- while (chunk)
- {
- SLANG_RETURN_ON_FAIL(chunk->visitPreOrder(callback, data));
- chunk = chunk->m_next;
- }
- return SLANG_OK;
+ // After the header comes the payload of
+ // the chunk, which for a data chunk
+ // will be the concatenation of all its
+ // shards.
+ //
+ for (auto shard : dataChunk->getShards())
+ {
+ totalSize += shard->getPayloadSize();
}
- default:
- return SLANG_FAIL;
}
-}
-
-SlangResult RiffContainer::Chunk::visitPostOrder(VisitorCallback callback, void* data)
-{
- switch (m_kind)
+ else if (auto listChunk = as<ListChunkBuilder>(this))
{
- case Kind::Data:
- {
- return callback(this, data);
- }
- case Kind::List:
- {
- auto list = static_cast<ListChunk*>(this);
+ // A list chunk starts with a header, just
+ // like a data chunk, although its header
+ // is larger.
+ //
+ totalSize += sizeof(ListChunk::Header);
- // Do the contents first
- Chunk* chunk = list->m_containedChunks;
- while (chunk)
- {
- SLANG_RETURN_ON_FAIL(chunk->visitPostOrder(callback, data));
- chunk = chunk->m_next;
- }
- // Then the list node (so a post order)
- SLANG_RETURN_ON_FAIL(callback(this, data));
- return SLANG_OK;
+ // After the header come the child chunks, in order.
+ //
+ for (auto child : listChunk->getChildren())
+ {
+ // We recursively poke the child chunks to
+ // update their cached total size, so that
+ // we can be sure we are getting correct
+ // information.
+ //
+ auto childSize = child->_updateCachedTotalSize();
+
+ // We cannot simply add the size of the child
+ // chunk directly to `totalSize`, because a
+ // RIFF guarantees that every chunk must start
+ // on a suitably aligned boundary. Thus, we
+ // first round `totalSize` up to the necessary
+ // alignment, and then add the child's size.
+ //
+ totalSize = _roundUpToChunkAlignment(totalSize);
+ totalSize += childSize;
}
- default:
- return SLANG_FAIL;
}
-}
-
-size_t RiffContainer::Chunk::calcPayloadSize()
-{
- switch (m_kind)
+ else
{
- case Kind::Data:
- return static_cast<DataChunk*>(this)->calcPayloadSize();
- case Kind::List:
- return static_cast<ListChunk*>(this)->calcPayloadSize();
- default:
- return 0;
+ SLANG_UNREACHABLE("RIFF chunk must be data or list");
}
-}
-RiffContainer::Data* RiffContainer::Chunk::getSingleData() const
-{
- return (m_kind == Kind::Data) ? static_cast<const DataChunk*>(this)->getSingleData() : nullptr;
+ _cachedTotalSize = totalSize;
+ return totalSize;
}
-// !!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::ListChunk !!!!!!!!!!!!!!!!!!!!!!
-
-size_t RiffContainer::ListChunk::calcPayloadSize()
+Result ChunkBuilder::_writeTo(Stream* stream) const
{
- // Have to include the part of the header not taken up by the RiffHeader
- size_t size = sizeof(RiffListHeader) - sizeof(RiffHeader);
- Chunk* chunk = m_containedChunks;
- while (chunk)
- {
- size_t chunkSize = chunk->m_payloadSize + sizeof(RiffHeader);
- // Align the contained chunk size
- size += RiffUtil::getPadSize(chunkSize);
+ // The size information that gets written into
+ // the chunk header will be based on the cached
+ // size information for this chunk.
+ //
+ // If nobody has called `_updateCachedTotalSize()`
+ // at all, then that is a problem.
+ //
+ SLANG_ASSERT(_getCachedTotalSize() >= sizeof(Chunk::Header));
- chunk = chunk->m_next;
- }
- return size;
-}
+ // The size that gets written into the chunk header
+ // is the total size of the chunk, ecluding the chunk
+ // header. Note that this size will *include* the
+ // additional field of the list chunk header.
+ //
+ Size totalSizeExcludingChunkHeader = _getCachedTotalSize() - sizeof(Chunk::Header);
-RiffContainer::Chunk* RiffContainer::ListChunk::findContained(FourCC fourCC) const
-{
- Chunk* chunk = m_containedChunks;
- while (chunk)
- {
- if (chunk->m_fourCC == fourCC)
- {
- return chunk;
- }
- chunk = chunk->m_next;
- }
- return nullptr;
-}
+ // Because the size field in the header is only 32 bits,
+ // we want to double-check that it can actually represent
+ // the size of the data to be written into it.
+ //
+ UInt32 sizeToWriteInHeader = UInt32(totalSizeExcludingChunkHeader);
+ SLANG_ASSERT(Size(sizeToWriteInHeader) == totalSizeExcludingChunkHeader);
-void RiffContainer::ListChunk::findContained(FourCC type, List<ListChunk*>& out)
-{
- Chunk* chunk = m_containedChunks;
- while (chunk)
+ if (auto dataChunk = as<DataChunkBuilder>(this))
{
- if (chunk->m_fourCC == type && chunk->m_kind == Chunk::Kind::List)
- {
- out.add(static_cast<ListChunk*>(chunk));
- }
- chunk = chunk->m_next;
- }
-}
+ // We start by writing the header.
+ //
+ DataChunk::Header header;
+ header.size = sizeToWriteInHeader;
-void RiffContainer::ListChunk::findContained(FourCC type, List<DataChunk*>& out)
-{
- Chunk* chunk = m_containedChunks;
- while (chunk)
- {
- if (chunk->m_fourCC == type && chunk->m_kind == Chunk::Kind::Data)
- {
- out.add(static_cast<DataChunk*>(chunk));
- }
- chunk = chunk->m_next;
- }
-}
+ // The tag of a data chunk is its type `FourCC`.
+ //
+ header.tag = dataChunk->getType();
-RiffContainer::ListChunk* RiffContainer::ListChunk::findContainedList(FourCC type)
-{
- Chunk* chunk = m_containedChunks;
- while (chunk)
- {
- if (chunk->m_fourCC == type && chunk->m_kind == Chunk::Kind::List)
- {
- return static_cast<ListChunk*>(chunk);
- }
- chunk = chunk->m_next;
- }
- return nullptr;
-}
+ // Once we've filled in the header fields, we
+ // can write it to the output stream.
+ //
+ SLANG_RETURN_ON_FAIL(stream->write(&header, sizeof(header)));
-RiffContainer::Data* RiffContainer::ListChunk::findContainedData(FourCC type) const
-{
- Chunk* found = findContained(type);
- if (found && found->m_kind == Kind::Data)
- {
- DataChunk* dataChunk = static_cast<DataChunk*>(found);
- // Assumes that there is a single data chunk
-
- Data* data = dataChunk->m_dataList;
- if (data && data->m_next == nullptr)
+ // Now we can simply write the payload bytes,
+ // which are the concatenation of the payloads
+ // of all the shards.
+ //
+ for (auto shard : dataChunk->getShards())
{
- return data;
+ auto payload = shard->getPayload();
+ auto payloadSize = shard->getPayloadSize();
+ SLANG_RETURN_ON_FAIL(stream->write(payload, payloadSize));
}
}
- return nullptr;
-}
-
-void* RiffContainer::ListChunk::findContainedData(FourCC type, size_t minSize) const
-{
- Data* data = findContainedData(type);
- return (data && data->m_size >= minSize) ? data->getPayload() : nullptr;
-}
-
-static RiffContainer::ListChunk* _findListRec(RiffContainer::ListChunk* list, FourCC subType)
-{
- RiffContainer::Chunk* chunk = list->m_containedChunks;
- while (chunk)
- {
- if (auto childList = as<RiffContainer::ListChunk>(chunk))
+ else if (auto listChunk = as<ListChunkBuilder>(this))
+ {
+ // We start by writing the header.
+ //
+ ListChunk::Header header;
+ header.chunkHeader.size = sizeToWriteInHeader;
+
+ // The tag of a list chunk is either `"RIFF"`
+ // (for a root chunk) or `"LIST"` (for any
+ // other list chunk).
+ //
+ header.chunkHeader.tag =
+ listChunk->getKind() == Chunk::Kind::Root ? RootChunk::kTag : ListChunk::kTag;
+
+ // The type of a list chunk is stored in the
+ // additional header field after the base
+ // chunk header.
+ //
+ header.type = listChunk->getType();
+
+ // Once we've filled in the header fields, we
+ // can write it to the output stream.
+ //
+ SLANG_RETURN_ON_FAIL(stream->write(&header, sizeof(header)));
+
+ // Now we recursively write each of the child chunks,
+ // keeping track of the total size so far, so that
+ // we can insert padding as needing to bring things
+ // up to alignment.
+ //
+ Size totalSize = sizeof(header);
+ for (auto child : listChunk->getChildren())
{
- // Test if the child is the subtype, if so we are done
- if (childList->getSubType() == subType)
+ // We note the total size written so far,
+ // as well as the size after rounding up
+ // to the required alignment.
+ //
+ auto unalignedSize = totalSize;
+ auto alignedSize = _roundUpToChunkAlignment(unalignedSize);
+ SLANG_ASSERT(alignedSize >= unalignedSize);
+
+ // If the aligned size is greater than the
+ // unaligned size, then we may need to write
+ // some padding bytes into the output stream.
+ //
+ auto paddingSize = alignedSize - unalignedSize;
+
+ // The amount of padding to be inserted must
+ // always be less than the minimum chunk alignment.
+ //
+ SLANG_ASSERT(paddingSize < Chunk::kChunkAlignment);
+
+ // We'll write padding bytes to get things up
+ // to the necessary alignment.
+ //
+ auto remainingPaddingToWrite = paddingSize;
+ while (remainingPaddingToWrite--)
{
- return childList;
- }
- auto found = _findListRec(childList, subType);
- if (found)
- {
- return found;
+ static const Byte kPadding[1] = {0};
+ stream->write(kPadding, 1);
}
+
+ // Now we are at a suitably aligned offset.
+ //
+ totalSize = alignedSize;
+
+ // With the alignment concern dealt with,
+ // we can simply recursively write the
+ // child chunk and update the total size.
+ //
+ SLANG_RETURN_ON_FAIL(child->_writeTo(stream));
+ totalSize += child->_getCachedTotalSize();
}
- chunk = chunk->m_next;
+
+ // As a validation check, we expect the total number
+ // of bytes we've written here to match the total
+ // size that was cached on this chunk (and that was
+ // written into the chunk header).
+ //
+ SLANG_ASSERT(totalSize == _getCachedTotalSize());
}
- return nullptr;
+ else
+ {
+ SLANG_UNREACHABLE("RIFF chunk must be data or list");
+ }
+ return SLANG_OK;
}
-/* static */ RiffContainer::ListChunk* RiffContainer::ListChunk::findListRec(FourCC subType)
+MemoryArena& ChunkBuilder::_getMemoryArena() const
{
- return (getSubType() == subType) ? this : _findListRec(this, subType);
+ return getRIFFBuilder()->_getMemoryArena();
}
-// !!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::DataChunk !!!!!!!!!!!!!!!!!!!!!!
+//
+// RIFF::ListChunkBuilder
+//
-RiffContainer::Data* RiffContainer::DataChunk::getSingleData() const
+DataChunkBuilder* ListChunkBuilder::addDataChunk(Chunk::Type type)
{
- Data* data = m_dataList;
- return (data && data->m_next == nullptr) ? data : nullptr;
+ auto chunk = new (_getMemoryArena()) DataChunkBuilder(type, this);
+ _children.add(chunk);
+ return chunk;
}
-RiffReadHelper RiffContainer::DataChunk::asReadHelper() const
+ListChunkBuilder* ListChunkBuilder::addListChunk(Chunk::Type type)
{
- Data* data = getSingleData();
- if (data)
- {
- return RiffReadHelper((const uint8_t*)data->getPayload(), data->getSize());
- }
- return RiffReadHelper(nullptr, 0);
+ auto chunk = new (_getMemoryArena()) ListChunkBuilder(type, this);
+ _children.add(chunk);
+ return chunk;
}
-RiffHashCode RiffContainer::DataChunk::calcHash() const
-{
- RiffHashCode hash = 0;
-
- Data* data = m_dataList;
- while (data)
- {
- // This is a little contrived (in that we don't use the function getHashCode), but the
- // reason to be careful is we want the same result however many Data blocks there are.
- const char* buffer = (const char*)data->getPayload();
- const size_t size = data->getSize();
-
- for (size_t i = 0; i < size; ++i)
+//
+// RIFF::DataChunkBuilder
+//
+
+void DataChunkBuilder::addData(void const* data, Size size)
+{
+ // Adding no data should be a no-op.
+ //
+ if (size == 0)
+ return;
+
+ // The most interesting implementation detail here
+ // is that we will try to detect cases where we
+ // can re-use an existing `Shard` by adding the data
+ // to the end of that shard's allocation.
+ //
+ // This is only possible because of the way that
+ // we are using a single `MemoryArena` to allocate
+ // everything, which makes it possible that the
+ // next address the arena would return for an allocation
+ // of `size` bytes is the same as the ending address
+ // of the payload for the last shard of this chunk.
+ //
+ auto& arena = _getMemoryArena();
+
+ // We start by checking if this chunk already has
+ // a last shard that we could consider appending to.
+ //
+ auto lastShard = _shards.getLast();
+ if (lastShard)
+ {
+ // If there is a last shard, then we can compute
+ // the end address of its payload, and see if
+ // it is the same as the cursor of the arena
+ // we are allocating from.
+ //
+ auto payload = lastShard->getPayload();
+ auto payloadSize = lastShard->getPayloadSize();
+ auto payloadEnd = (Byte*)payload + payloadSize;
+ if (payloadEnd == arena.getCursor())
{
- hash = RiffHashCode(buffer[i]) + (hash << 6) + (hash << 16) - hash;
+ // Now that we've confirmed that the shard's
+ // payload ends at an address the arena could
+ // conceivably allocate from, we need to ask
+ // the arena to allocate `size` bytes from
+ // the current block it is using, and see if
+ // doing so succeeds.
+ //
+ if (arena.allocateCurrentUnaligned(size))
+ {
+ // At this point, we've confirmed that we
+ // are in our special case, and the relevant
+ // bytes have been allocated from the arena.
+ //
+ // Now we can simply write the new data at
+ // what used to be the end address for the
+ // shard's payload, and adjust its state
+ // to account for the new allocation.
+ //
+ ::memcpy(payloadEnd, data, size);
+ lastShard->setPayload(payload, payloadSize + size);
+ return;
+ }
}
-
- data = data->m_next;
}
- return hash;
+ // If we didn't land in our special case, we
+ // will simply allocate a new shard to hold
+ // the data.
+ //
+ // Note that the order of allocation here is
+ // intentional, and supports the optimized special
+ // case that we checked for above. We make the
+ // allocation for the payload *last*, so that
+ // it is possible that the arena's next allocation
+ // could come right after the payload allocation
+ // in memory.
+ //
+ // If we allocated the payload first and the
+ // `Shard` second, then there would already be
+ // another allocation after the payload, and
+ // the optimized case would never trigger.
+ //
+ auto shard = _addShard();
+ auto payload = arena.allocateUnaligned(size);
+ ::memcpy(payload, data, size);
+ shard->setPayload(payload, size);
}
-size_t RiffContainer::DataChunk::calcPayloadSize() const
+void DataChunkBuilder::addUnownedData(void const* data, size_t size)
{
- size_t size = 0;
- Data* data = m_dataList;
- while (data)
- {
- size += data->getSize();
- data = data->m_next;
- }
- return size;
+ // Unowned data will always have to be added as its own shard.
+ //
+ auto shard = _addShard();
+ shard->setPayload(data, size);
}
-void RiffContainer::DataChunk::getPayload(void* inDst) const
+DataChunkBuilder::Shard* DataChunkBuilder::_addShard()
{
- uint8_t* dst = (uint8_t*)inDst;
+ auto shard = new (_getMemoryArena()) Shard();
+ _shards.add(shard);
+ return shard;
+}
- Data* data = m_dataList;
- while (data)
- {
- const size_t size = data->getSize();
- ::memcpy(dst, data->getPayload(), size);
+//
+// RIFF::Builder
+//
- dst += size;
- data = data->m_next;
- }
+Builder::Builder()
+ : _arena(4096)
+{
}
-bool RiffContainer::DataChunk::isEqual(const void* inData, size_t count) const
+Result Builder::writeTo(Stream* stream)
{
- const uint8_t* src = (const uint8_t*)inData;
-
- Data* data = m_dataList;
- while (data)
- {
- const size_t size = data->getSize();
- // Can't have more content than remaining
- // Contents must match
- if (size > count || ::memcmp(src, data->getPayload(), size) != 0)
- {
- return false;
- }
-
- src += size;
- count -= size;
-
- // Next data block
- data = data->m_next;
- }
+ // If there's no root chunk, then this isn't
+ // a well-formed RIFF.
+ //
+ if (!_rootChunk)
+ return SLANG_FAIL;
- // If match must be at the end
- return count == 0;
+ // The `ChunkBuilder::_writeTo()` method requires size
+ // information for each of the chunks in the hierarchy.
+ // Rather than try to keep size information up-to-date
+ // during the building process, we simply compute it
+ // all at once, right before writing the output.
+ //
+ _rootChunk->_updateCachedTotalSize();
+ _rootChunk->_writeTo(stream);
+ return SLANG_OK;
}
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-RiffContainer::RiffContainer()
- : m_arena(4096)
+Result Builder::writeToBlob(ISlangBlob** outBlob)
{
- m_rootList = nullptr;
- m_listChunk = nullptr;
- m_dataChunk = nullptr;
-}
+ OwnedMemoryStream stream(FileAccess::Write);
+ SLANG_RETURN_ON_FAIL(writeTo(&stream));
-void RiffContainer::reset()
-{
- m_arena.reset();
+ List<uint8_t> data;
+ stream.swapContents(data);
- m_rootList = nullptr;
- m_listChunk = nullptr;
- m_dataChunk = nullptr;
+ *outBlob = ListBlob::moveCreate(data).detach();
+ return SLANG_OK;
}
-RiffContainer::ListChunk* RiffContainer::_newListChunk(FourCC subType)
+ListChunkBuilder* Builder::addRootChunk(Chunk::Type type)
{
- SLANG_ASSERT(!RiffUtil::isListType(subType));
+ // There must not already be a root chunk set.
+ SLANG_ASSERT(getRootChunk() == nullptr);
- ListChunk* chunk = (ListChunk*)m_arena.allocate(sizeof(ListChunk));
- chunk->init(subType);
+ auto chunk = new (_getMemoryArena()) ListChunkBuilder(type, this);
+ _rootChunk = chunk;
return chunk;
}
-RiffContainer::DataChunk* RiffContainer::_newDataChunk(FourCC type)
-{
- SLANG_ASSERT(!RiffUtil::isListType(type));
- DataChunk* chunk = (DataChunk*)m_arena.allocate(sizeof(DataChunk));
- chunk->init(type);
- return chunk;
-}
+//
+// RIFF::BuildCursor
+//
-void RiffContainer::_addChunk(Chunk* chunk)
+BuildCursor::BuildCursor() {}
+
+BuildCursor::BuildCursor(Builder& builder)
+ : _riffBuilder(&builder)
{
- if (m_listChunk)
- {
- chunk->m_parent = m_listChunk;
- Chunk*& next = m_listChunk->m_endChunk ? m_listChunk->m_endChunk->m_next
- : m_listChunk->m_containedChunks;
- SLANG_ASSERT(next == nullptr);
- next = chunk;
- m_listChunk->m_endChunk = chunk;
- }
}
-void RiffContainer::setCurrentChunk(Chunk* chunk)
+BuildCursor::BuildCursor(ChunkBuilder* chunk)
{
- SLANG_ASSERT(chunk);
-
- switch (chunk->m_kind)
- {
- case Chunk::Kind::Data:
- m_listChunk = nullptr;
- m_dataChunk = static_cast<RiffContainer::DataChunk*>(chunk);
- break;
-
- case Chunk::Kind::List:
- m_dataChunk = nullptr;
- m_listChunk = static_cast<RiffContainer::ListChunk*>(chunk);
- break;
- }
+ setCurrentChunk(chunk);
}
-void RiffContainer::startChunk(Chunk::Kind kind, FourCC fourCC)
+void BuildCursor::setCurrentChunk(ChunkBuilder* chunk)
{
- SLANG_ASSERT(m_listChunk || m_rootList == nullptr);
-
- switch (kind)
- {
- case Chunk::Kind::Data:
- {
- // We can only start a data chunk if we are in a container, and we can't already be in
- // data chunk
- SLANG_ASSERT(m_listChunk && m_dataChunk == nullptr);
-
- DataChunk* chunk = _newDataChunk(fourCC);
- _addChunk(chunk);
- m_dataChunk = chunk;
- break;
- }
- case Chunk::Kind::List:
- {
- // We can't be in a data chunk
- SLANG_ASSERT(m_dataChunk == nullptr);
-
- ListChunk* list = _newListChunk(fourCC);
-
- // If this is the first, make it the root
- if (!m_rootList)
- {
- m_rootList = list;
- }
-
- _addChunk(list);
-
- m_listChunk = list;
- break;
- }
- }
+ _currentChunk = chunk;
+ _riffBuilder = chunk ? chunk->getRIFFBuilder() : nullptr;
}
-void RiffContainer::endChunk()
+DataChunkBuilder* BuildCursor::addDataChunk(Chunk::Type type)
{
- size_t chunkPayloadSize;
-
- // The chunk we are popping
- // Only keep track of this in debug builds
- [[maybe_unused]] Chunk* chunk = nullptr;
+ // The current chunk must be a list chunk, so that
+ // we can add children to it.
+ //
+ auto parentChunk = as<ListChunkBuilder>(getCurrentChunk());
+ SLANG_ASSERT(parentChunk);
- ListChunk* parent;
- if (m_dataChunk)
- {
- chunk = m_dataChunk;
-
- parent = m_dataChunk->m_parent;
- chunkPayloadSize = m_dataChunk->m_payloadSize;
-
- m_dataChunk = nullptr;
- }
- else
- {
- chunk = m_listChunk;
-
- SLANG_ASSERT(m_listChunk && m_dataChunk == nullptr);
- parent = m_listChunk->m_parent;
- chunkPayloadSize = m_listChunk->m_payloadSize;
- }
-
- m_listChunk = parent;
-
- if (parent)
- {
- // Fix the size taking into account padding bytes requirement
- chunkPayloadSize = RiffUtil::getPadSize(chunkPayloadSize);
- // Update the parents size
- parent->m_payloadSize += sizeof(RiffHeader) + chunkPayloadSize;
- }
-
- // Check it's size seems ok
- SLANG_ASSERT(isChunkOk(chunk));
+ return parentChunk->addDataChunk(type);
}
-void RiffContainer::addDataChunk(FourCC dataFourCC, const void* data, size_t dataSizeInBytes)
+void BuildCursor::addDataChunk(Chunk::Type type, void const* data, size_t size)
{
- startChunk(Chunk::Kind::Data, dataFourCC);
- write(data, dataSizeInBytes);
+ beginDataChunk(type);
+ addData(data, size);
endChunk();
}
-void RiffContainer::setPayload(Data* data, const void* payload, size_t size)
+ListChunkBuilder* BuildCursor::addListChunk(Chunk::Type type)
{
- // We must be in a data chunk
- SLANG_ASSERT(m_dataChunk);
- // The data shouldn't be set up
- SLANG_ASSERT(data->m_ownership == Ownership::Uninitialized);
-
- // Add current chunks data
- m_dataChunk->m_payloadSize += size;
-
- data->m_ownership = Ownership::Arena;
- data->m_size = size;
-
- if (size)
- {
- data->m_payload = m_arena.allocateAligned(size, kPayloadMinAlignment);
- }
-
- if (payload)
+ // If there is no current chunk being written into,
+ // then an attempt to add a new chunk should set
+ // the root chunk of the entire RIFF.
+ //
+ auto currentChunk = getCurrentChunk();
+ if (!currentChunk)
{
- ::memcpy(data->m_payload, payload, size);
+ SLANG_ASSERT(getRIFFBuilder());
+ return _riffBuilder->addRootChunk(type);
}
-}
-
-void RiffContainer::moveOwned(Data* data, void* payload, size_t size)
-{
- // We must be in a data chunk
- SLANG_ASSERT(m_dataChunk);
- // The data shouldn't be set up
- SLANG_ASSERT(data->m_ownership == Ownership::Uninitialized);
-
- // Add current chunks data
- m_dataChunk->m_payloadSize += size;
- data->m_ownership = Ownership::Owned;
- data->m_size = size;
+ // Otherwise, the current chunk must be a list
+ // chunk, and we add a new child to it.
+ //
+ auto parentChunk = as<ListChunkBuilder>(currentChunk);
+ SLANG_ASSERT(parentChunk);
- // The area will manage this block
- m_arena.addExternalBlock(payload, size);
- data->m_payload = payload;
+ return parentChunk->addListChunk(type);
}
-void RiffContainer::setUnowned(Data* data, void* payload, size_t size)
+void BuildCursor::beginDataChunk(Chunk::Type type)
{
- // We must be in a data chunk
- SLANG_ASSERT(m_dataChunk);
- // The data shouldn't be set up
- SLANG_ASSERT(data->m_ownership == Ownership::Uninitialized);
- // Add current chunks data
- m_dataChunk->m_payloadSize += size;
-
- data->m_ownership = Ownership::NotOwned;
- data->m_size = size;
- data->m_payload = payload;
-}
-
-RiffContainer::Data* RiffContainer::addData()
-{
- // We must be in a chunk
- SLANG_ASSERT(m_dataChunk);
-
- Data* data = (Data*)m_arena.allocate(sizeof(Data));
- data->init();
-
- Data*& next = m_dataChunk->m_endData ? m_dataChunk->m_endData->m_next : m_dataChunk->m_dataList;
- SLANG_ASSERT(next == nullptr);
-
- // Add to linked list
- next = data;
- // Make this the new end
- m_dataChunk->m_endData = data;
- return data;
+ auto chunk = addDataChunk(type);
+ setCurrentChunk(chunk);
}
-RiffContainer::Data* RiffContainer::makeSingleData(DataChunk* dataChunk)
+void BuildCursor::beginListChunk(Chunk::Type type)
{
- // There is no data
- if (dataChunk->m_dataList == nullptr)
- {
- return nullptr;
- }
-
- if (dataChunk->m_dataList->m_next == nullptr)
- {
- return dataChunk->m_dataList;
- }
-
- {
- Data* data = dataChunk->m_dataList;
-
- // Okay lets combine all into one block
- const size_t payloadSize = dataChunk->calcPayloadSize();
-
- void* dst = m_arena.allocateAligned(payloadSize, kPayloadMinAlignment);
- dataChunk->getPayload(dst);
-
- // Remove other datas
- data->m_next = nullptr;
- // Make this the end
- dataChunk->m_endData = data;
-
- // Point to the block with all of the data
- data->m_ownership = Ownership::Arena;
- data->m_payload = dst;
- data->m_size = payloadSize;
-
- return data;
- }
+ auto chunk = addListChunk(type);
+ setCurrentChunk(chunk);
}
-void RiffContainer::write(const void* inData, size_t size)
+void BuildCursor::endChunk()
{
- // We must be in a chunk
- SLANG_ASSERT(m_dataChunk);
- // Get the last data chunk
- Data* endData = m_dataChunk->m_endData;
- if (endData)
- {
- uint8_t* end = ((uint8_t*)endData->m_payload) + endData->m_size;
- // See if can just add to end of current data
- if (end == m_arena.getCursor() && m_arena.allocateCurrentUnaligned(size))
- {
- ::memcpy(end, inData, size);
- endData->m_size += size;
+ SLANG_ASSERT(getCurrentChunk() != nullptr);
- // Add current chunks data
- m_dataChunk->m_payloadSize += size;
- return;
- }
- }
-
- auto data = addData();
- setPayload(data, inData, size);
+ auto chunk = getCurrentChunk();
+ setCurrentChunk(chunk->getParent());
}
-static SlangResult _isChunkOk(RiffContainer::Chunk* chunk, void* data)
+void BuildCursor::addData(void const* data, Size size)
{
- SLANG_UNUSED(data);
- return chunk->calcPayloadSize() == chunk->m_payloadSize ? SLANG_OK : SLANG_FAIL;
-}
+ // The current chunk must be a data chunk, so that
+ // we can add data to it.
+ //
+ auto dataChunk = as<DataChunkBuilder>(getCurrentChunk());
+ SLANG_ASSERT(dataChunk);
-/* static */ bool RiffContainer::isChunkOk(Chunk* chunk)
-{
- return SLANG_SUCCEEDED(chunk->visitPostOrder(&_isChunkOk, nullptr));
+ dataChunk->addData(data, size);
}
-static SlangResult _calcAndSetSize(RiffContainer::Chunk* chunk, void* data)
+void BuildCursor::addUnownedData(void const* data, Size size)
{
- SLANG_UNUSED(data);
- chunk->m_payloadSize = chunk->calcPayloadSize();
- return SLANG_OK;
-}
+ // The current chunk must be a data chunk, so that
+ // we can add data to it.
+ //
+ auto dataChunk = as<DataChunkBuilder>(getCurrentChunk());
+ SLANG_ASSERT(dataChunk);
-/* static */ void RiffContainer::calcAndSetSize(Chunk* chunk)
-{
- chunk->visitPostOrder(&_calcAndSetSize, nullptr);
+ dataChunk->addUnownedData(data, size);
}
+} // namespace RIFF
} // namespace Slang
diff --git a/source/core/slang-riff.h b/source/core/slang-riff.h
index c858158e6..9c533aeb8 100644
--- a/source/core/slang-riff.h
+++ b/source/core/slang-riff.h
@@ -1,527 +1,1095 @@
+// slang-riff.h
#ifndef SLANG_RIFF_H
#define SLANG_RIFF_H
+// This file defines an API for reading and writing files in the
+// RIFF file format.
+//
+// Some references on the RIFF format include:
+//
+// * http://fileformats.archiveteam.org/wiki/RIFF
+// * http://www.fileformat.info/format/riff/egff.htm
+//
+// RIFF files, and formats inspired by it, are commonly used as
+// binary interchange formats in cases where ad hoc extensibility
+// is needed.
+//
+
#include "slang-basic.h"
#include "slang-memory-arena.h"
-#include "slang-semantic-version.h"
#include "slang-stream.h"
#include "slang-writer.h"
namespace Slang
{
-// http://fileformats.archiveteam.org/wiki/RIFF
-// http://www.fileformat.info/format/riff/egff.htm
+//
+// An important concept in the RIFF format, as well as
+// many derived formats, is a *four-character code*, usually
+// referred to as a "FourCC" or "FOURCC".
+//
+/// A 32-bit value that comprises four ASCII characters.
+///
+/// A `FourCC` can be used as a kind of "extensible `enum`" in situations
+/// where different developers or groups may want to independently add
+/// cases, while minimizing the chances of accidental collisions.
+///
+/// A `FourCC` can be efficienctly compared, or used in `switch`
+/// statements, which can be an advantage compared to alternative
+/// extensible formats like strings or UUIDs.
+///
+/// In memory, the characters of a `FourCC` come in the
+/// usual order that they would for an array of four `char`s;
+/// that is, the first character occupies the byte with the
+/// lowest address, and so forth. When that same memory
+/// is read as a 32-bit integer, the integer value read will
+/// depend on the endianness of the architecture.
+///
+struct FourCC
+{
+public:
+ /// The value of a `FourCC`, represented as single integer.
+ using RawValue = UInt32;
-typedef uint32_t FourCC;
+ FourCC() { _rawValue = 0; }
-/* Use of macros to construct and extract from FourCC means the FourCC ordering can be fixed for
- * endian differences. */
+ FourCC(RawValue rawValue) { _rawValue = rawValue; }
+
+ void operator=(RawValue rawValue) { _rawValue = rawValue; }
+
+ RawValue getRawValue() const { return _rawValue; }
+
+ operator RawValue() const { return _rawValue; }
+
+private:
+ //
+ // The storage for a `FourCC` is defined in a
+ // way that makes the textual form more visible
+ // when debugging.
+ //
+ union
+ {
+ char _text[4];
+ RawValue _rawValue;
+ };
+};
+
+//
+// Because the integer representation of a `FourCC` depends
+// on the endianness of the architecture, we define a macro
+// to turn a sequence of four independent characters into
+// a single `FourCC::RawValue`, based on the target
+// architecture.
+//
#if SLANG_LITTLE_ENDIAN
-#define SLANG_FOUR_CC(c0, c1, c2, c3) \
- ((FourCC(c0) << 0) | (FourCC(c1) << 8) | (FourCC(c2) << 16) | (FourCC(c3) << 24))
+#define SLANG_FOUR_CC(c0, c1, c2, c3) \
+ ((FourCC::RawValue(c0) << 0) | (FourCC::RawValue(c1) << 8) | (FourCC::RawValue(c2) << 16) | \
+ (FourCC::RawValue(c3) << 24))
#else
-#define SLANG_FOUR_CC(c0, c1, c2, c3) \
- ((FourCC(c0) << 24) | (FourCC(c1) << 16) | (FourCC(c2) << 8) | (FourCC(c3) << 0))
-
+#define SLANG_FOUR_CC(c0, c1, c2, c3) \
+ ((FourCC::RawValue(c0) << 24) | (FourCC::RawValue(c1) << 16) | (FourCC::RawValue(c2) << 8) | \
+ (FourCC::RawValue(c3) << 0))
#endif
-enum
+
+namespace RIFF
{
- kRiffPadSize = 2, ///< We only align to 2 bytes
- kRiffPadMask = kRiffPadSize - 1,
-};
-// Uses it's own version of a hash
-typedef int RiffHashCode;
+struct Chunk;
+struct DataChunk;
+struct ListChunk;
+struct RootChunk;
+class ChunkBuilder;
+class ListChunkBuilder;
+class DataChunkBuilder;
+struct Builder;
-struct RiffHeader
-{
- FourCC type; ///< The FourCC code that identifies this chunk
- uint32_t size; ///< Size does *NOT* include the riff chunk size. The size can be byte sized, but
- ///< on storage it will always be treated as aligned up by 4.
-};
+//
+// A RIFF file is organized as a tree of *chunks*.
+//
-struct RiffListHeader
+/// A chunk in a RIFF file.
+///
+struct Chunk
{
- RiffHeader chunk;
- FourCC subType;
- // This is then followed by the contained subchunk/s
-};
+public:
+ //
+ // The starting offset of a chunk in a RIFF file
+ // is only guaranteed to be 2-byte aligned.
+ // Code that reads from a chunk must be cautious about
+ // the possibility of performing unaligned loads.
+ //
+
+ /// Required alignment for the starting offset of a chunk.
+ static const UInt32 kChunkAlignment = 2;
+
+ //
+ // Every chunk starts with a *header*, which includes
+ // a *tag* used to identify the kind/type of chunk
+ // as well a representation of the size of the chunk
+ // in bytes.
+ //
+ struct Header
+ {
+ //
+ // Note that when loading a RIFF file into memory,
+ // chunks may not start on 4-byte-aligned boundaries,
+ // so code should not directly read the following
+ // fields unless preconditions exist to guarantee
+ // higher alignment.
+ //
+
+ /// Tag for this chunk.
+ ///
+ /// * For a data chunk, this will be its type.
+ /// * For a list chunk this will be `"LIST"`.
+ /// * For a root chunk this will be `"RIFF"`.
+ ///
+ FourCC tag;
+
+ /// Size in bytes of this chunk, not including this header.
+ UInt32 size;
+ };
-struct RiffFourCC
-{
- /// A 'riff' is the high level file container. It is followed by a subtype and then the
- /// contained chunks.
- static const FourCC kRiff = SLANG_FOUR_CC('R', 'I', 'F', 'F');
- /// A list is the same as a 'riff' except can be placed anywhere in hierarchy.
- static const FourCC kList = SLANG_FOUR_CC('L', 'I', 'S', 'T');
+ /// Get the header for this chunk.
+ Header const* getHeader() const { return (Header const*)this; }
+
+ /// Get the tag from the header of this chunk.
+ FourCC getTag() const { return _readTagFromHeader(); }
+
+ /// Get the total size of this chunk, in bytes.
+ ///
+ /// This size includes the chunk header.
+ ///
+ UInt32 getTotalSize() const { return sizeof(RIFF::Chunk) + _readSizeFromHeader(); }
+
+ //
+ // There are three *kinds* of chunks that can appear in a RIFF:
+ //
+ // * *data chunks* contain zero or more bytes of data.
+ //
+ // * *list chunks* contain a sequence of other chunks
+ //
+ // * a *root chunk* is a special case of list chunk that
+ // is used as the root of the chunk hierarchy in a RIFF file.
+ //
+ // List chunks are identified by having a tag of `"LIST"`
+ // in their header, while root chunks have a tag of `"RIFF"`
+ //
+
+ /// Kind of a chunk.
+ enum class Kind
+ {
+ Data,
+ List,
+ Root,
+ };
-private:
- RiffFourCC() = delete;
-};
+ /// Get the kind of this chunk.
+ Kind getKind() const;
-// Follows semantic version rules
-// https://semver.org/
-//
-// major.minor.patch
-// Patch versions indicate a change.
-// Minor means a change that is backwards compatible with previous minor versions. A step in minor
-// and/or major zeros patch. Major means a non compatible change. A step in major, zeros minor and
-// patch.
-struct RiffSemanticVersion
-{
- typedef RiffSemanticVersion ThisType;
- typedef uint32_t RawType;
+ //
+ // Every chunk has a *type*, which is a `FourCC`.
+ //
+ // For data chunks, the type is stored as the tag
+ // of the chunk header, while for list and root
+ // chunks the type is stored immediately after
+ // the chunk header.
+ //
- /// ==
- bool operator==(const ThisType& rhs) const { return m_raw == rhs.m_raw; }
- bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
+ /// Type of a chunk.
+ using Type = FourCC;
- /// A patch change indices a different version but does not change the compatibility of the
- /// format
- int getPatch() const { return m_raw & 0xff; }
- /// A minor change implies a format change that is backwards compatible
- int getMinor() const { return (m_raw >> 8) & 0xff; }
- /// A major change is binary incompatible by default
- int getMajor() const { return (m_raw >> 16); }
+ /// Get the type of this chunk.
+ Type getType() const;
- SemanticVersion asSemanticVersion() const
- {
- return SemanticVersion(getMajor(), getMinor(), getPatch());
- }
+private:
+ Header _header;
- static RawType makeRaw(int major, int minor, int patch)
+protected:
+ //
+ // Rather than directly reading the `_tag` or `_size`
+ // members, code should use the following accessors,
+ // which account for possible alignment issues.
+ //
+
+ FourCC _readTagFromHeader() const
{
- SLANG_ASSERT((major | minor | patch) >= 0);
- SLANG_ASSERT(major < 0x10000 && minor < 0x100 && patch < 0x100);
- return (RawType(major) << 16) | (RawType(minor) << 8) | RawType(patch);
+ auto header = getHeader();
+ FourCC result;
+ memcpy(&result, &header->tag, sizeof(header->tag));
+ return result;
}
- static RiffSemanticVersion makeFromRaw(RawType raw)
+ UInt32 _readSizeFromHeader() const
{
- ThisType version;
- version.m_raw = raw;
- return version;
+ auto header = getHeader();
+ UInt32 result;
+ memcpy(&result, &header->size, sizeof(header->size));
+ return result;
}
+};
- static RiffSemanticVersion make(int major, int minor, int patch)
+/// A chunk that contains zero or more bytes of payload data.
+struct DataChunk : Chunk
+{
+public:
+ /// Get the size in bytes of the payload data of this chunk.
+ UInt32 getPayloadSize() const { return _readSizeFromHeader(); }
+
+ /// Get a pointer to the payload data of this chunk.
+ ///
+ /// Note that this pointer is only guaranteed to be aligned
+ /// up to `RIFF::Chunk::kAlignment`, for chunks of a RIFF
+ /// file loaded directly into memory.
+ ///
+ void const* getPayload() const { return static_cast<void const*>(this + 1); }
+
+ /// Write the payload data of this chunk into the given buffer.
+ ///
+ /// The payload must be at least `size` bytes.
+ /// If the payload is larger than `size` bytes, only the
+ /// first `size` bytes will be written to the buffer.
+ ///
+ void writePayloadInto(void* outData, Size size) const;
+
+ /// Get the payload data of this chunk.
+ ///
+ template<typename T>
+ void writePayloadInto(T& outValue) const
{
- return makeFromRaw(makeRaw(major, minor, patch));
+ writePayloadInto(&outValue, sizeof(outValue));
}
- static RiffSemanticVersion make(const SemanticVersion& in)
+
+ /// Get the payload data of this chunk.
+ ///
+ template<typename T>
+ T readPayloadAs() const
{
- return makeFromRaw(makeRaw(in.m_major, in.m_minor, in.m_patch));
+ T result;
+ writePayloadInto(result);
+ return result;
}
- /// True if the read version is compatible with the current version, based on semantic rules.
- static bool areCompatible(const ThisType& currentVersion, const ThisType& readVersion)
+ /// Get the type of this chunk.
+ Type getType() const
{
- const RawType currentRaw = currentVersion.m_raw;
- const RawType readRaw = readVersion.m_raw;
+ // The type of a data chunk is just the tag
+ // from the chunk header.
- // Must have same major version.
- // For minor version, the read version must be less than or equal.
- return ((currentRaw & 0xffff0000) == (readRaw & 0xffff0000)) &&
- ((currentRaw & 0xff00) >= (readRaw & 0xff00));
+ return getTag();
}
- RawType m_raw;
+ /// Determine if a chunk is an instance of this kind.
+ static bool _isChunkOfThisKind(Chunk const* chunk)
+ {
+ return chunk->getKind() == Chunk::Kind::Data;
+ }
};
-/* A helper class that makes reading data from a data block simpler */
-class RiffReadHelper
+/// A pointer to a `RIFF::Chunk` that is dynamically
+/// checked to make sure access doesn't go past a
+/// certain size bound.
+///
+struct BoundsCheckedChunkPtr
{
public:
- template<typename T>
- SlangResult read(T& out)
- {
- if (m_cur + sizeof(T) > m_end)
- {
- return SLANG_FAIL;
- }
- // TODO: consider whether this type should enforce alignment.
- // SLANG_ASSERT((size_t(m_cur) & (SLANG_ALIGN_OF(T) - 1)) == 0);
- ::memcpy(&out, m_cur, sizeof(T));
- m_cur += sizeof(T);
- return SLANG_OK;
- }
+ /// Initialize a null pointer
+ BoundsCheckedChunkPtr() {}
- /// Get the data
- const uint8_t* getData() const { return m_cur; }
- /// Get the remaining size
- size_t getRemainingSize() const { return size_t(m_end - m_cur); }
+ /// Initialize a null pointer
+ BoundsCheckedChunkPtr(nullptr_t) {}
- RiffReadHelper(const uint8_t* data, size_t size)
- : m_start(data), m_end(data + size), m_cur(data)
- {
- }
- SlangResult skip(size_t size)
- {
- if (m_cur + size > m_end)
- {
- return SLANG_FAIL;
- }
- m_cur += size;
- return SLANG_OK;
- }
+ /// Initialize a pointer to a chunk, with a size limit.
+ BoundsCheckedChunkPtr(Chunk const* chunk, Size sizeLimit) { _set(chunk, sizeLimit); }
-protected:
- const uint8_t* m_start;
- const uint8_t* m_end;
- const uint8_t* m_cur;
-};
+ /// Initialize a pointer to a chunk, with a limit based on its reported size.
+ BoundsCheckedChunkPtr(Chunk const* chunk) { _set(chunk); }
+
+ /// Get the underlying chunk pointer.
+ Chunk const* get() const { return _ptr; }
-/* A container for data in RIFF format. Holds the contents in memory.
+ operator Chunk const*() const { return get(); }
+ Chunk const* operator->() const { return get(); }
-With the data held in memory allows for adding or removing chunks at will.
+ BoundsCheckedChunkPtr getNextSibling() const;
+
+private:
+ Chunk const* _ptr = nullptr;
+ Size _sizeLimit = 0;
+
+ void _set(Chunk const* chunk, Size sizeLimit);
+ void _set(Chunk const* chunk);
+};
-A future implementation does not necessarily have to be backed by memory when construction,
-as data could be written to stream, and the chunk sizes written by seeking back over the file and
-setting the value.
-In normal usage the chunk sizes are calculated during construction. If the structure is changed, the
-sizes may need to be recalculated, before serialization.
-*/
-class RiffContainer
+template<typename T = Chunk>
+struct ChunkList
{
public:
- // This alignment is only made for arena based allocations.
- // For external blocks it's client code to have appropriate alignment.
- // This is needed because when reading a RiffContainer, all allocation is arena based, and
- // if the payload contains 8 byte aligned data, the overall payload needs to be 8 byte aligned.
- static const size_t kPayloadMinAlignment = 8;
-
- enum class Ownership
- {
- Uninitialized, ///< Doesn't contain anything
- NotOwned, ///< It's not owned by the container
- Arena, ///< It's owned and allocated on the arena
- Owned, ///< It's owned, but wasn't allocated on the arena
- };
+ ChunkList() {}
+
+ ChunkList(BoundsCheckedChunkPtr firstChunk)
+ : _firstChunk(firstChunk)
+ {
+ }
- struct Data
+ struct Iterator
{
- /// Get the payload
- void* getPayload() { return m_payload; }
- /// Get the end pointer
- void* getPayloadEnd() { return (void*)((uint8_t*)m_payload + m_size); }
- /// Get the size of the payload
- size_t getSize() const { return m_size; }
- /// Get the ownership of the data held in the payload
- Ownership getOwnership() const { return m_ownership; }
+ public:
+ Iterator() {}
- void init()
+ Iterator(BoundsCheckedChunkPtr chunk)
+ : _chunk(chunk)
{
- m_ownership = Ownership::Uninitialized;
- m_size = 0;
- m_next = nullptr;
- m_payload = nullptr;
}
- Ownership m_ownership; ///< Stores the ownership of the payload
- size_t m_size; ///< The size of the payload
- void* m_payload; ///< The payload
- Data* m_next; ///< The next Data block in the list
- };
+ T const* operator*() const { return static_cast<T const*>(_chunk.get()); }
+
+ void operator++() { _chunk = _chunk.getNextSibling(); }
+
+ bool operator!=(Iterator const& that) const { return _chunk != that._chunk; }
- struct Chunk;
- struct ListChunk;
- struct DataChunk;
+ private:
+ BoundsCheckedChunkPtr _chunk;
+ };
- typedef SlangResult (*VisitorCallback)(Chunk* chunk, void* data);
+ Iterator begin() const { return Iterator(_firstChunk); }
+ Iterator end() const { return Iterator(); }
- class Visitor;
- struct Chunk
+ template<typename U>
+ ChunkList<U> cast() const
{
- enum class Kind
- {
- List, ///< Strictly speaking this can be a 'LIST' or a 'RIFF' as they have the same
- ///< structure
- Data,
- };
+ return ChunkList<U>(_firstChunk);
+ }
- void init(Kind kind, FourCC fourCC)
- {
- m_kind = kind;
- m_fourCC = fourCC;
- m_payloadSize = 0;
- m_next = nullptr;
- m_parent = nullptr;
- }
+ T const* getFirst() const { return *begin(); }
+
+private:
+ friend struct ListChunk;
- SlangResult visit(Visitor* visitor);
- SlangResult visitPostOrder(VisitorCallback callback, void* data);
- SlangResult visitPreOrder(VisitorCallback callback, void* data);
+ BoundsCheckedChunkPtr _firstChunk;
+};
- /// Returns a single data chunk
- Data* getSingleData() const;
+struct ListChunk : Chunk
+{
+public:
+ //
+ // A (non-root) list chunk has a tag of `"LIST"`
+ // in its header.
+ //
- /// Calculate the payload size
- size_t calcPayloadSize();
+ static const FourCC::RawValue kTag = SLANG_FOUR_CC('L', 'I', 'S', 'T');
- Kind m_kind; ///< Kind of chunk
- FourCC m_fourCC; ///< The chunk type for data, or the sub type for a List (riff/list)
- size_t m_payloadSize; ///< The payload size (ie does NOT include RiffChunk header).
- Chunk* m_next; ///< Next chunk in this list
- ListChunk* m_parent; ///< The chunk this belongs to
- };
+ //
+ // A list chunk starts with a header, as all chunks do,
+ // but for a list chunk the ordinary `Chunk::Header`
+ // is followed by an additional `FourCC`, to specify
+ // the type of the list chunk.
+ //
- struct ListChunk : public Chunk
+ struct Header
{
- typedef Chunk Super;
- SLANG_FORCE_INLINE static bool isType(const Chunk* chunk)
- {
- return chunk->m_kind == Kind::List;
- }
+ //
+ // As is the case with any other chunk, code that
+ // wants to access these fields should be mindful
+ // of the way that a RIFF does not guarantee 4-byte
+ // alignment for chunks.
+ //
+
+ /// The base chunk header.
+ Chunk::Header chunkHeader;
+
+ /// The type of this list chunk.
+ Type type;
+ };
- void init(FourCC subType)
- {
- Super::init(Kind::List, subType);
- m_containedChunks = nullptr;
- m_endChunk = nullptr;
+ /// Get the header for this list chunk.
+ Header const* getHeader() const { return (Header const*)this; }
- m_payloadSize = uint32_t(sizeof(RiffListHeader) - sizeof(RiffHeader));
- }
+ //
+ // The content of a list chunk comprises zero or more
+ // child chunks, organized as a kind of linked list.
+ //
+ // The starting offset for each successive child chunk
+ // is the end offset of the previous child chunk, rounded
+ // up to the required alignment (`RIFF::Chunk::kAlignment`).
+ //
- /// Finds chunk (list or data) that matches type. For List/Riff, type is the subtype
- Chunk* findContained(FourCC type) const;
+ /// List of child chunks.
+ using ChildList = ChunkList<>;
- void* findContainedData(FourCC type, size_t minSize) const;
+ /// Get the list of children of this chunk.
+ ChildList getChildren() const { return ChildList(getFirstChild()); }
- ListChunk* findContainedList(FourCC type);
+ /// Get the first child chunk (if any) of this chunk.
+ ///
+ /// The list of child chunks can be navigated using
+ /// the `BoundCheckedChunkPtr::getNextSibling` operation.
+ ///
+ BoundsCheckedChunkPtr getFirstChild() const;
- /// Finds the contained data. NOTE! Assumes that there is only as single data block, and
- /// will return nullptr if there is not
- Data* findContainedData(FourCC type) const;
+ /// Find a child data chunk of the given `type`.
+ DataChunk const* findDataChunk(Chunk::Type type) const;
- template<typename T>
- T* findContainedData(FourCC type) const
- {
- return (T*)findContainedData(type, sizeof(T));
- }
+ /// Find a child list chunk of the given `type`.
+ ListChunk const* findListChunk(Chunk::Type type) const;
- /// Find all contained that match the type
- void findContained(FourCC type, List<ListChunk*>& out);
+ /// Recursively search for a list chunk of the given `type`.
+ ///
+ /// Will consider this chunk itself as a possible match.
+ ///
+ ListChunk const* findListChunkRec(Chunk::Type type) const;
- /// Find all contained that match the type
- void findContained(FourCC type, List<DataChunk*>& out);
+ /// Get the type of this chunk.
+ Type getType() const { return _readTypeFromHeader(); }
- /// Find the list (including self) that matches subtype recursively
- ListChunk* findListRec(FourCC subType);
+ /// Determine if a chunk is an instance of this kind.
+ static bool _isChunkOfThisKind(Chunk const* chunk)
+ {
+ // Anything that isn't a data chunk is a list.
+ return chunk->getKind() != Chunk::Kind::Data;
+ }
- /// NOTE! Assumes all contained chunks have correct payload sizes
- size_t calcPayloadSize();
+private:
+ //
+ // Because we are inheriting from `Chunk`, we do not
+ // declare a full `ListChunk::Header` here, and instead
+ // just declare the additional field that appears after
+ // the base header.
+ //
+
+ Type _type;
+
+ //
+ // The `_type` field is mostly just there for debugging
+ // purposes; when actually reading from the header, we
+ // make use of a cast.
+ //
+
+ Type _readTypeFromHeader() const
+ {
+ auto header = getHeader();
+ Type result;
+ memcpy(&result, &header->type, sizeof(header->type));
+ return result;
+ }
+};
- /// Get the sub type
- FourCC getSubType() const { return m_fourCC; }
+struct RootChunk : ListChunk
+{
+public:
+ //
+ // A root chunk has a tag of `"RIFF"` in its header.
+ //
+
+ static const FourCC::RawValue kTag = SLANG_FOUR_CC('R', 'I', 'F', 'F');
+
+ /// Get a pointer to the root chunk of a RIFF hierarchy stored in a data blob.
+ ///
+ /// Performs some minimal validity checks, and returns `nullptr` if
+ /// the blob provided does not superficially appear to be a valid RIFF.
+ ///
+ static RootChunk const* getFromBlob(void const* data, size_t dataSize);
+
+ /// Get a pointer to the root chunk of a RIFF hierarchy stored in a data blob.
+ ///
+ /// Performs some minimal validity checks, and returns `nullptr` if
+ /// the blob provided does not superficially appear to be a valid RIFF.
+ ///
+ static RootChunk const* getFromBlob(ISlangBlob* blob);
+
+ /// Determine if a chunk is an instance of this kind.
+ static bool _isChunkOfThisKind(Chunk const* chunk)
+ {
+ return chunk->getKind() == Chunk::Kind::Root;
+ }
- /// A singly linked list of contained chunks directly contained in this chunk
- Chunk* getFirstContainedChunk() const { return m_containedChunks; }
+private:
+ static bool _isTagForThisKind(FourCC tag) { return tag == kTag; }
+};
- Chunk* m_containedChunks; ///< The contained chunks
- Chunk* m_endChunk; ///< The last chunk (only set when pushed, and used when popped)
- };
+inline Chunk::Kind Chunk::getKind() const
+{
+ switch (getTag())
+ {
+ case RootChunk::kTag:
+ return Chunk::Kind::Root;
+ case ListChunk::kTag:
+ return Chunk::Kind::List;
+ default:
+ return Chunk::Kind::Data;
+ }
+}
- struct DataChunk : public Chunk
+inline Chunk::Type Chunk::getType() const
+{
+ auto tag = getTag();
+ switch (tag)
{
- typedef Chunk Super;
- SLANG_FORCE_INLINE static bool isType(const Chunk* chunk)
- {
- return chunk->m_kind == Kind::Data;
- }
+ case RootChunk::kTag:
+ case ListChunk::kTag:
+ return static_cast<ListChunk const*>(this)->getType();
+
+ default:
+ return tag;
+ }
+}
- /// Calculate a hash (not necessarily very fast)
- RiffHashCode calcHash() const;
- /// Calculate the payload size
- size_t calcPayloadSize() const;
+/// Cast a `Chunk` to a sub-type of `Chunk`.
+template<typename T>
+T* as(Chunk* chunk)
+{
+ if (!chunk)
+ return nullptr;
+ if (!T::_isChunkOfThisKind(chunk))
+ return nullptr;
+ return static_cast<T*>(chunk);
+}
- /// Copy the payload to dst. Dst must be at least the payload size.
- void getPayload(void* dst) const;
+/// Cast a `Chunk` to a sub-type of `Chunk`.
+template<typename T>
+T const* as(Chunk const* chunk)
+{
+ if (!chunk)
+ return nullptr;
+ if (!T::_isChunkOfThisKind(chunk))
+ return nullptr;
+ return static_cast<T const*>(chunk);
+}
- /// True if payloads contents is equal to data
- bool isEqual(const void* data, size_t count) const;
+/// A linked list where the elements are themselves the nodes.
+///
+template<typename T>
+struct InternallyLinkedList
+{
+public:
+ struct Node
+ {
+ public:
+ Node() {}
- /// Get single data payload.
- Data* getSingleData() const;
+ private:
+ friend struct InternallyLinkedList<T>;
+ T* _next = nullptr;
+ };
- /// Return as read helper
- RiffReadHelper asReadHelper() const;
+ struct Iterator
+ {
+ public:
+ Iterator() {}
- void init(FourCC fourCC)
+ Iterator(T* node)
+ : _node(node)
{
- Super::init(Kind::Data, fourCC);
- m_dataList = nullptr;
- m_endData = nullptr;
}
- Data* m_dataList; ///< List of 0 or more data items
- Data* m_endData; ///< The last data point
+ T* operator*() const { return _node; }
+
+ void operator++() { _node = static_cast<Node const*>(_node)->_next; }
+
+ bool operator!=(Iterator const& that) const { return _node != that._node; }
+
+ private:
+ T* _node = nullptr;
};
- class ScopeChunk
+ Iterator begin() { return Iterator(_first); }
+
+ Iterator end() { return Iterator(); }
+
+ T* getFirst() const { return _first; }
+
+ T* getLast() const { return _last; }
+
+ void add(T* element)
{
- public:
- ScopeChunk(RiffContainer* container, Chunk::Kind kind, FourCC fourCC)
- : m_container(container)
+ if (!_last)
{
- container->startChunk(kind, fourCC);
+ SLANG_ASSERT(_first == nullptr);
+
+ _first = element;
+ _last = element;
}
- ~ScopeChunk() { m_container->endChunk(); }
+ else
+ {
+ SLANG_ASSERT(_first != nullptr);
- private:
- RiffContainer* m_container;
- };
+ _last->_next = element;
+ _last = element;
+ }
+ }
+
+private:
+ T* _first = nullptr;
+ T* _last = nullptr;
+};
+
+/// A builder for a chunk in a RIFF.
+class ChunkBuilder : public InternallyLinkedList<ChunkBuilder>::Node
+{
+public:
+ /// Get the kind of the chunk being built.
+ Chunk::Kind getKind() const { return _kind; }
+
+ /// Get the type of the chunk being built.
+ Chunk::Type getType() const { return _type; }
+
+ /// Set the type of the chunk being built.
+ void setType(Chunk::Type type) { _type = type; }
+
+ /// Get the parent chunk of this chunk in the RIFF hierarchy.
+ ///
+ ListChunkBuilder* getParent() const { return _parent; }
- class Visitor
+ /// Get the RIFF builder that this chunk belongs to.
+ ///
+ RIFF::Builder* getRIFFBuilder() const { return _riffBuilder; }
+
+protected:
+ ChunkBuilder(
+ Chunk::Kind kind,
+ Chunk::Type type,
+ ListChunkBuilder* parent,
+ RIFF::Builder* riffBuilder)
+ : _kind(kind), _type(type), _parent(parent), _riffBuilder(riffBuilder)
{
- public:
- virtual SlangResult enterList(ListChunk* list) = 0;
- virtual SlangResult handleData(DataChunk* data) = 0;
- virtual SlangResult leaveList(ListChunk* list) = 0;
- };
+ }
+
+ ChunkBuilder(ChunkBuilder const&) = delete;
+ void operator=(ChunkBuilder const&) = delete;
+ MemoryArena& _getMemoryArena() const;
+
+private:
+ Chunk::Kind _kind = Chunk::Kind(-1);
+ Chunk::Type _type = 0;
+ ListChunkBuilder* _parent = nullptr;
+ Builder* _riffBuilder = nullptr;
+
+ // A cached total size for this chunk. This
+ // is only valid after `_updateCachedTotalSize()`
+ // has been called, and before any subsequent
+ // changes to the content of this chunk or any
+ // of its descendents in the hierarchy.
+ //
+ mutable Size _cachedTotalSize = 0;
+
+ Size _updateCachedTotalSize() const;
+
+ Size _getCachedTotalSize() const { return _cachedTotalSize; }
+
+ /// Write the binary representation of this chunk to the given `stream`
+ ///
+ /// Assumes that `_updateCachedTotalSize` has been used
+ /// so that the cached total size of this chunk is valid.
+ ///
+ Result _writeTo(Stream* stream) const;
+
+ friend struct Builder;
+};
- /// Add a complete data chunk
- void addDataChunk(FourCC dataFourCC, const void* data, size_t dataSizeInBytes);
+class ListChunkBuilder : public ChunkBuilder
+{
+public:
+ /// A list of child chunks.
+ using ChildList = InternallyLinkedList<ChunkBuilder>;
- /// Start a chunk
- void startChunk(Chunk::Kind kind, FourCC type);
+ /// Get the child chunks of this list.
+ ChildList getChildren() const { return _children; }
- /// Write data into a chunk (can only be inside a Kind::Data)
- void write(const void* data, size_t size);
+ /// Append a new data chunk to the current list chunk.
+ DataChunkBuilder* addDataChunk(Chunk::Type type);
- /// Adds an empty data block
- Data* addData();
- /// Set the payload on a data. Payload can be passed as nullptr, if it is no memory will be
- /// copied.
- void setPayload(Data* data, const void* payload, size_t size);
+ /// Append a new data chunk to the current list chunk.
+ ListChunkBuilder* addListChunk(Chunk::Type type);
- /// Move ownership to.
- /// NOTE! The payload *must* be deallocatable via 'free'
- void moveOwned(Data* data, void* payload, size_t size);
- /// Move unowned. The payload scope must last longer than the RiffContainer
- void setUnowned(Data* data, void* payload, size_t size);
+ /// Determine if a chunk is an instance of this kind.
+ static bool _isChunkOfThisKind(ChunkBuilder const* chunk)
+ {
+ return chunk->getKind() != Chunk::Kind::Data;
+ }
- /// End a chunk
- void endChunk();
+private:
+ ListChunkBuilder(Chunk::Type type, ListChunkBuilder* parent)
+ : ChunkBuilder(Chunk::Kind::List, type, parent, parent->getRIFFBuilder())
+ {
+ }
- /// Get the root
- ListChunk* getRoot() const { return m_rootList; }
+ friend struct RIFF::Builder;
- /// Get the current chunk
- Chunk* getCurrentChunk()
+ ListChunkBuilder(Chunk::Type type, RIFF::Builder* riffBuilder)
+ : ChunkBuilder(Chunk::Kind::Root, type, nullptr, riffBuilder)
{
- return m_dataChunk ? static_cast<Chunk*>(m_dataChunk) : static_cast<Chunk*>(m_listChunk);
}
- /// Reset the container
- void reset();
+ ChildList _children;
+};
+
+
+/// A builder for a data chunk in a RIFF.
+class DataChunkBuilder : public ChunkBuilder
+{
+public:
+ /// Append data to this chunk.
+ void addData(void const* data, Size size);
- /// true if has a root container, and nothing remains open
- bool isFullyConstructed()
+ /// Append data to this chunk.
+ template<typename T>
+ void addData(T const& value)
{
- return m_rootList && m_listChunk == nullptr && m_dataChunk == nullptr;
+ addData(&value, sizeof(value));
}
- /// Makes a data chunk contain a single contiguous data block
- Data* makeSingleData(DataChunk* dataChunk);
+ /// Append existing data to this chunk.
+ ///
+ /// The caller takes responsibility for ensuring that
+ /// the passed-in data pointer will remain valid for
+ /// the rest of the lifetime of the enclosing RIFF
+ /// builder.
+ ///
+ void addUnownedData(void const* data, size_t size);
+
+ //
+ // While the payload of a chunk in a RIFF file is
+ // contiguous, the payload of a `DataChunkBuilder`
+ // can span multiple different allocations, which
+ // this implementation refers to as *shards*.
+ //
+ // Each shard has a contiguous payload, and the
+ // `DataChunkBuilder` owns a list of shards. The
+ // logical payload of the data chunk is the
+ // concatenation of the payloads of its shards.
+ //
+
+ /// A contiguous range of bytes in a `RIFF::DataChunkBuilder`
+ class Shard : public InternallyLinkedList<Shard>::Node
+ {
+ public:
+ /// Get the payload of this shard.
+ void const* getPayload() const { return _payload; }
- /// Get the memory arena that is backing the storage of data
- MemoryArena& getMemoryArena() { return m_arena; }
+ /// Get the size of the payload of this shard.
+ Size getPayloadSize() const { return _payloadSize; }
- /// The if the list and sublists appear correct
- static bool isChunkOk(Chunk* chunk);
+ private:
+ friend class DataChunkBuilder;
- /// Traverses over chunk hierarchy and sets the sizes
- static void calcAndSetSize(Chunk* chunk);
+ Shard() {}
- /// Ctor
- RiffContainer();
+ void setPayload(void const* data, Size size)
+ {
+ _payload = data;
+ _payloadSize = size;
+ }
- void setCurrentChunk(Chunk* chunk);
+ void const* _payload = nullptr;
+ Size _payloadSize = 0;
+ };
-protected:
- void _addChunk(Chunk* chunk);
- ListChunk* _newListChunk(FourCC subType);
- DataChunk* _newDataChunk(FourCC type);
+ /// List of shards in a data chunk.
+ using ShardList = InternallyLinkedList<Shard>;
- ListChunk* m_rootList; ///< Root list
+ /// Get the list of shards that make up this chunk.
+ ShardList getShards() const { return _shards; }
- ListChunk* m_listChunk;
- DataChunk* m_dataChunk;
+ /// Determine if a chunk is an instance of this kind.
+ static bool _isChunkOfThisKind(ChunkBuilder const* chunk)
+ {
+ return chunk->getKind() == Chunk::Kind::Data;
+ }
- MemoryArena m_arena; ///< Can be used to use other owned blocks
+private:
+ friend class ListChunkBuilder;
+
+ DataChunkBuilder(Chunk::Type type, ListChunkBuilder* parent)
+ : ChunkBuilder(Chunk::Kind::Data, type, parent, parent->getRIFFBuilder())
+ {
+ }
+
+ Shard* _addShard();
+
+ ShardList _shards;
};
-// -----------------------------------------------------------------------------
template<typename T>
-T* as(RiffContainer::Chunk* chunk)
+T* as(ChunkBuilder* chunk)
{
- return chunk && T::isType(chunk) ? static_cast<T*>(chunk) : nullptr;
+ if (!chunk)
+ return nullptr;
+ if (!T::_isChunkOfThisKind(chunk))
+ return nullptr;
+ return static_cast<T*>(chunk);
}
-// -----------------------------------------------------------------------------
+
template<typename T>
-T* as(RiffContainer::Chunk* chunk, FourCC fourCC)
+T const* as(ChunkBuilder const* chunk)
{
- return chunk && chunk->m_fourCC == fourCC && T::isType(chunk) ? static_cast<T*>(chunk)
- : nullptr;
+ if (!chunk)
+ return nullptr;
+ if (!T::_isChunkOfThisKind(chunk))
+ return nullptr;
+ return static_cast<T const*>(chunk);
}
-struct RiffUtil
+/// A builder for a RIFF-structured file.
+///
+struct Builder
+{
+public:
+ /// Initialize a builder with an empty tree of chunks.
+ Builder();
+
+ /// Write the built hierarchy out to the given `stream`.
+ Result writeTo(Stream* stream);
+
+ /// Write the built hierarchy out as a blob.
+ Result writeToBlob(ISlangBlob** outBlob);
+
+ /// Get the root chunk of the RIFF being built.
+ ///
+ /// If a root chunk has not yet been added, returns `nullptr`.
+ ///
+ ListChunkBuilder* getRootChunk() const { return _rootChunk; }
+
+ /// Add a root chunk to the RIFF being built.
+ ///
+ /// There must not already be a root chunk.
+ ///
+ /// Returns the root chunk that was added.
+ ///
+ ListChunkBuilder* addRootChunk(Chunk::Type type);
+
+ /// Get the memory arena used for allocation.
+ ///
+ /// This arena is used for allocating all of the chunk
+ /// builders, as well as their data.
+ ///
+ /// Note: typical use cases should never need to
+ /// access this; it is part of the public API
+ /// primarily to enable some of the unit tests.
+ ///
+ MemoryArena& _getMemoryArena() { return _arena; }
+
+private:
+ Builder(Builder const&) = delete;
+ void operator=(Builder const&) = delete;
+
+ /// The root chunk of the RIFF.
+ ListChunkBuilder* _rootChunk = nullptr;
+
+ /// Arena to use for all allocations.
+ MemoryArena _arena;
+};
+
+/// A stateful cursor for a RIFF::Builder.
+///
+/// Represents a kind of pointer to a location in
+/// the hierarchy of RIFF chunks, and allows for
+/// new chunks to be added at that location.
+///
+struct BuildCursor
{
- typedef RiffContainer::Chunk Chunk;
- typedef RiffContainer::ListChunk ListChunk;
- typedef RiffContainer::DataChunk DataChunk;
+public:
+ /// Construct a cursor writing into no chunk.
+ BuildCursor();
+
+ /// Construct a cursor writing into the given `chunk`.
+ BuildCursor(ChunkBuilder* chunk);
+
+ /// Construct a cursor writing at the root of the given `builder`.
+ ///
+ /// Note that this is not the same as constructing a
+ /// cursor for the root chunk of `builder`. Instead, adding
+ /// a chunk via this cursor will add/create the root chunk
+ /// of the entire RIFF hierarchy.
+ ///
+ BuildCursor(Builder& builder);
+
+ /// Get the RIFF being written into, if any.
+ RIFF::Builder* getRIFFBuilder() const { return _riffBuilder; }
+
+ /// Get the current chunk being written into, if any.
+ ChunkBuilder* getCurrentChunk() const { return _currentChunk; }
+
+ /// Set the current chunk to write into.
+ void setCurrentChunk(ChunkBuilder* chunk);
+
+ /// Append a new data chunk to the current list chunk.
+ DataChunkBuilder* addDataChunk(Chunk::Type type);
+
+ /// Append a complete data chunk to the current list chunk.
+ void addDataChunk(Chunk::Type type, void const* data, size_t size);
+
+ /// Append a new data chunk to the current list chunk.
+ ListChunkBuilder* addListChunk(Chunk::Type type);
+
+ /// Begin a new data chunk as a child of the current list chunk.
+ ///
+ /// On return, the cursor will be set to write into the new chunk.
+ ///
+ void beginDataChunk(Chunk::Type type);
+
+ /// Begin a new list chunk as a child of the current list chunk.
+ ///
+ /// On return, the cursor will be set to write into the new chunk.
+ ///
+ void beginListChunk(Chunk::Type type);
+
+ /// End the current chunk.
+ ///
+ /// Sets the cursor to write to the parent of the chunk that was ended.
+ ///
+ void endChunk();
+
+ /// Append data onto the current data chunk.
+ void addData(void const* data, Size size);
+
+ /// Write data onto the current data chunk.
+ template<typename T>
+ void addData(T const& value)
+ {
+ addData(&value, sizeof(value));
+ }
+
+ /// Append existing data to the current data chunk.
+ ///
+ /// The caller takes responsibility for ensuring that
+ /// the passed-in data pointer will remain valid for
+ /// the rest of the lifetime of the enclosing RIFF
+ /// builder.
+ ///
+ void addUnownedData(void const* data, Size size);
+
+ /// Base type for RAII helpers to pair begin/end chunk calls.
+ struct ScopedChunk
+ {
+ protected:
+ ScopedChunk(BuildCursor& cursor)
+ : _cursor(cursor)
+ {
+ }
+
+ ~ScopedChunk() { _cursor.endChunk(); }
+
+ private:
+ BuildCursor& _cursor;
+ };
+
+ struct ScopedDataChunk : ScopedChunk
+ {
+ public:
+ ScopedDataChunk(BuildCursor& cursor, Chunk::Type type)
+ : ScopedChunk(cursor)
+ {
+ cursor.beginDataChunk(type);
+ }
+ };
+
+ struct ScopedListChunk : ScopedChunk
+ {
+ public:
+ ScopedListChunk(BuildCursor& cursor, Chunk::Type type)
+ : ScopedChunk(cursor)
+ {
+ cursor.beginListChunk(type);
+ }
+ };
+
+private:
+ RIFF::Builder* _riffBuilder = nullptr;
+ ChunkBuilder* _currentChunk = nullptr;
+};
+
+#define SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(CURSOR, TYPE) \
+ ::Slang::RIFF::BuildCursor::ScopedDataChunk _scopedRIFFBuilderDataChunk(CURSOR, TYPE)
- static int64_t calcChunkTotalSize(const RiffHeader& chunk);
+#define SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(CURSOR, TYPE) \
+ ::Slang::RIFF::BuildCursor::ScopedListChunk _scopedRIFFBuilderListChunk(CURSOR, TYPE)
- static SlangResult skip(const RiffHeader& chunk, Stream* stream, int64_t* remainingBytesInOut);
+} // namespace RIFF
- static SlangResult readChunk(Stream* stream, RiffHeader& outChunk);
+/// A simple helper for reading from a blob.
+///
+struct MemoryReader
+{
+ //
+ // TODO: This type should eventually either find
+ // a home somewhere that has nothing to do with
+ // RIFF files, or its usage in RIFF-related contexts
+ // should be replaced with other types.
+ //
- static SlangResult writeData(
- const RiffHeader* header,
- size_t headerSize,
- const void* payload,
- size_t payloadSize,
- Stream* out);
- static SlangResult readData(
- Stream* stream,
- RiffHeader* outHeader,
- size_t headerSize,
- List<uint8_t>& data);
+public:
+ /// Initialize a reader with no bytes remaining.
+ ///
+ MemoryReader() {}
- static SlangResult readPayload(Stream* stream, size_t size, void* outData, size_t& outReadSize);
+ /// Initialize a reader for the given blob.
+ MemoryReader(void const* data, Size size)
+ : _cursor(static_cast<Byte const*>(data)), _remainingSize(size)
+ {
+ }
- /// Read a header. Handles special case of list/riff types
- static SlangResult readHeader(Stream* stream, RiffListHeader& outHeader);
+ /// Read data into the given buffer.
+ ///
+ /// Fails if `size` is greater than the
+ /// amount of data remaining.
+ ///
+ SlangResult read(void* dst, Size size)
+ {
+ if (size > getRemainingSize())
+ {
+ return SLANG_FAIL;
+ }
+ ::memcpy(dst, _cursor, size);
+ _cursor += size;
+ _remainingSize -= size;
+ return SLANG_OK;
+ }
- /// True if the type is a container type
- static bool isListType(FourCC type)
+ /// Read data into the given value.
+ ///
+ /// Fails if `sizeof(dst)` is greater than the
+ /// amount of data remaining.
+ ///
+ template<typename T>
+ SlangResult read(T& dst)
{
- return type == RiffFourCC::kRiff || type == RiffFourCC::kList;
+ return read(&dst, sizeof(dst));
}
- /// Dump the chunk structure
- static void dump(Chunk* chunk, WriterHelper writer);
+ /// Skip over the given number of bytes.
+ ///
+ /// Fails if `size` is greater than the
+ /// amount of data remaining.
+ ///
+ SlangResult skip(Size size)
+ {
+ if (size > getRemainingSize())
+ {
+ return SLANG_FAIL;
+ }
+ _cursor += size;
+ _remainingSize -= size;
+ return SLANG_OK;
+ }
- /// Get the size taking into account padding
- static size_t getPadSize(size_t in) { return (in + kRiffPadMask) & ~size_t(kRiffPadMask); }
+ /// Get a pointer to the data that remains to be read.
+ Byte const* getRemainingData() const { return _cursor; }
- /// Write a chunk list and contents to a stream
- static SlangResult write(ListChunk* listChunk, bool isRoot, Stream* stream);
- /// Write a container to the stream
- static SlangResult write(RiffContainer* container, Stream* stream);
+ /// Get the size of the data that remains to be read.
+ Size getRemainingSize() const { return _remainingSize; }
- /// Read the stream into the container
- static SlangResult read(Stream* stream, RiffContainer& outContainer);
+private:
+ Byte const* _cursor = nullptr;
+ Size _remainingSize = 0;
};
} // namespace Slang
diff --git a/source/core/slang-semantic-version.cpp b/source/core/slang-semantic-version.cpp
index b05eb503f..a7697ad8f 100644
--- a/source/core/slang-semantic-version.cpp
+++ b/source/core/slang-semantic-version.cpp
@@ -93,6 +93,28 @@ void SemanticVersion::append(StringBuilder& buf) const
return bestVersion;
}
+bool SemanticVersion::isBackwardsCompatibleWith(const ThisType& otherVersion) const
+{
+ // Compatibility is not guaranteed across major revisions.
+ //
+ if (m_major != otherVersion.m_major)
+ return false;
+
+ // Within a given major revision, a version with a higher
+ // minor revision is backwards-compatible with one that
+ // has a lower minor revision, but not vice-versa.
+ //
+ if (m_minor < otherVersion.m_minor)
+ return false;
+
+ // If the major and minor revisions pass our check, then
+ // we consider it a match. Note that this intentionally
+ // doesn't check the path version at all.
+ //
+ return true;
+}
+
+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MatchSemanticVersion !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/* static */ SemanticVersion MatchSemanticVersion::findAnyBest(
diff --git a/source/core/slang-semantic-version.h b/source/core/slang-semantic-version.h
index 0bb467ab7..096606e08 100644
--- a/source/core/slang-semantic-version.h
+++ b/source/core/slang-semantic-version.h
@@ -12,7 +12,7 @@ struct SemanticVersion
{
typedef SemanticVersion ThisType;
- typedef uint64_t IntegerType;
+ typedef UInt64 RawValue;
SemanticVersion()
: m_major(0), m_minor(0), m_patch(0)
@@ -34,14 +34,22 @@ struct SemanticVersion
/// All zeros means nothing is set
bool isSet() const { return m_major || m_minor || m_patch; }
- IntegerType toInteger() const
+ RawValue getRawValue() const
{
- return (IntegerType(m_major) << 48) | (IntegerType(m_minor) << 32) | m_patch;
+ return (RawValue(m_major) << 48) | (RawValue(m_minor) << 32) | m_patch;
}
- void setFromInteger(IntegerType v)
+ void setRawValue(RawValue v)
{
set(int(v >> 48), int((v >> 32) & 0xffff), int(v & 0xffffffff));
}
+
+ static SemanticVersion fromRaw(RawValue rawValue)
+ {
+ SemanticVersion result;
+ result.setRawValue(rawValue);
+ return result;
+ }
+
void set(int major, int minor, int patch = 0)
{
SLANG_ASSERT(major >= 0 && minor >= 0 && patch >= 0);
@@ -52,7 +60,7 @@ struct SemanticVersion
}
/// Get hash value
- HashCode getHashCode() const { return Slang::getHashCode(toInteger()); }
+ HashCode getHashCode() const { return Slang::getHashCode(getRawValue()); }
static SlangResult parse(const UnownedStringSlice& value, SemanticVersion& outVersion);
static SlangResult parse(
@@ -63,16 +71,29 @@ struct SemanticVersion
static ThisType getEarliest(const ThisType* versions, Count count);
static ThisType getLatest(const ThisType* versions, Count count);
+ /// Determine if this version is backwards-compatible with `otherVersion`.
+ ///
+ /// Examples of when to apply this:
+ ///
+ /// * Code for this version is asked to load data produced by `otherVersion`.
+ ///
+ /// * Code for this version is asked if it provides the API of `otherVersion`.
+ ///
+ /// This uses the rules of semantic versioning, so it should only
+ /// be applied to versions that obey those rules.
+ ///
+ bool isBackwardsCompatibleWith(const ThisType& otherVersion) const;
+
void append(StringBuilder& buf) const;
- bool operator>(const ThisType& rhs) const { return toInteger() > rhs.toInteger(); }
- bool operator>=(const ThisType& rhs) const { return toInteger() >= rhs.toInteger(); }
+ bool operator>(const ThisType& rhs) const { return getRawValue() > rhs.getRawValue(); }
+ bool operator>=(const ThisType& rhs) const { return getRawValue() >= rhs.getRawValue(); }
- bool operator<(const ThisType& rhs) const { return toInteger() < rhs.toInteger(); }
- bool operator<=(const ThisType& rhs) const { return toInteger() <= rhs.toInteger(); }
+ bool operator<(const ThisType& rhs) const { return getRawValue() < rhs.getRawValue(); }
+ bool operator<=(const ThisType& rhs) const { return getRawValue() <= rhs.getRawValue(); }
- bool operator==(const ThisType& rhs) const { return toInteger() == rhs.toInteger(); }
- bool operator!=(const ThisType& rhs) const { return toInteger() != rhs.toInteger(); }
+ bool operator==(const ThisType& rhs) const { return getRawValue() == rhs.getRawValue(); }
+ bool operator!=(const ThisType& rhs) const { return getRawValue() != rhs.getRawValue(); }
uint16_t m_major;
uint16_t m_minor;
diff --git a/source/core/slang-stream.h b/source/core/slang-stream.h
index 78664c17a..0a5adbde8 100644
--- a/source/core/slang-stream.h
+++ b/source/core/slang-stream.h
@@ -303,6 +303,12 @@ struct StreamUtil
/// Appends all bytes that can be read from stream into bytes
static SlangResult readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes);
+ /// Appends all bytes that can be read from stream into bytes
+ static SlangResult readAll(Stream* stream, List<Byte>& ioBytes)
+ {
+ return readAll(stream, 0, ioBytes);
+ }
+
/// Read as much as can be read until a 0 sized read, or an error and append onto ioBytes
/// Read size controls the size of each buffer read. Passing 0, will use the default read size.
static SlangResult read(Stream* stream, size_t readSize, List<Byte>& ioBytes);
diff --git a/source/core/slang-text-io.cpp b/source/core/slang-text-io.cpp
index 9cc3896fd..d0f7ca551 100644
--- a/source/core/slang-text-io.cpp
+++ b/source/core/slang-text-io.cpp
@@ -125,7 +125,7 @@ SlangResult StreamReader::readBuffer()
return SLANG_OK;
}
-char StreamReader::readBufferChar()
+Byte StreamReader::readBufferByte()
{
if (m_index < m_buffer.getCount())
{
diff --git a/source/core/slang-text-io.h b/source/core/slang-text-io.h
index 7c13ebec8..727634c1b 100644
--- a/source/core/slang-text-io.h
+++ b/source/core/slang-text-io.h
@@ -74,23 +74,23 @@ protected:
{
case CharEncodeType::UTF8:
{
- codePoint = getUnicodePointFromUTF8([&]() -> Byte { return readBufferChar(); });
+ codePoint = getUnicodePointFromUTF8([&]() -> Byte { return readBufferByte(); });
break;
}
case CharEncodeType::UTF16:
{
- codePoint = getUnicodePointFromUTF16([&]() -> Byte { return readBufferChar(); });
+ codePoint = getUnicodePointFromUTF16([&]() -> Byte { return readBufferByte(); });
break;
}
case CharEncodeType::UTF16Reversed:
{
codePoint =
- getUnicodePointFromUTF16Reversed([&]() -> Byte { return readBufferChar(); });
+ getUnicodePointFromUTF16Reversed([&]() -> Byte { return readBufferByte(); });
break;
}
case CharEncodeType::UTF32:
{
- codePoint = getUnicodePointFromUTF32([&]() -> Byte { return readBufferChar(); });
+ codePoint = getUnicodePointFromUTF32([&]() -> Byte { return readBufferByte(); });
break;
}
}
@@ -99,11 +99,11 @@ protected:
}
private:
- char readBufferChar();
+ Byte readBufferByte();
SlangResult readBuffer();
RefPtr<Stream> m_stream;
- List<char> m_buffer;
+ List<Byte> m_buffer;
CharEncodeType m_encodingType = CharEncodeType::UTF8;
CharEncoding* m_encoding = nullptr;
diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h
index f85bebc21..6ec776ddf 100644
--- a/source/core/slang-writer.h
+++ b/source/core/slang-writer.h
@@ -188,7 +188,7 @@ public:
SLANG_OVERRIDE;
/// Ctor
- StringWriter(StringBuilder* builder, WriterFlags flags)
+ StringWriter(StringBuilder* builder, WriterFlags flags = 0)
: Parent(flags), m_builder(builder)
{
}