Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5d40fc4
Avoid crashes in fuzz-tests due to input strings being too long.
jmarantz Oct 27, 2019
8f9ab93
Eliminate 64k limit on # of segments in a real symbol-table stat-name…
jmarantz Oct 28, 2019
beb4684
some cleanup
jmarantz Oct 28, 2019
d143365
Share helper methods for encoding/decoding symbols and sizes.
jmarantz Oct 29, 2019
5ef1a49
cleanup
jmarantz Oct 29, 2019
c425551
typo
jmarantz Oct 29, 2019
f680ccc
fix confusing about whether the encoding-length was the number of byt…
jmarantz Oct 29, 2019
9fee9f5
fix over-aggressive inequality for fake symbol tables
jmarantz Oct 29, 2019
184fbed
don't recompute the number of bytes consumed.
jmarantz Oct 29, 2019
6ac3dee
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Oct 29, 2019
9c62a9e
remove dynamo-stats test change which is not needed
jmarantz Oct 29, 2019
4482ca7
use totalSizeBytes to simplify some code.
jmarantz Oct 29, 2019
14220bb
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 1, 2019
4d345d0
add MemBlock abstraction to isolate risky memory operations.
jmarantz Nov 2, 2019
5b6f0db
reduce need to do byte access to the memory buffers.
jmarantz Nov 2, 2019
70ffe33
remove remainder of memcpy and pointer arithmetic outside MemBlock cl…
jmarantz Nov 2, 2019
9f1eae7
format
jmarantz Nov 2, 2019
cf33e18
some cleanup -- still needs more unit tests.
jmarantz Nov 2, 2019
14b8740
remove superfluous methods.
jmarantz Nov 2, 2019
eb6eb51
add unit test.
jmarantz Nov 2, 2019
6d4b8bc
improve class comment.
jmarantz Nov 2, 2019
cf71f0b
rename MemBlock to MemBlockBuilder.
jmarantz Nov 2, 2019
256a857
Rename MemBlock to MemBlockBuilder to better reflect intended usage m…
jmarantz Nov 2, 2019
cb17d28
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 2, 2019
7b3150e
format.
jmarantz Nov 2, 2019
c4ddd4a
improve comments and function naming.
jmarantz Nov 3, 2019
c728651
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 3, 2019
4824977
guard memcpy against 0-byte calls, which show up as asan failures.
jmarantz Nov 3, 2019
3c9b184
Merge branch 'stats-fuzzer-not-too-long' of github.com:jmarantz/envoy…
jmarantz Nov 14, 2019
5f4ea86
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 14, 2019
64c0af4
RELEASE_ASSERT rather than ASSERT, use non-constructing allocation.
jmarantz Nov 17, 2019
06e3fbc
add test to make sure we don't construct anything.
jmarantz Nov 18, 2019
b2131df
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 18, 2019
a281e28
update gold values
jmarantz Nov 18, 2019
cd98dd2
Go back to new-ing an array and clean up.
jmarantz Nov 19, 2019
cf7b451
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 19, 2019
8069e89
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 20, 2019
9ae8144
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Nov 22, 2019
922a860
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Dec 10, 2019
6dcc218
typo and gold values update.
jmarantz Dec 10, 2019
006b5c7
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Dec 14, 2019
aa70e5e
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Dec 15, 2019
9033ac6
post-merge cleanup
jmarantz Dec 15, 2019
d19d722
more cleanups
jmarantz Dec 15, 2019
ae1da1b
Cleanup lifetime issues in test infrastructure.
jmarantz Dec 15, 2019
bc9871e
update MemBlockBuilder interface change
jmarantz Dec 16, 2019
ae0b1dd
Merge branch 'master' into stats-fuzzer-not-too-long
jmarantz Dec 16, 2019
f4ef7c6
popuplate -> setCacacity
jmarantz Dec 16, 2019
3edfac7
add direct fuzzing of the encoder.
jmarantz Dec 18, 2019
4f5be6c
Share testing infrastructure between fuzz-tests and unit-tests.
jmarantz Dec 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions source/common/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ envoy_cc_library(
deps = [":assert_lib"],
)

