// Copyright 2023 The Centipede Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This library defines the concepts "fuzzing feature" and "feature domain". // It is used by Centipede, and it can be used by fuzz runners to // define their features in a way most friendly to Centipede. // Fuzz runners do not have to use this file nor to obey the rules defined here. // But using this file and following its rules is the simplest way if you want // Centipede to understand the details about the features generated by the // runner. #ifndef THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_ #define THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_ #include #include #include #include // WARNING!!!: Be very careful with what STL headers or other dependencies you // add here. This header needs to remain mostly bare-bones so that we can // include it into runner. #include "absl/base/const_init.h" namespace fuzztest::internal { // TODO(kcc): replace the standalone ForEachNonZeroByte with code from here. // TODO(kcc): ConcurrentByteSet is an unoptimized single-layer byte set. // Implement multi-layer byte set(s). // A fixed-size byte set containing kSize bytes, kSize must be a multiple of 64. // Set() can be called concurrently with another Set(), other uses should be // synchronized externally. // Intended usage is to call ForEachNonZeroByte() from one thread. // // IMPORTANT!!! Objects of this class should only be constructed with static // storage duration. This is because the class has intentionally uninitialized // direct and transitive data members that rely on static initialization in the // compiled process image. template class ConcurrentByteSet { public: static constexpr size_t kSizeInBytes = kSize; // kSize must be multiple of this. static constexpr size_t kSizeMultiple = 64; static_assert((kSize % kSizeMultiple) == 0); // Creates a ConcurrentByteSet with static storage duration. explicit constexpr ConcurrentByteSet(absl::ConstInitType) {} // Clears the set. void clear() { memset(bytes_, 0, sizeof(bytes_)); } // Sets element `idx` to `value`. `idx` must be <= kSize. // Can be called concurrently. void Set(size_t idx, uint8_t value) { if (idx >= kSize) __builtin_trap(); __atomic_store_n(&bytes_[idx], value, __ATOMIC_RELAXED); } // Performs a saturated increment of element `idx`. void SaturatedIncrement(size_t idx) { if (idx >= kSize) __builtin_trap(); uint8_t counter = __atomic_load_n(&bytes_[idx], __ATOMIC_RELAXED); if (counter != 255) __atomic_store_n(&bytes_[idx], counter + 1, __ATOMIC_RELAXED); } // Calls `action(index, value)` for every {index,value} of a non-zero byte in // the set, then sets all those bytes to zero. // `from` and `to` set the range of elements to iterate, both must be // multiples of kSizeMultiple. void ForEachNonZeroByte(const std::function &action, size_t from = 0, size_t to = kSize) { using word_t = uintptr_t; constexpr size_t kWordSize = sizeof(word_t); if (from % kSizeMultiple) __builtin_trap(); if (to % kSizeMultiple) __builtin_trap(); if (to > kSize) __builtin_trap(); // Iterate one word at a time. for (uint8_t *ptr = &bytes_[from], *end = &bytes_[to]; ptr < end; ptr += kWordSize) { word_t word; __builtin_memcpy(&word, ptr, kWordSize); if (!word) continue; __builtin_memset(ptr, 0, kWordSize); // This loop assumes little-endianness. (Tests will break on big-endian). for (size_t pos = 0; pos < kWordSize; pos++) { uint8_t value = word >> (pos * CHAR_BIT); // lowest byte is taken. if (value) action(ptr - &bytes_[0] + pos, value); } } } private: // No initializer for performance (`kSize` can be quite large). Relies on // static initialization in the process image (see the class comment). uint8_t bytes_[kSize] __attribute__((aligned(64))); }; // Similar to ConcurrentByteSet, but consists of two layers, upper and lower. // The size of the lower layer is a multiple of the size of the upper layer. // Set() writes 1 to an element in the upper layer and then writes `value` to an // element of the lower value. This allows ForEachNonZeroByte() to // skip sub-regions of lower layer that were not written to. Otherwise, the // interface and the behaviour is equivalent to ConcurrentByteSet. template > class LayeredConcurrentByteSet { public: static constexpr size_t kSizeInBytes = kSize; static constexpr size_t kSizeMultiple = Lower::kSizeMultiple * Upper::kSizeMultiple; static_assert(kSize == Lower::kSizeInBytes); LayeredConcurrentByteSet() = default; // Creates a LayeredConcurrentByteSet with static storage duration. explicit constexpr LayeredConcurrentByteSet(absl::ConstInitType) : upper_layer_(absl::kConstInit), lower_layer_(absl::kConstInit) {} void clear() { upper_layer_.clear(); lower_layer_.clear(); } void Set(size_t idx, uint8_t value) { if (idx >= kSize) __builtin_trap(); upper_layer_.Set(idx / kLayerRatio, 1); lower_layer_.Set(idx, value); } void SaturatedIncrement(size_t idx) { if (idx >= kSize) __builtin_trap(); upper_layer_.Set(idx / kLayerRatio, 1); lower_layer_.SaturatedIncrement(idx); } void ForEachNonZeroByte(const std::function &action, size_t from = 0, size_t to = kSize) { if (to > kSize) __builtin_trap(); if (from % kSizeMultiple) __builtin_trap(); if (to % kSizeMultiple) __builtin_trap(); size_t upper_from = from / kLayerRatio; size_t upper_to = to / kLayerRatio; upper_layer_.ForEachNonZeroByte( [&](size_t idx, uint8_t value) { size_t lower_from = idx * kLayerRatio; size_t lower_to = lower_from + kLayerRatio; lower_layer_.ForEachNonZeroByte(action, lower_from, lower_to); }, upper_from, upper_to); } private: Upper upper_layer_; Lower lower_layer_; static constexpr size_t kLayerRatio = Lower::kSizeInBytes / Upper::kSizeInBytes; static_assert((Lower::kSizeInBytes % Upper::kSizeInBytes) == 0); }; // Two-layer ConcurrentByteSet() with upper layer 64x smaller than the lower. template class TwoLayerConcurrentByteSet : public LayeredConcurrentByteSet> { public: // Creates a TwoLayerConcurrentByteSet with static storage duration. explicit constexpr TwoLayerConcurrentByteSet(absl::ConstInitType) : LayeredConcurrentByteSet>( absl::kConstInit) {} }; } // namespace fuzztest::internal #endif // THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_