Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make SCALE header-only and add sanitizer options for tests #34

Merged
merged 5 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
159 changes: 105 additions & 54 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,89 +6,140 @@

cmake_minimum_required(VERSION 3.12)

option(JAM_COMPATIBLE "Build compatible with JAM-codec" OFF)
option(CUSTOM_CONFIG_SUPPORT "Support custom config of coder" OFF)
set(MAX_AGGREGATE_FIELDS 20 CACHE STRING "Max number of aggregates fields (1..1000); for generation")

option(BUILD_TESTS "Whether to include the test suite in build" OFF)

if (PACKAGE_MANAGER)
if(PACKAGE_MANAGER NOT MATCHES "^(hunter|vcpkg)$")
message(FATAL_ERROR "PACKAGE_MANAGER must be set to 'hunter', 'vcpkg' or isn't set")
endif ()
else ()
set(PACKAGE_MANAGER "hunter")
if (CMAKE_TOOLCHAIN_FILE)
get_filename_component(ACTUAL_NAME ${CMAKE_TOOLCHAIN_FILE} NAME)
if(ACTUAL_NAME STREQUAL "vcpkg.cmake")
message(STATUS "vcpkg will be used because vcpkg.cmake has found")
set(PACKAGE_MANAGER "vcpkg")
endif ()
endif ()
endif ()
# Select package manager
if(PACKAGE_MANAGER)
if(PACKAGE_MANAGER NOT MATCHES "^(hunter|vcpkg)$")
message(FATAL_ERROR "PACKAGE_MANAGER must be set to 'hunter', 'vcpkg' or isn't set")
endif()
else()
set(PACKAGE_MANAGER "hunter")
if(CMAKE_TOOLCHAIN_FILE)
get_filename_component(ACTUAL_NAME ${CMAKE_TOOLCHAIN_FILE} NAME)
if(ACTUAL_NAME STREQUAL "vcpkg.cmake")
message(STATUS "vcpkg will be used because vcpkg.cmake has found")
set(PACKAGE_MANAGER "vcpkg")
endif()
endif()
endif()
message(STATUS "Selected package manager: ${PACKAGE_MANAGER}")

if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.27")
cmake_policy(SET CMP0144 NEW)
endif ()
# Default values of options
set(JAM_COMPATIBLE_DEFAULT OFF)
set(CUSTOM_CONFIG_SUPPORT_DEFAULT OFF)
set(BUILD_TESTS_DEFAULT OFF)
set(ASAN_DEFAULT OFF)
set(TSAN_DEFAULT OFF)
set(UBSAN_DEFAULT OFF)

# Adjust value by vcpkg manifest if any
if(PACKAGE_MANAGER STREQUAL "vcpkg")
if("jam-compatibility" IN_LIST VCPKG_MANIFEST_FEATURES)
set(JAM_COMPATIBLE_DEFAULT ON)
endif()
if("configurable-coding" IN_LIST VCPKG_MANIFEST_FEATURES)
set(CUSTOM_CONFIG_SUPPORT_DEFAULT ON)
endif()
if("scale-tests" IN_LIST VCPKG_MANIFEST_FEATURES)
set(BUILD_TESTS_DEFAULT ON)
endif()
if("asan" IN_LIST VCPKG_MANIFEST_FEATURES)
set(ASAN_DEFAULT ON)
endif()
if("tsan" IN_LIST VCPKG_MANIFEST_FEATURES)
set(TSAN_DEFAULT ON)
endif()
if("ubsan" IN_LIST VCPKG_MANIFEST_FEATURES)
set(UBSAN_DEFAULT ON)
endif()
endif()

if (PACKAGE_MANAGER STREQUAL "hunter")
include("cmake/Hunter/init.cmake")
endif ()
# Init options
option(JAM_COMPATIBLE "Build compatible with JAM-codec" ${JAM_COMPATIBLE_DEFAULT})
option(CUSTOM_CONFIG_SUPPORT "Support custom config of coder" ${CUSTOM_CONFIG_SUPPORT_DEFAULT})
option(BUILD_TESTS "Whether to include the test suite in build" ${BUILD_TESTS_DEFAULT})
option(ASAN "Build tests with address sanitizer" ${ASAN_DEFAUL})
option(TSAN "Build tests with thread sanitizer" ${TSAN_DEFAUL})
option(UBSAN "Build tests with undefined behavior sanitizer" ${UBSAN_DEFAUL})
if(ASAN OR TSAN OR UBSAN)
if(NOT BUILD_TESTS)
message(FATAL_ERROR "Sanitizer required but build of test is not set")
endif()
endif()