envoy_cc_library(
name = "mem_block_builder_lib",
hdrs = ["mem_block_builder.h"],
deps = [":assert_lib"],
)

# Contains macros and helpers for dumpState utilities
envoy_cc_library(
name = "dump_state_utils",
Expand Down
136 changes: 136 additions & 0 deletions source/common/common/mem_block_builder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#pragma once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we split this out into a distinct PR for review? Thanks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done --> #9306


#include <memory>
#include <vector>

#include "common/common/assert.h"

namespace Envoy {

// Manages a block of raw memory for objects of type T. T is generally expected
// to be a POD, where it makes sense to memcpy over it. This class carries extra
// member variables for tracking size, and a write-pointer to support safe
// appends.
//
// MemBlockBuilder is used to safely write blocks of data into a memory
// buffer. Due to two extra member variables, it is not optimal for storing in
// data structures. The intended usage model is to release the raw assembled
// memory block from the MemBlockBuilder for efficient storage.
//
// The goal for this class is to provide a usage model to replace direct usage
// of memcpy with a pattern that is easy to validate for correctness by
// inspection, asserts, and fuzzing, but when compiled for optimization is
// roughly as efficient as raw memcpy.
template <class T> class MemBlockBuilder {
public:
// Constructs a MemBlockBuilder allowing for 'capacity' instances of T.
explicit MemBlockBuilder(uint64_t capacity)
: data_(std::make_unique<T[]>(capacity)), capacity_(capacity), capacity_remaining_(capacity),
write_ptr_(data_.get()) {}
MemBlockBuilder() : capacity_(0), capacity_remaining_(0), write_ptr_(nullptr) {}

/**
* Populates (or repopulates) the MemBlockBuilder to make it the specified
* capacity. This does not have resize semantics; when populate() is called any
* previous contents are erased.
*
* @param capcity The number of memory elements to allocate.
*/
void populate(uint64_t capacity) {
data_ = std::make_unique<T[]>(capacity);
capacity_ = capacity;
capacity_remaining_ = capacity;
write_ptr_ = data_.get();
}

/**
* @return the capacity.
*/
uint64_t capacity() const { return capacity_; }

/**
* Appends a single object of type T, moving an internal write-pointer
* forward. Asserts that there is room to write the object when compiled
* for debug.
*
* @param object the object to append.
*/
void appendOne(T object) {
RELEASE_ASSERT(capacity_remaining_ >= 1, "insufficient capacity");
ASSERT(write_ptr_ < (data_.get() + capacity_));
*write_ptr_++ = object;
--capacity_remaining_;
}

/**
* Appends raw data, moving an internal write-pointer forward. Asserts
* that there is room to write the block when compiled for debug. It is
* the caller's responsibility to ensure that the input data is valid.
*
* @param data The block of objects to insert.
* @param size The number of elements in the block.
*/
void appendData(const T* data, uint64_t size) {
RELEASE_ASSERT(capacity_remaining_ >= size, "insufficient capacity");
ASSERT((write_ptr_ + size) <= (data_.get() + capacity_));
if (size != 0) {
memcpy(write_ptr_, data, size * sizeof(T));
}
write_ptr_ += size;
capacity_remaining_ -= size;
}

/**
* Appends the contents of another memory block to this one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a debug-only check for available capacity in the underlying implementation I think it would be good to reiterate at least some of the above comment regarding caller's responsibility that the appended MemBlockBuilder fits and the fact that it is a debug only assertion that guards against overrun.

(Perhaps also it should be a release assert? These are by definition risky operations.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to release-assert.

*
* @param src the block to append.
*/
void appendBlock(const MemBlockBuilder& src) { appendData(src.data_.get(), src.size()); }

/**
* @return the number of elements remaining in the MemBlockBuilder.
*/
uint64_t capacityRemaining() const { return capacity_remaining_; }

/**
* Empties the contents of this.
*/
void reset() {
data_.reset();
capacity_ = 0;
capacity_remaining_ = 0;
write_ptr_ = nullptr;
}

/**
* Returns the underlying storage as a unique pointer, clearing this.
*
* @return the transferred storage.
*/
std::unique_ptr<T[]> release() {
write_ptr_ = nullptr;
capacity_ = 0;
capacity_remaining_ = 0;
return std::move(data_);
}

/**
* @return the populated data as a vector.
*
* This is exposed to help with unit testing.
*/
std::vector<T> toVector() const { return std::vector<T>(data_.get(), write_ptr_); }

/**
* @return The number of elements the have been added to the builder.
*/
uint64_t size() const { return write_ptr_ - data_.get(); }

private:
std::unique_ptr<T[]> data_;
uint64_t capacity_;
uint64_t capacity_remaining_;
T* write_ptr_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would appreciate a comment here indicate that whenever it is not nullptr the write_ptr_ is guaranteed to be within data_. This also seems like a reasonable thing to ASSERT. I continue to believe maybe the other assert should be a RELEASE_ASSERT.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

};

} // namespace Envoy
1 change: 1 addition & 0 deletions source/common/stats/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ envoy_cc_library(
"//include/envoy/stats:symbol_table_interface",
"//source/common/common:assert_lib",
"//source/common/common:logger_lib",
"//source/common/common:mem_block_builder_lib",
"//source/common/common:stack_array",
"//source/common/common:thread_lib",
"//source/common/common:utility_lib",
Expand Down
35 changes: 15 additions & 20 deletions source/common/stats/fake_symbol_table_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,36 +64,31 @@ class FakeSymbolTableImpl : public SymbolTable {
// than 256 of them.
RELEASE_ASSERT(num_names < 256, "Maximum number elements in a StatNameList exceeded");

// First encode all the names. The '1' here represents the number of
// names. The num_names * StatNameSizeEncodingBytes reserves space for the
// lengths of each name.
size_t total_size_bytes = 1 + num_names * StatNameSizeEncodingBytes;

size_t total_size_bytes = 1; /* one byte for holding the number of names */
for (uint32_t i = 0; i < num_names; ++i) {
total_size_bytes += names[i].size();
size_t name_size = names[i].size();
total_size_bytes += SymbolTableImpl::Encoding::totalSizeBytes(name_size);
}

// Now allocate the exact number of bytes required and move the encodings
// into storage.
auto storage = std::make_unique<Storage>(total_size_bytes);
uint8_t* p = &storage[0];
*p++ = num_names;
MemBlockBuilder<uint8_t> mem_block(total_size_bytes);
mem_block.appendOne(num_names);
for (uint32_t i = 0; i < num_names; ++i) {
auto& name = names[i];
size_t sz = name.size();
p = SymbolTableImpl::writeLengthReturningNext(sz, p);
SymbolTableImpl::Encoding::appendEncoding(sz, mem_block);
if (!name.empty()) {
memcpy(p, name.data(), sz * sizeof(uint8_t));
p += sz;
mem_block.appendData(reinterpret_cast<const uint8_t*>(name.data()), sz);
}
}

// This assertion double-checks the arithmetic where we computed
// total_size_bytes. After appending all the encoded data into the
// allocated byte array, we should wind up with a pointer difference of
// total_size_bytes from the beginning of the allocation.
ASSERT(p == &storage[0] + total_size_bytes);
list.moveStorageIntoList(std::move(storage));
// allocated byte array, we should have exhausted all the memory
// we though we needed.
ASSERT(mem_block.capacityRemaining() == 0);
list.moveStorageIntoList(mem_block.release());
}

std::string toString(const StatName& stat_name) const override {
Expand Down Expand Up @@ -143,10 +138,10 @@ class FakeSymbolTableImpl : public SymbolTable {

StoragePtr encodeHelper(absl::string_view name) const {
name = StringUtil::removeTrailingCharacters(name, '.');
auto bytes = std::make_unique<Storage>(name.size() + StatNameSizeEncodingBytes);
uint8_t* buffer = SymbolTableImpl::writeLengthReturningNext(name.size(), bytes.get());
memcpy(buffer, name.data(), name.size());
return bytes;
MemBlockBuilder<uint8_t> mem_block(SymbolTableImpl::Encoding::totalSizeBytes(name.size()));
SymbolTableImpl::Encoding::appendEncoding(name.size(), mem_block);
mem_block.appendData(reinterpret_cast<const uint8_t*>(name.data()), name.size());
return mem_block.release();
}
};

Expand Down
Loading