Skip to content

Commit ff1a6e3

Browse files
HarrmxDimon
andauthored
Make SCALE header-only and add sanitizer options for tests (#34)
* Add sanitizer options * fix: asan issues Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * refactor: make scale header-only Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * refactor: maintain sanitizers over vcpkg feature Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * Refactor some things --------- Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> Co-authored-by: Dmitriy Khaustov aka xDimon <[email protected]>
1 parent 8b2a0c5 commit ff1a6e3

10 files changed

+217
-209
lines changed

CMakeLists.txt

+89-54
Original file line numberDiff line numberDiff line change
@@ -6,89 +6,124 @@
66

77
cmake_minimum_required(VERSION 3.12)
88

9-
option(JAM_COMPATIBLE "Build compatible with JAM-codec" OFF)
10-
option(CUSTOM_CONFIG_SUPPORT "Support custom config of coder" OFF)
11-
set(MAX_AGGREGATE_FIELDS 20 CACHE STRING "Max number of aggregates fields (1..1000); for generation")
12-
13-
option(BUILD_TESTS "Whether to include the test suite in build" OFF)
14-
15-
if (PACKAGE_MANAGER)
16-
if(PACKAGE_MANAGER NOT MATCHES "^(hunter|vcpkg)$")
17-
message(FATAL_ERROR "PACKAGE_MANAGER must be set to 'hunter', 'vcpkg' or isn't set")
18-
endif ()
19-
else ()
20-
set(PACKAGE_MANAGER "hunter")
21-
if (CMAKE_TOOLCHAIN_FILE)
22-
get_filename_component(ACTUAL_NAME ${CMAKE_TOOLCHAIN_FILE} NAME)
23-
if(ACTUAL_NAME STREQUAL "vcpkg.cmake")
24-
message(STATUS "vcpkg will be used because vcpkg.cmake has found")
25-
set(PACKAGE_MANAGER "vcpkg")
26-
endif ()
27-
endif ()
28-
endif ()
9+
# Select package manager
10+
if(PACKAGE_MANAGER)
11+
if(NOT PACKAGE_MANAGER MATCHES "^(hunter|vcpkg)$")
12+
message(FATAL_ERROR "PACKAGE_MANAGER must be set to 'hunter', 'vcpkg' or isn't set")
13+
endif()
14+
else()
15+
set(PACKAGE_MANAGER "hunter")
16+
if(CMAKE_TOOLCHAIN_FILE)
17+
get_filename_component(ACTUAL_NAME ${CMAKE_TOOLCHAIN_FILE} NAME)
18+
if(ACTUAL_NAME STREQUAL "vcpkg.cmake")
19+
message(STATUS "vcpkg will be used because vcpkg.cmake has found")
20+
set(PACKAGE_MANAGER "vcpkg")
21+
endif()
22+
endif()
23+
endif()
2924
message(STATUS "Selected package manager: ${PACKAGE_MANAGER}")
3025

31-
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.27")
26+
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.27")
27+
# find_package() uses upper-case <PACKAGENAME>_ROOT variables.
3228
cmake_policy(SET CMP0144 NEW)
33-
endif ()
29+
endif()
3430

35-
if (PACKAGE_MANAGER STREQUAL "hunter")
36-
include("cmake/Hunter/init.cmake")
37-
endif ()
31+
if(PACKAGE_MANAGER STREQUAL "hunter")
32+
include("cmake/Hunter/init.cmake")
33+
else()
34+
set(HUNTER_ENABLED OFF)
35+
endif()
3836

39-
if(BUILD_TESTS)
40-
if (PACKAGE_MANAGER STREQUAL "vcpkg")
41-
list(APPEND VCPKG_MANIFEST_FEATURES scale-tests)
42-
endif()
37+
# Adjust vcpkg features by custom defined option (for deploy possible dependencies)
38+
if(PACKAGE_MANAGER STREQUAL "vcpkg")
39+
if(BUILD_TESTS AND NOT "scale-tests" IN_LIST VCPKG_MANIFEST_FEATURES)
40+
list(APPEND VCPKG_MANIFEST_FEATURES "scale-tests")
41+
endif()
4342
endif()
4443

45-
project(Scale LANGUAGES CXX VERSION 2.0.0)
44+
project(Scale LANGUAGES CXX VERSION 2.0.1)
45+
46+
include(cmake/feature_option.cmake)
47+
48+
# Init options
49+
feature_option(JAM_COMPATIBLE "jam-compatibility" "Build compatible with JAM-codec" OFF)
50+
feature_option(CUSTOM_CONFIG_SUPPORT "configurable-coding" "Support custom config of coder" OFF)
51+
feature_option(BUILD_TESTS "scale-tests" "Whether to include the test suite in build" OFF)
52+
option(ASAN "Build tests with address sanitizer" OFF)
53+
option(TSAN "Build tests with thread sanitizer" OFF)
54+
option(UBSAN "Build tests with undefined behavior sanitizer" OFF)
55+
56+
if((ASAN OR TSAN OR UBSAN) AND NOT BUILD_TESTS)
57+
message(FATAL_ERROR "Since SCALE is header-only, sanitizers should only be enabled for tests")
58+
endif()
59+
60+
set(MAX_AGGREGATE_FIELDS 20 CACHE STRING "Max number of aggregates fields (1..1000); for generation")
4661

4762
set(CMAKE_CXX_STANDARD 20)
4863
set(CMAKE_CXX_STANDARD_REQUIRED ON)
4964
set(CMAKE_CXX_EXTENSIONS OFF)
5065

5166
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
5267

53-
if (PACKAGE_MANAGER STREQUAL "hunter")
54-
hunter_add_package(Boost)
55-
find_package(Boost)
68+
if(PACKAGE_MANAGER STREQUAL "hunter")
69+
hunter_add_package(Boost)
70+
find_package(Boost)
5671
else()
57-
find_package(Boost CONFIG REQUIRED COMPONENTS endian multiprecision)
58-
endif ()
72+
find_package(Boost CONFIG REQUIRED COMPONENTS endian multiprecision)
73+
endif()
5974

60-
if (PACKAGE_MANAGER STREQUAL "hunter")
61-
hunter_add_package(qtils)
62-
endif ()
75+
if(PACKAGE_MANAGER STREQUAL "hunter")
76+
hunter_add_package(qtils)
77+
endif()
6378
find_package(qtils CONFIG REQUIRED)
6479

65-
SET(JAM_COMPATIBILITY_ENABLED "${JAM_COMPATIBLE}")
80+
set(JAM_COMPATIBILITY_ENABLED "${JAM_COMPATIBLE}")
6681
set(CUSTOM_CONFIG_ENABLED "${CUSTOM_CONFIG_SUPPORT}")
6782
configure_file("${CMAKE_SOURCE_DIR}/include/scale/definitions.hpp.in" "${CMAKE_BINARY_DIR}/include/scale/definitions.hpp")
6883

69-
add_subdirectory(src)
84+
if(ASAN)
85+
message(STATUS "Address sanitizer will be used")
86+
add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer)
87+
add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer)
88+
endif()
89+
if(TSAN)
90+
message(STATUS "Thread sanitizer will be used")
91+
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
92+
add_link_options(-fsanitize=thread -fno-omit-frame-pointer)
93+
endif()
94+
if(UBSAN)
95+
message(STATUS "Undefined behavior sanitizer will be used")
96+
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer)
97+
add_link_options(-fsanitize=undefined -fno-omit-frame-pointer)
98+
endif()
99+
100+
include(cmake/generate_decompose_and_apply.cmake)
70101