if(BUILD_TESTS)
if (PACKAGE_MANAGER STREQUAL "vcpkg")
list(APPEND VCPKG_MANIFEST_FEATURES scale-tests)
endif()
# Adjust vcpkg features by custom defined option (for deploy possible dependencies)
if(PACKAGE_MANAGER STREQUAL "vcpkg")
if(BUILD_TESTS AND NOT "scale-tests" IN_LIST VCPKG_MANIFEST_FEATURES)
list(APPEND VCPKG_MANIFEST_FEATURES "scale-tests")
endif()
endif()

project(Scale LANGUAGES CXX VERSION 2.0.0)
set(MAX_AGGREGATE_FIELDS 20 CACHE STRING "Max number of aggregates fields (1..1000); for generation")

if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.27")
cmake_policy(SET CMP0144 NEW)
endif()

if(PACKAGE_MANAGER STREQUAL "hunter")
include("cmake/Hunter/init.cmake")
else()
set(HUNTER_ENABLED OFF)
endif()

project(Scale LANGUAGES CXX VERSION 2.0.1)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if (PACKAGE_MANAGER STREQUAL "hunter")
hunter_add_package(Boost)
find_package(Boost)
if(PACKAGE_MANAGER STREQUAL "hunter")
hunter_add_package(Boost)
find_package(Boost)
else()
find_package(Boost CONFIG REQUIRED COMPONENTS endian multiprecision)
endif ()
find_package(Boost CONFIG REQUIRED COMPONENTS endian multiprecision)
endif()

if (PACKAGE_MANAGER STREQUAL "hunter")
hunter_add_package(qtils)
endif ()
if(PACKAGE_MANAGER STREQUAL "hunter")
hunter_add_package(qtils)
endif()
find_package(qtils CONFIG REQUIRED)

SET(JAM_COMPATIBILITY_ENABLED "${JAM_COMPATIBLE}")
set(JAM_COMPATIBILITY_ENABLED "${JAM_COMPATIBLE}")
set(CUSTOM_CONFIG_ENABLED "${CUSTOM_CONFIG_SUPPORT}")
configure_file("${CMAKE_SOURCE_DIR}/include/scale/definitions.hpp.in" "${CMAKE_BINARY_DIR}/include/scale/definitions.hpp")

if(ASAN)
message(STATUS "Address sanitizer will be used")
add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer)
add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer)
endif()
if(TSAN)
message(STATUS "Thread sanitizer will be used")
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
add_link_options(-fsanitize=thread -fno-omit-frame-pointer)
endif()
if(UBSAN)
message(STATUS "Undefined behavior sanitizer will be used")
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer)
add_link_options(-fsanitize=undefined -fno-omit-frame-pointer)
endif()

add_subdirectory(src)

if (BUILD_TESTS)
enable_testing()
add_subdirectory(test ${CMAKE_BINARY_DIR}/test_bin)
endif ()
if(BUILD_TESTS)
enable_testing()
add_subdirectory(test ${CMAKE_BINARY_DIR}/test_bin)
endif()

###############################################################################
# INSTALLATION
###############################################################################

include(GNUInstallDirs)

install(TARGETS scale EXPORT scaleConfig
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
)

install(TARGETS scale_append EXPORT scaleConfig
install(
TARGETS scale EXPORT scaleConfig
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
Expand Down
10 changes: 5 additions & 5 deletions include/scale/bit_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1263,16 +1263,16 @@ namespace scale {
if (sbf_ and new_size > arr_.size() * CHAR_BIT) {
switch_to_vector();
}
if (not sbf_) {
if (new_size != vec_.size() * CHAR_BIT) {
vec_.resize((new_size + CHAR_BIT - 1) / CHAR_BIT, 0);
}
}
if (new_size <= size_) {
auto data = sbf_ ? arr_.data() : vec_.data();
data[new_size / CHAR_BIT] &=
static_cast<uint8_t>(-1) >> (CHAR_BIT - (new_size % CHAR_BIT));
}
if (not sbf_) {
if (new_size != vec_.size() * CHAR_BIT) {
vec_.resize((new_size + CHAR_BIT - 1) / CHAR_BIT, 0);
}
}
if (sbf_ and new_size > size_) {
for (auto i = (size_ + CHAR_BIT - 1) / CHAR_BIT;
i <= new_size / CHAR_BIT;
Expand Down
50 changes: 48 additions & 2 deletions include/scale/encode_append.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,52 @@ namespace scale {
* \param self_encoded
* @return success if input was appended to self_encoded, failure otherwise
*/
outcome::result<void> append_or_new_vec(std::vector<uint8_t> &self_encoded,
ConstSpanOfBytes input);
inline outcome::result<void> append_or_new_vec(
std::vector<uint8_t> &self_encoded, ConstSpanOfBytes input) {
EncodeOpaqueValue opaque_value{.v = input};

// No data present, just encode the given input data.
if (self_encoded.empty()) {
backend::ToBytes encoder(self_encoded);
encode(std::vector{opaque_value}, encoder);
return outcome::success();
}

// Take old size, calculate old size length and encode new size
OUTCOME_TRY(size, impl::memory::decode<Compact<uint32_t>>(self_encoded));
auto old_size = untagged(size);
auto new_size = old_size + 1;
auto encoded_old_size_len = lengthOfEncodedCompactInteger(old_size);
OUTCOME_TRY(encoded_new_size, impl::memory::encode(as_compact(new_size)));

const auto old_data_size = self_encoded.size();
const auto encoded_new_size_len = encoded_new_size.size();
const auto shift_size = encoded_new_size_len - encoded_old_size_len;

// if old and new encoded size length is equal, no need to shift data
if (encoded_old_size_len != encoded_new_size_len) {
// reserve place for new size length, old vector and new vector
self_encoded.reserve(old_data_size + shift_size + opaque_value.v.size());

// increase size to make space for new size encoding
self_encoded.resize(old_data_size + shift_size);

// shift existing data
std::memmove(self_encoded.data() + encoded_new_size_len,
self_encoded.data() + encoded_old_size_len,
old_data_size - encoded_old_size_len);
} else {
// reserve place for existing and new vector
self_encoded.reserve(old_data_size + opaque_value.v.size());
}

// copy new size bytes at the beginning
std::memmove(
self_encoded.data(), encoded_new_size.data(), encoded_new_size.size());
// append new data bytes
self_encoded.insert(
self_encoded.end(), opaque_value.v.begin(), opaque_value.v.end());
return outcome::success();
}

} // namespace scale
47 changes: 47 additions & 0 deletions include/scale/scale_error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,50 @@ namespace scale {

OUTCOME_HPP_DECLARE_ERROR(scale, EncodeError)
OUTCOME_HPP_DECLARE_ERROR(scale, DecodeError)

/**
* @brief Defines the error category for SCALE encoding errors.
* @param e The specific encoding error.
* @return A string describing the error.
*/
inline OUTCOME_CPP_DEFINE_CATEGORY(scale, EncodeError, e) {
using scale::EncodeError;
switch (e) {
case EncodeError::NEGATIVE_INTEGER:
return "SCALE encode: negative integers is not supported";
case EncodeError::VALUE_TOO_BIG_FOR_COMPACT_REPRESENTATION:
return "SCALE decode: value too big for compact representation";
case EncodeError::DEREF_NULLPOINTER:
return "SCALE encode: attempt to dereference a nullptr";
}
return "unknown EncodeError";
}

/**
* @brief Defines the error category for SCALE decoding errors.
* @param e The specific decoding error.
* @return A string describing the error.
*/
inline OUTCOME_CPP_DEFINE_CATEGORY(scale, DecodeError, e) {
using scale::DecodeError;
switch (e) {
case DecodeError::NOT_ENOUGH_DATA:
return "SCALE decode: not enough data to decode";
case DecodeError::UNEXPECTED_VALUE:
return "SCALE decode: unexpected value occurred";
case DecodeError::TOO_MANY_ITEMS:
return "SCALE decode: collection has too many items or memory is out or "
"data is damaged, unable to unpack";
case DecodeError::WRONG_TYPE_INDEX:
return "SCALE decode: wrong type index, cannot decode variant";
case DecodeError::INVALID_ENUM_VALUE:
return "SCALE decode: decoded enum value does not belong to the enum";
case DecodeError::UNUSED_BITS_ARE_SET:
return "SCALE decode: bits which must be unused have set";
case DecodeError::REDUNDANT_COMPACT_ENCODING:
return "SCALE decode: redundant bytes in compact encoding";
case DecodeError::DECODED_VALUE_OVERFLOWS_TARGET:
return "SCALE decode: encoded value overflows target type";
}
return "unknown SCALE DecodeError";
}
23 changes: 5 additions & 18 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,14 @@ add_custom_command(
VERBATIM
)

add_library(scale
scale_error.cpp
add_library(scale INTERFACE
${DECOMPOSE_AND_APPLY_HPP}
)
target_include_directories(scale PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(scale
Boost::boost
)

add_library(scale_append
encode_append.cpp
)
target_include_directories(scale_append PUBLIC
target_include_directories(scale PUBLIC INTERFACE
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(scale_append
scale
)
target_link_libraries(scale INTERFACE
Boost::boost
)
Loading