71-
if (BUILD_TESTS)
72-
enable_testing()
73-
add_subdirectory(test ${CMAKE_BINARY_DIR}/test_bin)
74-
endif ()
102+
add_library(scale INTERFACE
103+
${DECOMPOSE_AND_APPLY_HPP}
104+
)
105+
target_include_directories(scale PUBLIC INTERFACE
106+
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
107+
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
108+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
109+
)
110+
target_link_libraries(scale INTERFACE
111+
Boost::boost
112+
)
113+
114+
if(BUILD_TESTS)
115+
enable_testing()
116+
add_subdirectory(test ${CMAKE_BINARY_DIR}/test_bin)
117+
endif()
75118

76119
###############################################################################
77120
# INSTALLATION
78121
###############################################################################
79122

80123
include(GNUInstallDirs)
81124

82-
install(TARGETS scale EXPORT scaleConfig
83-
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
84-
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
85-
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
86-
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
87-
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
88-
FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
89-
)
90-
91-
install(TARGETS scale_append EXPORT scaleConfig
125+
install(
126+
TARGETS scale EXPORT scaleConfig
92127
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
93128
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
94129
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}

cmake/feature_option.cmake

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#
2+
# Copyright Quadrivium LLC
3+
# All Rights Reserved
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
# Connects CMake options with vcpkg features
8+
function (feature_option variable feature_name help_text default)
9+
if(PACKAGE_MANAGER STREQUAL "vcpkg" AND ${feature_name} IN_LIST VCPKG_MANIFEST_FEATURES)
10+
set(${variable} ON CACHE BOOL ${help_text} FORCE)
11+
else()
12+
set(${variable} ${default} CACHE BOOL ${help_text})
13+
endif()
14+
endfunction()
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#
2+
# Copyright Quadrivium LLC
3+
# All Rights Reserved
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
17
set(SCRIPT_PATH "${CMAKE_SOURCE_DIR}/scripts/generate_decompose_and_apply_hpp.sh")
28
set(DECOMPOSE_AND_APPLY_HPP_IN "${CMAKE_SOURCE_DIR}/include/scale/detail/decompose_and_apply.hpp.in")
39
set(DECOMPOSE_AND_APPLY_HPP "${CMAKE_BINARY_DIR}/include/scale/detail/decompose_and_apply.hpp")
@@ -9,28 +15,3 @@ add_custom_command(
915
COMMENT "Generating include/scale/detail/decompose_and_apply.hpp"
1016
VERBATIM
1117
)
12-
13-
add_library(scale
14-
scale_error.cpp
15-
${DECOMPOSE_AND_APPLY_HPP}
16-
)
17-
target_include_directories(scale PUBLIC
18-
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
19-
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
20-
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
21-
)
22-
target_link_libraries(scale
23-
Boost::boost
24-
)
25-
26-
add_library(scale_append
27-
encode_append.cpp
28-
)
29-
target_include_directories(scale_append PUBLIC
30-
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
31-
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
32-
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
33-
)
34-
target_link_libraries(scale_append
35-
scale
36-
)

include/scale/bit_vector.hpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -1263,16 +1263,16 @@ namespace scale {
12631263
if (sbf_ and new_size > arr_.size() * CHAR_BIT) {
12641264
switch_to_vector();
12651265
}
1266-
if (not sbf_) {
1267-
if (new_size != vec_.size() * CHAR_BIT) {
1268-
vec_.resize((new_size + CHAR_BIT - 1) / CHAR_BIT, 0);
1269-
}
1270-
}
12711266
if (new_size <= size_) {
12721267
auto data = sbf_ ? arr_.data() : vec_.data();
12731268
data[new_size / CHAR_BIT] &=
12741269
static_cast<uint8_t>(-1) >> (CHAR_BIT - (new_size % CHAR_BIT));
12751270
}
1271+
if (not sbf_) {
1272+
if (new_size != vec_.size() * CHAR_BIT) {
1273+
vec_.resize((new_size + CHAR_BIT - 1) / CHAR_BIT, 0);
1274+
}
1275+
}
12761276
if (sbf_ and new_size > size_) {
12771277
for (auto i = (size_ + CHAR_BIT - 1) / CHAR_BIT;
12781278
i <= new_size / CHAR_BIT;

include/scale/encode_append.hpp

+48-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,52 @@ namespace scale {
4040
* \param self_encoded
4141
* @return success if input was appended to self_encoded, failure otherwise
4242
*/
43-
outcome::result<void> append_or_new_vec(std::vector<uint8_t> &self_encoded,
44-
ConstSpanOfBytes input);
43+
inline outcome::result<void> append_or_new_vec(
44+
std::vector<uint8_t> &self_encoded, ConstSpanOfBytes input) {
45+
EncodeOpaqueValue opaque_value{.v = input};
46+
47+
// No data present, just encode the given input data.
48+
if (self_encoded.empty()) {
49+
backend::ToBytes encoder(self_encoded);
50+
encode(std::vector{opaque_value}, encoder);
51+
return outcome::success();
52+
}
53+
54+
// Take old size, calculate old size length and encode new size
55+
OUTCOME_TRY(size, impl::memory::decode<Compact<uint32_t>>(self_encoded));
56+
auto old_size = untagged(size);
57+
auto new_size = old_size + 1;
58+
auto encoded_old_size_len = lengthOfEncodedCompactInteger(old_size);
59+
OUTCOME_TRY(encoded_new_size, impl::memory::encode(as_compact(new_size)));
60+
61+
const auto old_data_size = self_encoded.size();
62+
const auto encoded_new_size_len = encoded_new_size.size();
63+
const auto shift_size = encoded_new_size_len - encoded_old_size_len;
64+
65+
// if old and new encoded size length is equal, no need to shift data
66+
if (encoded_old_size_len != encoded_new_size_len) {
67+
// reserve place for new size length, old vector and new vector
68+
self_encoded.reserve(old_data_size + shift_size + opaque_value.v.size());
69+
70+
// increase size to make space for new size encoding
71+
self_encoded.resize(old_data_size + shift_size);
72+
73+
// shift existing data
74+
std::memmove(self_encoded.data() + encoded_new_size_len,
75+
self_encoded.data() + encoded_old_size_len,
76+
old_data_size - encoded_old_size_len);
77+
} else {
78+
// reserve place for existing and new vector
79+
self_encoded.reserve(old_data_size + opaque_value.v.size());
80+
}
81+
82+
// copy new size bytes at the beginning
83+
std::memmove(
84+
self_encoded.data(), encoded_new_size.data(), encoded_new_size.size());
85+
// append new data bytes
86+
self_encoded.insert(
87+
self_encoded.end(), opaque_value.v.begin(), opaque_value.v.end());
88+
return outcome::success();
89+
}
90+
4591
} // namespace scale

include/scale/scale_error.hpp

+47
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,50 @@ namespace scale {
4949

5050
OUTCOME_HPP_DECLARE_ERROR(scale, EncodeError)
5151
OUTCOME_HPP_DECLARE_ERROR(scale, DecodeError)
52+
53+
/**
54+
* @brief Defines the error category for SCALE encoding errors.
55+
* @param e The specific encoding error.
56+
* @return A string describing the error.
57+
*/
58+
inline OUTCOME_CPP_DEFINE_CATEGORY(scale, EncodeError, e) {
59+
using scale::EncodeError;
60+
switch (e) {
61+
case EncodeError::NEGATIVE_INTEGER:
62+
return "SCALE encode: negative integers is not supported";
63+
case EncodeError::VALUE_TOO_BIG_FOR_COMPACT_REPRESENTATION:
64+
return "SCALE decode: value too big for compact representation";
65+
case EncodeError::DEREF_NULLPOINTER:
66+
return "SCALE encode: attempt to dereference a nullptr";
67+
}
68+
return "unknown EncodeError";
69+
}
70+
71+
/**
72+
* @brief Defines the error category for SCALE decoding errors.
73+
* @param e The specific decoding error.
74+
* @return A string describing the error.
75+
*/
76+
inline OUTCOME_CPP_DEFINE_CATEGORY(scale, DecodeError, e) {
77+
using scale::DecodeError;
78+
switch (e) {
79+
case DecodeError::NOT_ENOUGH_DATA:
80+
return "SCALE decode: not enough data to decode";
81+
case DecodeError::UNEXPECTED_VALUE:
82+
return "SCALE decode: unexpected value occurred";
83+
case DecodeError::TOO_MANY_ITEMS:
84+
return "SCALE decode: collection has too many items or memory is out or "
85+
"data is damaged, unable to unpack";
86+
case DecodeError::WRONG_TYPE_INDEX:
87+
return "SCALE decode: wrong type index, cannot decode variant";
88+
case DecodeError::INVALID_ENUM_VALUE:
89+
return "SCALE decode: decoded enum value does not belong to the enum";
90+
case DecodeError::UNUSED_BITS_ARE_SET:
91+
return "SCALE decode: bits which must be unused have set";
92+
case DecodeError::REDUNDANT_COMPACT_ENCODING:
93+
return "SCALE decode: redundant bytes in compact encoding";
94+
case DecodeError::DECODED_VALUE_OVERFLOWS_TARGET:
95+
return "SCALE decode: encoded value overflows target type";
96+
}
97+
return "unknown SCALE DecodeError";
98+
}

0 commit comments

Comments
 (0)