diff --git a/.github/workflows/check_bazel_tests.yml b/.github/workflows/check_bazel_tests.yml index a24aa99..19aa5bc 100644 --- a/.github/workflows/check_bazel_tests.yml +++ b/.github/workflows/check_bazel_tests.yml @@ -15,6 +15,36 @@ env: ZMQ_LIBRARY_PREFIX: /usr/lib/x86_64-linux-gnu ZMQ_INCLUDE_PREFIX: /usr/include jobs: + v2: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential curl + - uses: bazel-contrib/setup-bazel@0.19.0 + with: + bazelisk-cache: true + - name: Build + run: bazel build --verbose_failures //... + working-directory: ./udf-runner-cpp/v2 + - name: Install clang-tidy + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -- 22 + sudo apt-get install -y clang-tidy-22 + - name: Run clang-tidy + run: bazel build --verbose_failures --config clang-tidy //... + working-directory: ./udf-runner-cpp/v2 + - name: Install clang-format + run: sudo apt-get install -y clang-format + - name: Run clang-format + run: bazel build --verbose_failures --config clang-format //... + working-directory: ./udf-runner-cpp/v2 + - name: Run tests + run: bazel test --verbose_failures //... + working-directory: ./udf-runner-cpp/v2 + build: runs-on: ubuntu-24.04 steps: diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md new file mode 100644 index 0000000..380d2e0 --- /dev/null +++ b/doc/changes/unreleased.md @@ -0,0 +1,18 @@ +## Summary + + +## Bug Fixes + +n/a + +## Features / Enhancements + + - 32: Added clang tidy to v2 + +## Refactorings + +n/a + +## Internal + +* Updated Poetry dependencies and added developer guide and added .gitignore diff --git a/udf-runner-cpp/v2/.bazelrc b/udf-runner-cpp/v2/.bazelrc new file mode 100644 index 0000000..644d06a --- /dev/null +++ b/udf-runner-cpp/v2/.bazelrc @@ -0,0 +1,22 @@ +# The clang-tidy and clang-format rule packages are published in this registry. +common --registry=https://raw.githubusercontent.com/digiboys/bazel-registry/main +common --registry=https://bcr.bazel.build + +build:clang-tidy --@rules_clang_tidy//:config=//tools/clang-tidy:config +build:clang-tidy --@rules_clang_tidy//:clang-tidy=//tools/clang-tidy:wrapper +build:clang-tidy --aspects=@rules_clang_tidy//:aspects.bzl%check +build:clang-tidy --output_groups=report +build:clang-tidy --remote_download_outputs=toplevel +build:clang-tidy --keep_going +build:clang-tidy --build_tag_filters=-noclangtidy +build:clang-tidy --copt=-Wno-pragma-once-outside-header + +build:clang-format --aspects=@bazel_clang_format//:defs.bzl%check_aspect +build:clang-format --output_groups=report +build:clang-format --@bazel_clang_format//:config=//tools/clang-format:clang-format-config +build:clang-format --build_tag_filters=-noclangtidy + +build:clang-format-fix --aspects=@bazel_clang_format//:defs.bzl%fix_aspect +build:clang-format-fix --output_groups=report +build:clang-format-fix --use_action_cache=false +build:clang-format-fix --@bazel_clang_format//:config=//tools/clang-format:clang-format-config diff --git a/udf-runner-cpp/v2/BUILD.bazel b/udf-runner-cpp/v2/BUILD.bazel index b58b2d2..396b6fd 100644 --- a/udf-runner-cpp/v2/BUILD.bazel +++ b/udf-runner-cpp/v2/BUILD.bazel @@ -109,7 +109,7 @@ cc_binary( cc_test( name = "udf_protocol_symbol_leak_test", - srcs = ["udf_protocol_symbol_leak_test.cc"], + srcs = ["nm_runner.hpp", "udf_protocol_symbol_leak_test.cc"], copts = ["-std=c++20"], # The test inspects the built shared object with nm. data makes the # artifact available at runtime and args passes its runfiles path. @@ -121,7 +121,7 @@ cc_test( cc_test( name = "udf_protocol_static_symbol_leak_test", - srcs = ["udf_protocol_static_symbol_leak_test.cc"], + srcs = ["nm_runner.hpp", "udf_protocol_static_symbol_leak_test.cc"], copts = ["-std=c++20"], # cc_library produces multiple artifacts, so the test receives all # locations and selects the static .a archive for nm inspection. @@ -146,6 +146,9 @@ cc_test( alias( name = "arrow_core", actual = "@v2_arrow//:arrow_core", + # Arrow and its vendored sources are third-party code. Keep them out of + # the repository's clang-tidy and clang-format CI profiles. + tags = ["noclangtidy"], ) filegroup( diff --git a/udf-runner-cpp/v2/MODULE.bazel b/udf-runner-cpp/v2/MODULE.bazel index 5371491..7f560ec 100644 --- a/udf-runner-cpp/v2/MODULE.bazel +++ b/udf-runner-cpp/v2/MODULE.bazel @@ -8,6 +8,18 @@ bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "flatbuffers", version = "25.2.10") bazel_dep(name = "google_benchmark", version = "1.9.5") +bazel_dep( + name = "rules_clang_tidy", + version = "0.0.0", + dev_dependency = True, +) + +bazel_dep( + name = "bazel_clang_format", + version = "0.0.0", + dev_dependency = True, +) + # FlatBuffers currently selects versions of these transitive build tools that # still use the removed incompatible_use_toolchain_transition rule attribute. # Override them so the v2 module can be analyzed by Bazel 9. diff --git a/udf-runner-cpp/v2/arrow_c_data_demo.cc b/udf-runner-cpp/v2/arrow_c_data_demo.cc index a101c78..3c4ae0f 100644 --- a/udf-runner-cpp/v2/arrow_c_data_demo.cc +++ b/udf-runner-cpp/v2/arrow_c_data_demo.cc @@ -50,7 +50,8 @@ arrow::Result> MakeDemoRecordBatch() { arrow::Status ExportDemoRecordBatch(ArrowArray* out_array, ArrowSchema* out_schema) { if (out_array == nullptr || out_schema == nullptr) { - return arrow::Status::Invalid("output ArrowArray and ArrowSchema pointers must not be null"); + return arrow::Status::Invalid( + "output ArrowArray and ArrowSchema pointers must not be null"); } std::memset(out_array, 0, sizeof(*out_array)); @@ -64,8 +65,10 @@ arrow::Status ExportDemoRecordBatch(ArrowArray* out_array, ArrowSchema* out_sche return arrow::Status::OK(); } -arrow::Status ConsumeDemoRecordBatch(ArrowArray* array, ArrowSchema* schema, - int64_t* out_row_count, int64_t* out_id_sum) { +arrow::Status ConsumeDemoRecordBatch(ArrowArray* array, + ArrowSchema* schema, + int64_t* out_row_count, + int64_t* out_id_sum) { if (array == nullptr || schema == nullptr || out_row_count == nullptr || out_id_sum == nullptr) { return arrow::Status::Invalid("input and output pointers must not be null"); @@ -75,12 +78,11 @@ arrow::Status ConsumeDemoRecordBatch(ArrowArray* array, ArrowSchema* schema, if (batch->num_columns() != 2) { return arrow::Status::Invalid("expected two columns"); } - if (batch->schema()->field(0)->name() != "id" || - batch->schema()->field(1)->name() != "name") { + if (batch->schema()->field(0)->name() != "id" || batch->schema()->field(1)->name() != "name") { return arrow::Status::Invalid("unexpected schema"); } - auto ids = std::static_pointer_cast(batch->column(0)); + auto ids = std::static_pointer_cast(batch->column(0)); int64_t sum = 0; for (int64_t index = 0; index < ids->length(); ++index) { if (!ids->IsNull(index)) { @@ -89,11 +91,11 @@ arrow::Status ConsumeDemoRecordBatch(ArrowArray* array, ArrowSchema* schema, } *out_row_count = batch->num_rows(); - *out_id_sum = sum; + *out_id_sum = sum; return arrow::Status::OK(); } -} // namespace +} // namespace extern "C" UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_export_record_batch( ArrowArray* out_array, ArrowSchema* out_schema) { @@ -107,10 +109,8 @@ extern "C" UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_export_record_bat } extern "C" UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_consume_record_batch( - ArrowArray* array, ArrowSchema* schema, int64_t* out_row_count, - int64_t* out_id_sum) { - const arrow::Status status = - ConsumeDemoRecordBatch(array, schema, out_row_count, out_id_sum); + ArrowArray* array, ArrowSchema* schema, int64_t* out_row_count, int64_t* out_id_sum) { + const arrow::Status status = ConsumeDemoRecordBatch(array, schema, out_row_count, out_id_sum); if (!status.ok()) { SetLastError(status); return 1; diff --git a/udf-runner-cpp/v2/arrow_c_data_demo.h b/udf-runner-cpp/v2/arrow_c_data_demo.h index 992d1f8..3fe1636 100644 --- a/udf-runner-cpp/v2/arrow_c_data_demo.h +++ b/udf-runner-cpp/v2/arrow_c_data_demo.h @@ -17,13 +17,13 @@ extern "C" { UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_export_record_batch( struct ArrowArray* out_array, struct ArrowSchema* out_schema); -UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_consume_record_batch( - struct ArrowArray* array, struct ArrowSchema* schema, int64_t* out_row_count, - int64_t* out_id_sum); +UDF_RUNNER_CPP_V2_EXPORT int udf_runner_cpp_v2_demo_consume_record_batch(struct ArrowArray* array, + struct ArrowSchema* schema, + int64_t* out_row_count, + int64_t* out_id_sum); UDF_RUNNER_CPP_V2_EXPORT const char* udf_runner_cpp_v2_demo_last_error(void); #ifdef __cplusplus -} // extern "C" +} // extern "C" #endif - diff --git a/udf-runner-cpp/v2/arrow_c_data_demo_test.cc b/udf-runner-cpp/v2/arrow_c_data_demo_test.cc index 354584a..0f3b74f 100644 --- a/udf-runner-cpp/v2/arrow_c_data_demo_test.cc +++ b/udf-runner-cpp/v2/arrow_c_data_demo_test.cc @@ -3,8 +3,8 @@ #include #include -#include #include +#include #include #include #include @@ -20,9 +20,9 @@ namespace { -using export_fn_t = int (*)(ArrowArray*, ArrowSchema*); +using export_fn_t = int (*)(ArrowArray*, ArrowSchema*); using consume_fn_t = int (*)(ArrowArray*, ArrowSchema*, int64_t*, int64_t*); -using error_fn_t = const char* (*)(void); +using error_fn_t = const char* (*)(); constexpr std::string_view kArrowMangledPrefix = "_ZN5arrow"; constexpr std::string_view kDemoExportedPrefix = "udf_runner_cpp_v2_demo_"; @@ -37,19 +37,20 @@ T read_object(const std::vector& file, std::size_t offset) { fail("ELF file is truncated"); } - T result; + T result{}; std::memcpy(&result, file.data() + offset, sizeof(result)); return result; } -std::string read_string(const std::vector& file, std::size_t offset, +std::string read_string(const std::vector& file, + std::size_t offset, std::size_t maximum_size) { if (offset > file.size() || file.size() - offset < maximum_size) { fail("ELF string table is truncated"); } const char* begin = file.data() + offset; - const void* end = std::memchr(begin, '\0', maximum_size); + const void* end = std::memchr(begin, '\0', maximum_size); if (end == nullptr) { fail("ELF symbol name is not terminated"); } @@ -65,19 +66,17 @@ std::vector read_file(const std::string& path) { } bool is_exported(const Elf64_Sym& symbol) { - const unsigned char binding = ELF64_ST_BIND(symbol.st_info); + const unsigned char binding = ELF64_ST_BIND(symbol.st_info); const unsigned char visibility = ELF64_ST_VISIBILITY(symbol.st_other); - return symbol.st_shndx != SHN_UNDEF && - (binding == STB_GLOBAL || binding == STB_WEAK) && + return symbol.st_shndx != SHN_UNDEF && (binding == STB_GLOBAL || binding == STB_WEAK) && (visibility == STV_DEFAULT || visibility == STV_PROTECTED); } void verify_symbols(const std::string& library_path) { const std::vector file = read_file(library_path); - const Elf64_Ehdr header = read_object(file, 0); + const auto header = read_object(file, 0); if (std::memcmp(header.e_ident, ELFMAG, SELFMAG) != 0 || - header.e_ident[EI_CLASS] != ELFCLASS64 || - header.e_ident[EI_DATA] != ELFDATA2LSB || + header.e_ident[EI_CLASS] != ELFCLASS64 || header.e_ident[EI_DATA] != ELFDATA2LSB || header.e_shentsize != sizeof(Elf64_Shdr)) { fail("shared library is not a little-endian ELF64 file"); } @@ -92,10 +91,10 @@ void verify_symbols(const std::string& library_path) { Elf64_Shdr dynamic_symbols{}; bool found_dynamic_symbols = false; for (std::size_t index = 0; index < section_count; ++index) { - const Elf64_Shdr section = + const auto section = read_object(file, section_table + index * sizeof(Elf64_Shdr)); if (section.sh_type == SHT_DYNSYM) { - dynamic_symbols = section; + dynamic_symbols = section; found_dynamic_symbols = true; break; } @@ -106,8 +105,8 @@ void verify_symbols(const std::string& library_path) { fail("ELF dynamic symbol table is invalid"); } - const Elf64_Shdr string_table = read_object( - file, section_table + dynamic_symbols.sh_link * sizeof(Elf64_Shdr)); + const auto string_table = + read_object(file, section_table + dynamic_symbols.sh_link * sizeof(Elf64_Shdr)); if (dynamic_symbols.sh_offset > file.size() || dynamic_symbols.sh_size > file.size() - dynamic_symbols.sh_offset || string_table.sh_offset > file.size() || @@ -116,16 +115,14 @@ void verify_symbols(const std::string& library_path) { } bool found_demo_symbol = false; - for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; - offset += sizeof(Elf64_Sym)) { - const Elf64_Sym symbol = - read_object(file, dynamic_symbols.sh_offset + offset); + for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; offset += sizeof(Elf64_Sym)) { + const auto symbol = read_object(file, dynamic_symbols.sh_offset + offset); if (!is_exported(symbol) || symbol.st_name >= string_table.sh_size) { continue; } - const std::string name = read_string( - file, string_table.sh_offset + symbol.st_name, string_table.sh_size - symbol.st_name); + const std::string name = read_string(file, string_table.sh_offset + symbol.st_name, + string_table.sh_size - symbol.st_name); if (name.compare(0, kArrowMangledPrefix.size(), kArrowMangledPrefix) == 0) { fail("shared library exports an Arrow C++ symbol: " + name); } @@ -152,66 +149,70 @@ std::shared_ptr import_record_batch(ArrowArray* array, Arrow if (!maybe_batch.ok()) { fail(maybe_batch.status().ToString()); } - return *std::move(maybe_batch); + return *maybe_batch; } -} // namespace +} // namespace int main(int argc, char** argv) { - if (argc != 2) { - fail("expected the shared library path as argv[1]"); - } + try { + if (argc != 2) { + fail("expected the shared library path as argv[1]"); + } - void* handle = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); - if (handle == nullptr) { - fail(dlerror()); - } + void* handle = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (handle == nullptr) { + fail(dlerror()); + } - auto export_batch = reinterpret_cast( - dlsym(handle, "udf_runner_cpp_v2_demo_export_record_batch")); - auto consume_batch = reinterpret_cast( - dlsym(handle, "udf_runner_cpp_v2_demo_consume_record_batch")); - auto last_error = reinterpret_cast( - dlsym(handle, "udf_runner_cpp_v2_demo_last_error")); - if (export_batch == nullptr || consume_batch == nullptr || last_error == nullptr) { - fail(dlerror()); - } + const auto export_batch = reinterpret_cast( + dlsym(handle, "udf_runner_cpp_v2_demo_export_record_batch")); + const auto consume_batch = reinterpret_cast( + dlsym(handle, "udf_runner_cpp_v2_demo_consume_record_batch")); + const auto last_error = + reinterpret_cast(dlsym(handle, "udf_runner_cpp_v2_demo_last_error")); + if (export_batch == nullptr || consume_batch == nullptr || last_error == nullptr) { + fail(dlerror()); + } - const std::string library_path = find_library_path(reinterpret_cast(export_batch)); - verify_symbols(library_path); + const std::string library_path = find_library_path(reinterpret_cast(export_batch)); + verify_symbols(library_path); - ArrowArray array{}; - ArrowSchema schema{}; - if (export_batch(&array, &schema) != 0) { - fail(last_error()); - } + ArrowArray array{}; + ArrowSchema schema{}; + if (export_batch(&array, &schema) != 0) { + fail(last_error()); + } - const std::shared_ptr batch = import_record_batch(&array, &schema); - assert(batch->num_rows() == 4); - assert(batch->num_columns() == 2); - assert(batch->schema()->field(0)->name() == "id"); - assert(batch->schema()->field(1)->name() == "name"); - - auto ids = std::static_pointer_cast(batch->column(0)); - auto names = std::static_pointer_cast(batch->column(1)); - assert(ids->Value(0) == 1); - assert(ids->Value(3) == 4); - assert(names->GetString(0) == "alpha"); - assert(names->GetString(3) == "delta"); - - ArrowArray second_array{}; - ArrowSchema second_schema{}; - if (export_batch(&second_array, &second_schema) != 0) { - fail(last_error()); - } + const auto batch = import_record_batch(&array, &schema); + assert(batch->num_rows() == 4); + assert(batch->num_columns() == 2); + assert(batch->schema()->field(0)->name() == "id"); + assert(batch->schema()->field(1)->name() == "name"); + + const auto ids = std::static_pointer_cast(batch->column(0)); + const auto names = std::static_pointer_cast(batch->column(1)); + assert(ids->Value(0) == 1); + assert(ids->Value(3) == 4); + assert(names->GetString(0) == "alpha"); + assert(names->GetString(3) == "delta"); + + ArrowArray second_array{}; + ArrowSchema second_schema{}; + if (export_batch(&second_array, &second_schema) != 0) { + fail(last_error()); + } - int64_t row_count = 0; - int64_t id_sum = 0; - if (consume_batch(&second_array, &second_schema, &row_count, &id_sum) != 0) { - fail(last_error()); + int64_t row_count = 0; + int64_t id_sum = 0; + if (consume_batch(&second_array, &second_schema, &row_count, &id_sum) != 0) { + fail(last_error()); + } + assert(row_count == 4); + assert(id_sum == 10); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; } - assert(row_count == 4); - assert(id_sum == 10); - return 0; } diff --git a/udf-runner-cpp/v2/include/exasol/udf/v2/mpmc_queue.hpp b/udf-runner-cpp/v2/include/exasol/udf/v2/mpmc_queue.hpp index da0006a..96dc0bf 100644 --- a/udf-runner-cpp/v2/include/exasol/udf/v2/mpmc_queue.hpp +++ b/udf-runner-cpp/v2/include/exasol/udf/v2/mpmc_queue.hpp @@ -13,7 +13,6 @@ template using MpmcQueue = third_party::moodycamel::ConcurrentQueue; template -using BlockingMpmcQueue = - third_party::moodycamel::BlockingConcurrentQueue; +using BlockingMpmcQueue = third_party::moodycamel::BlockingConcurrentQueue; -} // namespace exasol::udf::v2 +} // namespace exasol::udf::v2 diff --git a/udf-runner-cpp/v2/include/exasol/udf/v2/spsc_queue.hpp b/udf-runner-cpp/v2/include/exasol/udf/v2/spsc_queue.hpp index 1a2cb51..8740247 100644 --- a/udf-runner-cpp/v2/include/exasol/udf/v2/spsc_queue.hpp +++ b/udf-runner-cpp/v2/include/exasol/udf/v2/spsc_queue.hpp @@ -14,7 +14,6 @@ template using SpscQueue = third_party::moodycamel::ReaderWriterQueue; template -using SpscCircularBuffer = - third_party::moodycamel::BlockingReaderWriterCircularBuffer; +using SpscCircularBuffer = third_party::moodycamel::BlockingReaderWriterCircularBuffer; -} // namespace exasol::udf::v2 +} // namespace exasol::udf::v2 diff --git a/udf-runner-cpp/v2/include/exasol/udf/v2/waitable_queue.hpp b/udf-runner-cpp/v2/include/exasol/udf/v2/waitable_queue.hpp index 0ca7c50..8845d22 100644 --- a/udf-runner-cpp/v2/include/exasol/udf/v2/waitable_queue.hpp +++ b/udf-runner-cpp/v2/include/exasol/udf/v2/waitable_queue.hpp @@ -23,23 +23,19 @@ namespace exasol::udf::v2 { // one-to-one mapping between eventfd counter values and queue elements. template class WaitableQueue { - public: +public: using queue_type = Queue; - WaitableQueue() - : notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { + WaitableQueue() : notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { if (notification_fd_ == -1) { - throw std::system_error(errno, std::generic_category(), - "eventfd"); + throw std::system_error(errno, std::generic_category(), "eventfd"); } } explicit WaitableQueue(Queue queue) - : queue_(std::move(queue)), - notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { + : queue_(std::move(queue)), notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { if (notification_fd_ == -1) { - throw std::system_error(errno, std::generic_category(), - "eventfd"); + throw std::system_error(errno, std::generic_category(), "eventfd"); } } @@ -49,19 +45,20 @@ class WaitableQueue { } } - WaitableQueue(const WaitableQueue&) = delete; + WaitableQueue(const WaitableQueue&) = delete; WaitableQueue& operator=(const WaitableQueue&) = delete; WaitableQueue(WaitableQueue&& other) noexcept : queue_(std::move(other.queue_)), - notification_fd_(std::exchange(other.notification_fd_, -1)) {} + notification_fd_(std::exchange(other.notification_fd_, -1)) { + } WaitableQueue& operator=(WaitableQueue&& other) noexcept { if (this != &other) { if (notification_fd_ != -1) { ::close(notification_fd_); } - queue_ = std::move(other.queue_); + queue_ = std::move(other.queue_); notification_fd_ = std::exchange(other.notification_fd_, -1); } return *this; @@ -106,9 +103,8 @@ class WaitableQueue { std::uint64_t drain_notifications() { std::uint64_t total = 0; for (;;) { - std::uint64_t value = 0; - const ssize_t result = ::read(notification_fd_, &value, - sizeof(value)); + std::uint64_t value = 0; + const ssize_t result = ::read(notification_fd_, &value, sizeof(value)); if (result == sizeof(value)) { total += value; continue; @@ -120,23 +116,24 @@ class WaitableQueue { return total; } if (result == -1) { - throw std::system_error(errno, std::generic_category(), - "read eventfd"); + throw std::system_error(errno, std::generic_category(), "read eventfd"); } - throw std::system_error(EIO, std::generic_category(), - "short read from eventfd"); + throw std::system_error(EIO, std::generic_category(), "short read from eventfd"); } } - Queue& queue() noexcept { return queue_; } - const Queue& queue() const noexcept { return queue_; } + Queue& queue() noexcept { + return queue_; + } + const Queue& queue() const noexcept { + return queue_; + } - private: +private: void notify() { constexpr std::uint64_t signal = 1; for (;;) { - const ssize_t result = - ::write(notification_fd_, &signal, sizeof(signal)); + const ssize_t result = ::write(notification_fd_, &signal, sizeof(signal)); if (result == sizeof(signal)) { return; } @@ -149,11 +146,9 @@ class WaitableQueue { return; } if (result == -1) { - throw std::system_error(errno, std::generic_category(), - "write eventfd"); + throw std::system_error(errno, std::generic_category(), "write eventfd"); } - throw std::system_error(EIO, std::generic_category(), - "short write to eventfd"); + throw std::system_error(EIO, std::generic_category(), "short write to eventfd"); } } @@ -167,4 +162,4 @@ using WaitableSpscQueue = WaitableQueue>; template using WaitableMpmcQueue = WaitableQueue>; -} // namespace exasol::udf::v2 +} // namespace exasol::udf::v2 diff --git a/udf-runner-cpp/v2/json_schema_symbol_leak_test.cc b/udf-runner-cpp/v2/json_schema_symbol_leak_test.cc index 3a16b60..9d7ebba 100644 --- a/udf-runner-cpp/v2/json_schema_symbol_leak_test.cc +++ b/udf-runner-cpp/v2/json_schema_symbol_leak_test.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -16,9 +17,8 @@ namespace isolated_nlohmann = exasol::udf::v2::third_party::nlohmann; namespace { -constexpr std::string_view kGlobalNamespacePrefix = "_ZN8nlohmann"; -constexpr std::string_view kIsolatedNamespacePrefix = - "_ZN6exasol3udf2v211third_party8nlohmann"; +constexpr std::string_view kGlobalNamespacePrefix = "_ZN8nlohmann"; +constexpr std::string_view kIsolatedNamespacePrefix = "_ZN6exasol3udf2v211third_party8nlohmann"; [[noreturn]] void fail(const std::string& message) { throw std::runtime_error(message); @@ -30,19 +30,20 @@ T read_object(const std::vector& file, std::size_t offset) { fail("ELF file is truncated"); } - T result; + T result{}; std::memcpy(&result, file.data() + offset, sizeof(result)); return result; } -std::string read_string(const std::vector& file, std::size_t offset, +std::string read_string(const std::vector& file, + std::size_t offset, std::size_t maximum_size) { if (offset > file.size() || file.size() - offset < maximum_size) { fail("ELF string table is truncated"); } const char* begin = file.data() + offset; - const void* end = std::memchr(begin, '\0', maximum_size); + const void* end = std::memchr(begin, '\0', maximum_size); if (end == nullptr) { fail("ELF symbol name is not terminated"); } @@ -58,27 +59,24 @@ std::vector read_file(const std::string& path) { } bool is_exported(const Elf64_Sym& symbol) { - const unsigned char binding = ELF64_ST_BIND(symbol.st_info); + const unsigned char binding = ELF64_ST_BIND(symbol.st_info); const unsigned char visibility = ELF64_ST_VISIBILITY(symbol.st_other); - return symbol.st_shndx != SHN_UNDEF && - (binding == STB_GLOBAL || binding == STB_WEAK) && + return symbol.st_shndx != SHN_UNDEF && (binding == STB_GLOBAL || binding == STB_WEAK) && (visibility == STV_DEFAULT || visibility == STV_PROTECTED); } void verify_symbols(const std::string& library_path) { const std::vector file = read_file(library_path); - const Elf64_Ehdr header = read_object(file, 0); + const auto header = read_object(file, 0); if (std::memcmp(header.e_ident, ELFMAG, SELFMAG) != 0 || - header.e_ident[EI_CLASS] != ELFCLASS64 || - header.e_ident[EI_DATA] != ELFDATA2LSB || + header.e_ident[EI_CLASS] != ELFCLASS64 || header.e_ident[EI_DATA] != ELFDATA2LSB || header.e_shentsize != sizeof(Elf64_Shdr)) { fail("validator library is not a little-endian ELF64 file"); } const std::size_t section_count = header.e_shnum; const std::size_t section_table = header.e_shoff; - if (section_count == 0 || - section_table > file.size() || + if (section_count == 0 || section_table > file.size() || section_count > (file.size() - section_table) / sizeof(Elf64_Shdr)) { fail("ELF section table is invalid"); } @@ -86,10 +84,10 @@ void verify_symbols(const std::string& library_path) { Elf64_Shdr dynamic_symbols{}; bool found_dynamic_symbols = false; for (std::size_t index = 0; index < section_count; ++index) { - const Elf64_Shdr section = read_object( - file, section_table + index * sizeof(Elf64_Shdr)); + const auto section = + read_object(file, section_table + index * sizeof(Elf64_Shdr)); if (section.sh_type == SHT_DYNSYM) { - dynamic_symbols = section; + dynamic_symbols = section; found_dynamic_symbols = true; break; } @@ -100,8 +98,8 @@ void verify_symbols(const std::string& library_path) { fail("ELF dynamic symbol table is invalid"); } - const Elf64_Shdr string_table = read_object( - file, section_table + dynamic_symbols.sh_link * sizeof(Elf64_Shdr)); + const auto string_table = + read_object(file, section_table + dynamic_symbols.sh_link * sizeof(Elf64_Shdr)); if (dynamic_symbols.sh_offset > file.size() || dynamic_symbols.sh_size > file.size() - dynamic_symbols.sh_offset || string_table.sh_offset > file.size() || @@ -110,23 +108,18 @@ void verify_symbols(const std::string& library_path) { } bool found_isolated_symbol = false; - for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; - offset += sizeof(Elf64_Sym)) { - const Elf64_Sym symbol = read_object( - file, dynamic_symbols.sh_offset + offset); + for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; offset += sizeof(Elf64_Sym)) { + const auto symbol = read_object(file, dynamic_symbols.sh_offset + offset); if (!is_exported(symbol) || symbol.st_name >= string_table.sh_size) { continue; } - const std::string name = read_string( - file, string_table.sh_offset + symbol.st_name, - string_table.sh_size - symbol.st_name); - if (name.compare(0, kGlobalNamespacePrefix.size(), - kGlobalNamespacePrefix) == 0) { + const std::string name = read_string(file, string_table.sh_offset + symbol.st_name, + string_table.sh_size - symbol.st_name); + if (name.compare(0, kGlobalNamespacePrefix.size(), kGlobalNamespacePrefix) == 0) { fail("validator exports a global nlohmann symbol: " + name); } - if (name.compare(0, kIsolatedNamespacePrefix.size(), - kIsolatedNamespacePrefix) == 0) { + if (name.compare(0, kIsolatedNamespacePrefix.size(), kIsolatedNamespacePrefix) == 0) { found_isolated_symbol = true; } } @@ -146,8 +139,13 @@ void verify_loaded_validator() { verify_symbols(library.dli_fname); } -} // namespace +} // namespace int main() { - verify_loaded_validator(); + try { + verify_loaded_validator(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } } diff --git a/udf-runner-cpp/v2/json_schema_validation_test.cc b/udf-runner-cpp/v2/json_schema_validation_test.cc index d79509a..09443a4 100644 --- a/udf-runner-cpp/v2/json_schema_validation_test.cc +++ b/udf-runner-cpp/v2/json_schema_validation_test.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -16,32 +17,38 @@ isolated_nlohmann::json read_json(const std::string& path) { return isolated_nlohmann::json::parse(input); } -} // namespace +} // namespace int main() { - const auto import_schema = read_json("json_schema/import_specification.schema.json"); - isolated_nlohmann::json_schema::json_validator validator( - [](const isolated_nlohmann::json_uri&, isolated_nlohmann::json& schema) { - schema = read_json("json_schema/connection_information.schema.json"); - }); - validator.set_root_schema(import_schema); - - const isolated_nlohmann::json valid = { - {"is_subselect", true}, - {"connection_information", { - {"kind", "JDBC"}, - {"address", "jdbc:example://host/database"}, - {"user", "user"}, - {"password", "secret"}, - }}, - }; - validator.validate(valid); - - bool rejected = false; try { - validator.validate(isolated_nlohmann::json::object()); - } catch (const std::exception&) { - rejected = true; + const auto import_schema = read_json("json_schema/import_specification.schema.json"); + isolated_nlohmann::json_schema::json_validator validator( + [](const isolated_nlohmann::json_uri&, isolated_nlohmann::json& schema) { + schema = read_json("json_schema/connection_information.schema.json"); + }); + validator.set_root_schema(import_schema); + + const isolated_nlohmann::json valid = { + {"is_subselect", true}, + {"connection_information", + { + {"kind", "JDBC"}, + {"address", "jdbc:example://host/database"}, + {"user", "user"}, + {"password", "secret"}, + }}, + }; + validator.validate(valid); + + bool rejected = false; + try { + validator.validate(isolated_nlohmann::json::object()); + } catch (const std::exception&) { + rejected = true; + } + assert(rejected); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; } - assert(rejected); } diff --git a/udf-runner-cpp/v2/moodycamel_symbol_leak_test.cc b/udf-runner-cpp/v2/moodycamel_symbol_leak_test.cc index cfebd9a..2b659fb 100644 --- a/udf-runner-cpp/v2/moodycamel_symbol_leak_test.cc +++ b/udf-runner-cpp/v2/moodycamel_symbol_leak_test.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -13,9 +14,8 @@ namespace { -constexpr std::string_view kGlobalNamespacePrefix = "_ZN10moodycamel"; -constexpr std::string_view kIsolatedNamespacePrefix = - "_ZN6exasol3udf2v211third_party10moodycamel"; +constexpr std::string_view kGlobalNamespacePrefix = "_ZN10moodycamel"; +constexpr std::string_view kIsolatedNamespacePrefix = "_ZN6exasol3udf2v211third_party10moodycamel"; [[noreturn]] void fail(const std::string& message) { throw std::runtime_error(message); @@ -26,18 +26,19 @@ T read_object(const std::vector& file, std::size_t offset) { if (offset > file.size() || file.size() - offset < sizeof(T)) { fail("ELF file is truncated"); } - T result; + T result{}; std::memcpy(&result, file.data() + offset, sizeof(result)); return result; } -std::string read_string(const std::vector& file, std::size_t offset, +std::string read_string(const std::vector& file, + std::size_t offset, std::size_t maximum_size) { if (offset > file.size() || file.size() - offset < maximum_size) { fail("ELF string table is truncated"); } const char* begin = file.data() + offset; - const void* end = std::memchr(begin, '\0', maximum_size); + const void* end = std::memchr(begin, '\0', maximum_size); if (end == nullptr) { fail("ELF symbol name is not terminated"); } @@ -51,10 +52,9 @@ void verify_symbols(const std::string& path) { } const std::vector file{std::istreambuf_iterator(input), std::istreambuf_iterator()}; - const Elf64_Ehdr header = read_object(file, 0); + const auto header = read_object(file, 0); if (std::memcmp(header.e_ident, ELFMAG, SELFMAG) != 0 || - header.e_ident[EI_CLASS] != ELFCLASS64 || - header.e_ident[EI_DATA] != ELFDATA2LSB || + header.e_ident[EI_CLASS] != ELFCLASS64 || header.e_ident[EI_DATA] != ELFDATA2LSB || header.e_shentsize != sizeof(Elf64_Shdr)) { fail("queue library is not a little-endian ELF64 file"); } @@ -62,10 +62,9 @@ void verify_symbols(const std::string& path) { Elf64_Shdr dynamic_symbols{}; bool found_dynamic_symbols = false; for (std::size_t i = 0; i < header.e_shnum; ++i) { - const Elf64_Shdr section = read_object( - file, header.e_shoff + i * sizeof(Elf64_Shdr)); + const auto section = read_object(file, header.e_shoff + i * sizeof(Elf64_Shdr)); if (section.sh_type == SHT_DYNSYM) { - dynamic_symbols = section; + dynamic_symbols = section; found_dynamic_symbols = true; break; } @@ -75,19 +74,16 @@ void verify_symbols(const std::string& path) { fail("ELF dynamic symbol table is invalid"); } - const Elf64_Shdr string_table = read_object( + const auto string_table = read_object( file, header.e_shoff + dynamic_symbols.sh_link * sizeof(Elf64_Shdr)); bool found_isolated_symbol = false; - for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; - offset += sizeof(Elf64_Sym)) { - const Elf64_Sym symbol = read_object( - file, dynamic_symbols.sh_offset + offset); + for (std::size_t offset = 0; offset < dynamic_symbols.sh_size; offset += sizeof(Elf64_Sym)) { + const auto symbol = read_object(file, dynamic_symbols.sh_offset + offset); if (symbol.st_name >= string_table.sh_size) { continue; } - const std::string name = read_string( - file, string_table.sh_offset + symbol.st_name, - string_table.sh_size - symbol.st_name); + const std::string name = read_string(file, string_table.sh_offset + symbol.st_name, + string_table.sh_size - symbol.st_name); if (name.starts_with(kGlobalNamespacePrefix)) { fail("queue library exports a global moodycamel symbol: " + name); } @@ -100,9 +96,14 @@ void verify_symbols(const std::string& path) { } } -} // namespace +} // namespace int main(int argc, char** argv) { - assert(argc == 2); - verify_symbols(argv[1]); + try { + assert(argc == 2); + verify_symbols(argv[1]); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } } diff --git a/udf-runner-cpp/v2/nm_runner.hpp b/udf-runner-cpp/v2/nm_runner.hpp new file mode 100644 index 0000000..cc41b98 --- /dev/null +++ b/udf-runner-cpp/v2/nm_runner.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace exasol::udf::v2::test { + +inline std::string run_nm(const char* option, const std::string& path) { + std::array pipe_fds{}; + if (::pipe(pipe_fds.data()) != 0) { + throw std::runtime_error("cannot create pipe for nm"); + } + + const pid_t child = ::fork(); + if (child == -1) { + ::close(pipe_fds[0]); + ::close(pipe_fds[1]); + throw std::runtime_error("cannot fork nm"); + } + if (child == 0) { + if (::dup2(pipe_fds[1], STDOUT_FILENO) == -1) { + _exit(126); + } + ::close(pipe_fds[0]); + ::close(pipe_fds[1]); + ::execlp("nm", "nm", option, "--defined-only", "--", path.c_str(), + static_cast(nullptr)); + _exit(127); + } + + ::close(pipe_fds[1]); + std::string output; + std::array buffer{}; + for (;;) { + const ssize_t count = ::read(pipe_fds[0], buffer.data(), buffer.size()); + if (count > 0) { + output.append(buffer.data(), static_cast(count)); + } else if (count == 0) { + break; + } else if (errno != EINTR) { + ::close(pipe_fds[0]); + ::waitpid(child, nullptr, 0); + throw std::runtime_error("cannot read nm output"); + } + } + ::close(pipe_fds[0]); + + int status = 0; + if (::waitpid(child, &status, 0) == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + throw std::runtime_error("nm failed while inspecting " + path); + } + return output; +} + +} // namespace exasol::udf::v2::test diff --git a/udf-runner-cpp/v2/tools/clang-format/.clang-format b/udf-runner-cpp/v2/tools/clang-format/.clang-format new file mode 100644 index 0000000..48d0af7 --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-format/.clang-format @@ -0,0 +1,69 @@ + + +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BreakBeforeBraces: Attach +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 3 +NamespaceIndentation: Inner +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++20 +TabWidth: 4 +UseTab: Never diff --git a/udf-runner-cpp/v2/tools/clang-format/BUILD b/udf-runner-cpp/v2/tools/clang-format/BUILD new file mode 100644 index 0000000..2d65a0a --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-format/BUILD @@ -0,0 +1,5 @@ +filegroup( + name = "clang-format-config", + srcs = [".clang-format"], + visibility = ["//visibility:public"], +) diff --git a/udf-runner-cpp/v2/tools/clang-tidy/.clang-tidy b/udf-runner-cpp/v2/tools/clang-tidy/.clang-tidy new file mode 100644 index 0000000..ccc41d9 --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-tidy/.clang-tidy @@ -0,0 +1,563 @@ + +# Global canonical list of clang-tidy activated and deactivated checks for full code base. +# Activate Disabled Check: Remove the leading "-" from check entry below if applicable. +# The general rule for check disabling: Discuss/vote with all affected developers if a disable is common sense. +Checks: > + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-branch-clone, + bugprone-copy-constructor-init, + bugprone-dangling-handle, + bugprone-dynamic-static-initializers, + bugprone-exception-escape, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-forwarding-reference-overload, + bugprone-inaccurate-erase, + bugprone-incorrect-roundings, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-lambda-function-name, + bugprone-macro-parentheses, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-operator-in-strlen-in-alloc, + bugprone-misplaced-widening-cast, + bugprone-move-forwarding-reference, + bugprone-multiple-statement-macro, + bugprone-not-null-terminated-result, + bugprone-parent-virtual-call, + bugprone-posix-return, + bugprone-signed-char-misuse, + bugprone-sizeof-container, + bugprone-sizeof-expression, + bugprone-string-constructor, + bugprone-string-integer-assignment, + bugprone-string-literal-with-embedded-nul, + bugprone-suspicious-enum-usage, + bugprone-suspicious-memset-usage, + bugprone-suspicious-missing-comma, + bugprone-suspicious-semicolon, + bugprone-suspicious-string-compare, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-too-small-loop-variable, + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-self-assignment, + bugprone-unused-raii, + bugprone-unused-return-value, + bugprone-use-after-move, + bugprone-virtual-near-miss, + cert-dcl50-cpp, + cert-dcl58-cpp, + cert-env33-c, + cert-err34-c, + cert-err52-cpp, + cert-err58-cpp, + cert-flp30-c, + cert-mem57-cpp, + cert-msc50-cpp, + cert-msc51-cpp, + cert-oop58-cpp, + clang-analyzer-apiModeling.TrustNonnull, + clang-analyzer-apiModeling.google.GTest, + clang-analyzer-apiModeling.llvm.CastValue, + clang-analyzer-apiModeling.llvm.ReturnValue, + clang-analyzer-core.CallAndMessage, + clang-analyzer-core.DivideZero, + clang-analyzer-core.DynamicTypePropagation, + clang-analyzer-core.NonNullParamChecker, + clang-analyzer-core.NonnilStringConstants, + clang-analyzer-core.NullDereference, + clang-analyzer-core.StackAddrEscapeBase, + clang-analyzer-core.StackAddressEscape, + clang-analyzer-core.UndefinedBinaryOperatorResult, + clang-analyzer-core.VLASize, + clang-analyzer-core.builtin.BuiltinFunctions, + clang-analyzer-core.builtin.NoReturnFunctions, + clang-analyzer-core.uninitialized.ArraySubscript, + clang-analyzer-core.uninitialized.Assign, + clang-analyzer-core.uninitialized.Branch, + clang-analyzer-core.uninitialized.CapturedBlockVariable, + clang-analyzer-core.uninitialized.UndefReturn, + clang-analyzer-cplusplus.InnerPointer, + clang-analyzer-cplusplus.Move, + clang-analyzer-cplusplus.NewDelete, + clang-analyzer-cplusplus.NewDeleteLeaks, + clang-analyzer-cplusplus.PureVirtualCall, + clang-analyzer-cplusplus.SelfAssignment, + clang-analyzer-cplusplus.SmartPtrModeling, + clang-analyzer-cplusplus.VirtualCallModeling, + clang-analyzer-deadcode.DeadStores, + clang-analyzer-fuchsia.HandleChecker, + clang-analyzer-nullability.NullPassedToNonnull, + clang-analyzer-nullability.NullReturnedFromNonnull, + clang-analyzer-nullability.NullabilityBase, + clang-analyzer-nullability.NullableDereferenced, + clang-analyzer-nullability.NullablePassedToNonnull, + clang-analyzer-nullability.NullableReturnedFromNonnull, + clang-analyzer-optin.cplusplus.UninitializedObject, + clang-analyzer-optin.cplusplus.VirtualCall, + clang-analyzer-optin.performance.GCDAntipattern, + clang-analyzer-optin.performance.Padding, + clang-analyzer-optin.portability.UnixAPI, + clang-analyzer-security.FloatLoopCounter, + clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + clang-analyzer-security.insecureAPI.SecuritySyntaxChecker, + clang-analyzer-security.insecureAPI.UncheckedReturn, + clang-analyzer-security.insecureAPI.bcmp, + clang-analyzer-security.insecureAPI.bcopy, + clang-analyzer-security.insecureAPI.bzero, + clang-analyzer-security.insecureAPI.decodeValueOfObjCType, + clang-analyzer-security.insecureAPI.getpw, + clang-analyzer-security.insecureAPI.gets, + clang-analyzer-security.insecureAPI.mkstemp, + clang-analyzer-security.insecureAPI.mktemp, + clang-analyzer-security.insecureAPI.rand, + clang-analyzer-security.insecureAPI.strcpy, + clang-analyzer-security.insecureAPI.vfork, + clang-analyzer-unix.API, + clang-analyzer-unix.DynamicMemoryModeling, + clang-analyzer-unix.Malloc, + clang-analyzer-unix.MallocSizeof, + clang-analyzer-unix.MismatchedDeallocator, + clang-analyzer-unix.StdCLibraryFunctions, + clang-analyzer-unix.Vfork, + clang-analyzer-unix.cstring.BadSizeArg, + clang-analyzer-unix.cstring.CStringModeling, + clang-analyzer-unix.cstring.NullArg, + clang-analyzer-valist.CopyToSelf, + clang-analyzer-valist.Uninitialized, + clang-analyzer-valist.Unterminated, + clang-analyzer-valist.ValistBase, + cppcoreguidelines-avoid-goto, + cppcoreguidelines-init-variables, + cppcoreguidelines-interfaces-global-init, + cppcoreguidelines-macro-usage, + cppcoreguidelines-narrowing-conversions, + cppcoreguidelines-no-malloc, + cppcoreguidelines-pro-bounds-constant-array-index, + cppcoreguidelines-pro-type-const-cast, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-pro-type-static-cast-downcast, + cppcoreguidelines-pro-type-union-access, + cppcoreguidelines-slicing, + cppcoreguidelines-special-member-functions, + fuchsia-multiple-inheritance, + fuchsia-statically-constructed-objects, + fuchsia-trailing-return, + fuchsia-virtual-inheritance, + google-build-explicit-make-pair, + google-build-namespaces, + google-default-arguments, + google-explicit-constructor, + google-global-names-in-headers, + google-readability-avoid-underscore-in-googletest-name, + google-readability-casting, + google-runtime-int, + google-runtime-operator, + google-upgrade-googletest-case, + hicpp-exception-baseclass, + hicpp-multiway-paths-covered, + hicpp-no-assembler, + hicpp-signed-bitwise, + llvm-namespace-comment, + misc-definitions-in-headers, + misc-misplaced-const, + misc-new-delete-overloads, + misc-non-copyable-objects, + misc-non-private-member-variables-in-classes, + misc-redundant-expression, + misc-static-assert, + misc-throw-by-value-catch-by-reference, + misc-unconventional-assign-operator, + misc-uniqueptr-reset-release, + misc-unused-alias-decls, + misc-unused-parameters, + misc-unused-using-decls, + modernize-avoid-bind, + modernize-avoid-c-arrays, + modernize-concat-nested-namespaces, + modernize-deprecated-headers, + modernize-deprecated-ios-base-aliases, + modernize-loop-convert, + modernize-make-shared, + modernize-make-unique, + modernize-pass-by-value, + modernize-raw-string-literal, + modernize-redundant-void-arg, + modernize-replace-auto-ptr, + modernize-replace-random-shuffle, + modernize-shrink-to-fit, + modernize-unary-static-assert, + modernize-use-auto, + modernize-use-bool-literals, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-use-nodiscard, + modernize-use-noexcept, + modernize-use-nullptr, + modernize-use-override, + modernize-use-transparent-functors, + modernize-use-uncaught-exceptions, + modernize-use-using, + performance-faster-string-find, + performance-for-range-copy, + performance-implicit-conversion-in-loop, + performance-inefficient-algorithm, + performance-inefficient-string-concatenation, + performance-inefficient-vector-operation, + performance-move-const-arg, + performance-move-constructor-init, + performance-no-automatic-move, + performance-noexcept-move-constructor, + performance-trivially-destructible, + performance-type-promotion-in-math-fn, + performance-unnecessary-copy-initialization, + performance-unnecessary-value-param, + portability-simd-intrinsics, + readability-braces-around-statements, + readability-const-return-type, + readability-container-size-empty, + readability-delete-null-pointer, + readability-function-size, + readability-identifier-naming, + readability-implicit-bool-conversion, + readability-inconsistent-declaration-parameter-name, + readability-isolate-declaration, + readability-make-member-function-const, + readability-misleading-indentation, + readability-misplaced-array-index, + readability-named-parameter, + readability-non-const-parameter, + readability-qualified-auto, + readability-redundant-access-specifiers, + readability-redundant-control-flow, + readability-redundant-declaration, + readability-redundant-function-ptr-dereference, + readability-redundant-member-init, + readability-redundant-preprocessor, + readability-redundant-smartptr-get, + readability-redundant-string-cstr, + readability-redundant-string-init, + readability-simplify-boolean-expr, + readability-simplify-subscript-expr, + readability-static-accessed-through-instance, + readability-static-definition-in-anonymous-namespace, + readability-string-compare, + readability-uniqueptr-delete-release, + readability-uppercase-literal-suffix + # The following lists the deactivated checks. + # + # Part 1: New checks in clang 18 yet to be discussed/voted + # + # -altera-id-dependent-backward-branch, + # -altera-kernel-name-restriction, + # -altera-single-work-item-barrier, + # -altera-struct-pack-align, + # -altera-unroll-loops, + # -bugprone-assignment-in-if-condition, + # -bugprone-casting-through-void, + # -bugprone-chained-comparison, + # -bugprone-compare-pointer-to-member-virtual-function, + # -bugprone-easily-swappable-parameters, + # -bugprone-empty-catch, + # -bugprone-implicit-widening-of-multiplication-result, + # -bugprone-inc-dec-in-conditions, + # -bugprone-incorrect-enable-if, + # -bugprone-misplaced-pointer-arithmetic-in-alloc, + # -bugprone-multi-level-implicit-pointer-conversion, + # -bugprone-multiple-new-in-one-expression, + # -bugprone-no-escape, + # -bugprone-non-zero-enum-to-bool-conversion, + # -bugprone-optional-value-conversion, + # -bugprone-redundant-branch-condition, + # -bugprone-reserved-identifier, + # -bugprone-shared-ptr-array-mismatch, + # -bugprone-signal-handler, + # -bugprone-spuriously-wake-up-functions, + # -bugprone-standalone-empty, + # -bugprone-stringview-nullptr, + # -bugprone-suspicious-include, + # -bugprone-suspicious-memory-comparison, + # -bugprone-suspicious-realloc-usage, + # -bugprone-switch-missing-default-case, + # -bugprone-unchecked-optional-access, + # -bugprone-unhandled-exception-at-new, + # -bugprone-unique-ptr-array-mismatch, + # -bugprone-unsafe-functions, + # -bugprone-unused-local-non-trivial-variable, + # -cert-err33-c, + # -cert-oop57-cpp, + # -clang-analyzer-apiModeling.Errno, + # -clang-analyzer-apiModeling.TrustReturnsNonnull, + # -clang-analyzer-core.BitwiseShift, + # -clang-analyzer-core.CallAndMessageModeling, + # -clang-analyzer-core.uninitialized.NewArraySize, + # -clang-analyzer-cplusplus.PlacementNew, + # -clang-analyzer-cplusplus.StringChecker, + # -clang-analyzer-optin.core.EnumCastOutOfRange, + # -clang-analyzer-security.cert.env.InvalidPtr, + # -clang-analyzer-unix.Errno, + # -clang-analyzer-webkit.NoUncountedMemberChecker, + # -clang-analyzer-webkit.RefCntblBaseVirtualDtor, + # -clang-analyzer-webkit.UncountedLambdaCapturesChecker, + # -concurrency-mt-unsafe, + # -concurrency-thread-canceltype-asynchronous, + # -cppcoreguidelines-avoid-capturing-lambda-coroutines, + # -cppcoreguidelines-avoid-const-or-ref-data-members, + # -cppcoreguidelines-avoid-do-while, + # -cppcoreguidelines-avoid-reference-coroutine-parameters, + # -cppcoreguidelines-avoid-non-const-global-variables, + # -cppcoreguidelines-misleading-capture-default-by-value, + # -cppcoreguidelines-missing-std-forward, + # -cppcoreguidelines-no-suspend-with-lock, + # -cppcoreguidelines-prefer-member-initializer, + # -cppcoreguidelines-rvalue-reference-param-not-moved, + # -cppcoreguidelines-virtual-class-destructor, + # -llvmlibc-callee-namespace, + # -llvmlibc-implementation-in-namespace, + # -llvmlibc-restrict-system-libc-headers, + # -misc-confusable-identifiers, + # -misc-const-correctness, + # -misc-coroutine-hostile-raii, + # -misc-header-include-cycle, + # -misc-include-cleaner, + # -misc-misleading-bidirectional, + # -misc-misleading-identifier, + # -misc-no-recursion, + # -misc-use-anonymous-namespace, + # -modernize-macro-to-enum, + # -modernize-replace-disallow-copy-and-assign-macro, + # -modernize-type-traits, + # -modernize-use-constraints, + # -modernize-use-starts-ends-with, + # -modernize-use-std-numbers, + # -modernize-use-std-print, + # -performance-avoid-endl, + # -performance-enum-size, + # -performance-no-int-to-ptr, + # -performance-noexcept-destructor, + # -performance-noexcept-swap, + # -portability-restrict-system-includes, + # -portability-std-allocator-const, + # -readability-avoid-nested-conditional-operator, + # -readability-avoid-return-with-void-value, + # -readability-avoid-unconditional-preprocessor-if, + # -readability-container-contains, + # -readability-container-data-pointer, + # -readability-duplicate-include, + # -readability-function-cognitive-complexity, + # -readability-identifier-length, + # -readability-operators-representation, + # -readability-redundant-casting, + # -readability-redundant-inline-specifier, + # -readability-reference-to-constructed-temporary, + # -readability-suspicious-call-argument, + # -readability-use-anyofallof, + # + # Part 2: Disabled checks - Genrally disabled after vote. + # + # -cert-dcl21-cpp, + # -cert-err60-cpp, + # -clang-diagnostic-error, + # -clang-diagnostic-unused-command-line-argument, + # -cppcoreguidelines-owning-memory, + # -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + # -cppcoreguidelines-pro-bounds-pointer-arithmetic, + # -cppcoreguidelines-pro-type-reinterpret-cast, + # -cppcoreguidelines-pro-type-vararg, + # -fuchsia-default-arguments-calls, + # -fuchsia-default-arguments-declarations, + # -fuchsia-overloaded-operator, + # -google-readability-todo, + # -llvm-header-guard, + # -llvm-include-order, + # -modernize-return-braced-init-list, + # -readability-else-after-return, + # -modernize-use-trailing-return-type, + # -readability-avoid-const-params-in-decls, + # + # Part 3: Aliases (taken from https://releases.llvm.org/18.1.8/tools/clang/tools/extra/docs/clang-tidy/checks/list.html excluding clang-analyzer-*) + # + # -bugprone-narrowing-conversions, # cppcoreguidelines-narrowing-conversions + # -cert-con36-c, # bugprone-spuriously-wake-up-functions + # -cert-con54-cpp, # bugprone-spuriously-wake-up-functions + # -cert-dcl37-c, # bugprone-reserved-identifier + # -cert-dcl51-cpp, # bugprone-reserved-identifier + # -cert-dcl03-c, # misc-static-assert + # -cert-dcl16-c, # readability-uppercase-literal-suffix + # -cert-dcl54-cpp, # misc-new-delete-overloads + # -cert-dcl59-cpp, # google-build-namespaces + # -cert-err09-cpp, # misc-throw-by-value-catch-by-reference + # -cert-err61-cpp, # misc-throw-by-value-catch-by-reference + # -cert-exp42-c, # bugprone-suspicious-memory-comparison + # -cert-fio38-c, # misc-non-copyable-objects + # -cert-flp37-c, # bugprone-suspicious-memory-comparison + # -cert-msc24-c, # bugprone-unsafe-functions + # -cert-msc30-c, # cert-msc50-cpp + # -cert-msc32-c, # cert-msc51-cpp + # -cert-msc33-c, # bugprone-unsafe-functions + # -cert-msc54-cpp, # bugprone-signal-handler + # -cert-oop11-cpp, # performance-move-constructor-init + # -cert-oop54-cpp, # bugprone-unhandled-self-assignment + # -cert-pos44-c, # bugprone-bad-signal-to-kill-thread + # -cert-pos47-c, # concurrency-thread-canceltype-asynchronous + # -cert-sig30-c, # bugprone-signal-handler + # -cert-str34-c, # bugprone-signed-char-misuse + # -clang-analyzer-optin.mpi.MPI-Checker, # Clang Static Analyzer optin.mpi.MPI-Checker + # -clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker, # Clang Static Analyzer optin.osx.cocoa.localizability.EmptyLocalizationContextChecker + # -clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker, # Clang Static Analyzer optin.osx.cocoa.localizability.NonLocalizedStringChecker + # -clang-analyzer-osx.API, # Clang Static Analyzer osx.API + # -clang-analyzer-osx.SecKeychainAPI, # Clang Static Analyzer osx.SecKeychainAPI + # -clang-analyzer-osx.cocoa.AtSync, # Clang Static Analyzer osx.cocoa.AtSync + # -clang-analyzer-osx.cocoa.ClassRelease, # Clang Static Analyzer osx.cocoa.ClassRelease + # -clang-analyzer-osx.cocoa.Dealloc, # Clang Static Analyzer osx.cocoa.Dealloc + # -clang-analyzer-osx.cocoa.IncompatibleMethodTypes, # Clang Static Analyzer osx.cocoa.IncompatibleMethodTypes + # -clang-analyzer-osx.cocoa.NSAutoreleasePool, # Clang Static Analyzer osx.cocoa.NSAutoreleasePool + # -clang-analyzer-osx.cocoa.NSError, # Clang Static Analyzer osx.cocoa.NSError + # -clang-analyzer-osx.cocoa.NilArg, # Clang Static Analyzer osx.cocoa.NilArg + # -clang-analyzer-osx.cocoa.ObjCGenerics, # Clang Static Analyzer osx.cocoa.ObjCGenerics + # -clang-analyzer-osx.cocoa.RetainCount, # Clang Static Analyzer osx.cocoa.RetainCount + # -clang-analyzer-osx.cocoa.SelfInit, # Clang Static Analyzer osx.cocoa.SelfInit + # -clang-analyzer-osx.cocoa.SuperDealloc, # Clang Static Analyzer osx.cocoa.SuperDealloc + # -clang-analyzer-osx.cocoa.UnusedIvars, # Clang Static Analyzer osx.cocoa.UnusedIvars + # -clang-analyzer-osx.cocoa.VariadicMethodTypes, # Clang Static Analyzer osx.cocoa.VariadicMethodTypes + # -clang-analyzer-osx.coreFoundation.CFError, # Clang Static Analyzer osx.coreFoundation.CFError + # -clang-analyzer-osx.coreFoundation.CFNumber, # Clang Static Analyzer osx.coreFoundation.CFNumber + # -clang-analyzer-osx.coreFoundation.CFRetainRelease, # Clang Static Analyzer osx.coreFoundation.CFRetainRelease + # -clang-analyzer-osx.coreFoundation.containers.OutOfBounds, # Clang Static Analyzer osx.coreFoundation.containers.OutOfBounds + # -clang-analyzer-osx.coreFoundation.containers.PointerSizedValues, # Clang Static Analyzer osx.coreFoundation.containers.PointerSizedValues + # -cppcoreguidelines-avoid-c-arrays, # modernize-avoid-c-arrays + # -cppcoreguidelines-avoid-magic-numbers, # readability-magic-numbers + # -cppcoreguidelines-c-copy-assignment-signature, # misc-unconventional-assign-operator + # -cppcoreguidelines-explicit-virtual-functions, # modernize-use-override + # -cppcoreguidelines-macro-to-enum, # modernize-macro-to-enum + # -cppcoreguidelines-noexcept-destructor, # performance-noexcept-destructor + # -cppcoreguidelines-noexcept-move-operations, # performance-noexcept-move-constructor + # -cppcoreguidelines-noexcept-swap, # performance-noexcept-swap + # -cppcoreguidelines-non-private-member-variables-in-classes, # misc-non-private-member-variables-in-classes + # -cppcoreguidelines-use-default-member-init, # modernize-use-default-member-init + # -fuchsia-header-anon-namespaces, # google-build-namespaces + # -google-readability-braces-around-statements, # readability-braces-around-statements + # -google-readability-function-size, # readability-function-size + # -google-readability-namespace-comments, # llvm-namespace-comment + # -hicpp-avoid-c-arrays, # modernize-avoid-c-arrays + # -hicpp-braces-around-statements, # readability-braces-around-statements + # -hicpp-deprecated-headers, # modernize-deprecated-headers + # -hicpp-explicit-conversions, # google-explicit-constructor + # -hicpp-function-size, # readability-function-size + # -hicpp-ignored-remove-result, # bugprone-unused-return-value + # -hicpp-invalid-access-moved, # bugprone-use-after-move + # -hicpp-member-init, # cppcoreguidelines-pro-type-member-init + # -hicpp-move-const-arg, # performance-move-const-arg + # -hicpp-named-parameter, # readability-named-parameter + # -hicpp-new-delete-operators, # misc-new-delete-overloads + # -hicpp-no-array-decay, # cppcoreguidelines-pro-bounds-array-to-pointer-decay + # -hicpp-no-malloc, # cppcoreguidelines-no-malloc + # -hicpp-noexcept-move, # performance-noexcept-move-constructor + # -hicpp-special-member-functions, # cppcoreguidelines-special-member-functions + # -hicpp-static-assert, # misc-static-assert + # -hicpp-undelegated-constructor, # bugprone-undelegated-constructor + # -hicpp-uppercase-literal-suffix, # readability-uppercase-literal-suffix + # -hicpp-use-auto, # modernize-use-auto + # -hicpp-use-emplace, # modernize-use-emplace + # -hicpp-use-equals-default, # modernize-use-equals-default + # -hicpp-use-equals-delete, # modernize-use-equals-delete + # -hicpp-use-noexcept, # modernize-use-noexcept + # -hicpp-use-nullptr, # modernize-use-nullptr + # -hicpp-use-override, # modernize-use-override + # -hicpp-vararg, # cppcoreguidelines-pro-type-vararg + # -llvm-else-after-return, # readability-else-after-return + # -llvm-qualified-auto, # readability-qualified-auto + # abseil suggestion do not apply to our codebase (taken from https://releases.llvm.org/18.1.8/tools/clang/tools/extra/docs/clang-tidy/checks/list.html) + # -abseil-cleanup-ctad, + # -abseil-duration-addition, + # -abseil-duration-comparison, + # -abseil-duration-conversion-cast, + # -abseil-duration-division, + # -abseil-duration-factory-float, + # -abseil-duration-factory-scale, + # -abseil-duration-subtraction, + # -abseil-duration-unnecessary-conversion, + # -abseil-faster-strsplit-delimiter, + # -abseil-no-internal-dependencies, + # -abseil-no-namespace, + # -abseil-redundant-strcat-calls, + # -abseil-str-cat-append, + # -abseil-string-find-startswith, + # -abseil-string-find-str-contains, + # -abseil-time-comparison, + # -abseil-time-subtraction, + # -abseil-upgrade-duration-conversions, + # SPOT-13713: The following checks are irrelevant to our codebase + # -android-cloexec-accept, + # -android-cloexec-accept4, + # -android-cloexec-creat, + # -android-cloexec-dup, + # -android-cloexec-epoll-create, + # -android-cloexec-epoll-create1, + # -android-cloexec-fopen, + # -android-cloexec-inotify-init, + # -android-cloexec-inotify-init1, + # -android-cloexec-memfd-create, + # -android-cloexec-open, + # -android-cloexec-pipe, + # -android-cloexec-pipe2, + # -android-cloexec-socket, + # -android-comparison-in-temp-failure-retry, + # -boost-use-to-string, + # -clang-analyzer-optin.osx.OSObjectCStyleCast, + # -clang-analyzer-osx.MIG, + # -clang-analyzer-osx.NSOrCFErrorDerefChecker, + # -clang-analyzer-osx.NumberObjectConversion, + # -clang-analyzer-osx.OSObjectRetainCount, + # -clang-analyzer-osx.ObjCProperty, + # -clang-analyzer-osx.cocoa.AutoreleaseWrite, + # -clang-analyzer-osx.cocoa.Loops, + # -clang-analyzer-osx.cocoa.MissingSuperCall, + # -clang-analyzer-osx.cocoa.NonNilReturnValue, + # -clang-analyzer-osx.cocoa.RetainCountBase, + # -clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak, + # -darwin-avoid-spinlock, + # -darwin-dispatch-once-nonstatic, + # -google-objc-avoid-nsobject-new, + # -google-objc-avoid-throwing-exception, + # -google-objc-function-naming, + # -google-objc-global-variable-declaration, + # -hicpp-avoid-goto, + # -linuxkernel-must-check-errs, + # -llvm-prefer-isa-or-dyn-cast-in-conditionals, + # -llvm-prefer-register-over-unsigned, + # -llvm-twine-local, + # -llvmlibc-inline-function-decl, + # -mpi-buffer-deref, + # -mpi-type-mismatch, + # -objc-assert-equals, + # -objc-avoid-nserror-init, + # -objc-dealloc-in-category, + # -objc-forbidden-subclassing, + # -objc-missing-hash, + # -objc-nsdate-formatter, + # -objc-nsinvocation-argument-lifetime, + # -objc-property-declaration, + # -objc-super-self, + # -openmp-exception-escape, + # -openmp-use-default-none, + # -zircon-temporary-objects, +HeaderFilterRegex: "" +WarningsAsErrors: "*" +CheckOptions: + - key: hicpp-signed-bitwise.IgnorePositiveIntegerLiterals + value: 'true' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;10' +ExtraArgsBefore: + - "-std=c++17" + - "-xc++" diff --git a/udf-runner-cpp/v2/tools/clang-tidy/BUILD b/udf-runner-cpp/v2/tools/clang-tidy/BUILD new file mode 100644 index 0000000..0d325fb --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-tidy/BUILD @@ -0,0 +1,17 @@ +filegroup( + name = "config", + srcs = [".clang-tidy"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "wrapper", + srcs = ["clang-tidy-wrapper.sh"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "apply-replacements-wrapper", + srcs = ["clang-apply-replacements-wrapper.sh"], + visibility = ["//visibility:public"], +) diff --git a/udf-runner-cpp/v2/tools/clang-tidy/clang-apply-replacements-wrapper.sh b/udf-runner-cpp/v2/tools/clang-tidy/clang-apply-replacements-wrapper.sh new file mode 100755 index 0000000..d44214c --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-tidy/clang-apply-replacements-wrapper.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec clang-apply-replacements-22 "$@" diff --git a/udf-runner-cpp/v2/tools/clang-tidy/clang-tidy-wrapper.sh b/udf-runner-cpp/v2/tools/clang-tidy/clang-tidy-wrapper.sh new file mode 100755 index 0000000..3338257 --- /dev/null +++ b/udf-runner-cpp/v2/tools/clang-tidy/clang-tidy-wrapper.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec clang-tidy-22 --removed-arg=-fno-canonical-system-headers "$@" diff --git a/udf-runner-cpp/v2/udf_protocol.cc b/udf-runner-cpp/v2/udf_protocol.cc index 0ed1120..0aa39f1 100644 --- a/udf-runner-cpp/v2/udf_protocol.cc +++ b/udf-runner-cpp/v2/udf_protocol.cc @@ -3,10 +3,9 @@ namespace exasol::udf::protocol { bool VerifyFrameBuffer(const void* data, std::size_t size) { - using IsolatedVerifier = - exasol::udf::v2::third_party::flatbuffers::Verifier; + using IsolatedVerifier = exasol::udf::v2::third_party::flatbuffers::Verifier; IsolatedVerifier verifier(static_cast(data), size); return verifier.VerifyBuffer(); } -} // namespace exasol::udf::protocol +} // namespace exasol::udf::protocol diff --git a/udf-runner-cpp/v2/udf_protocol.fbs b/udf-runner-cpp/v2/udf_protocol.fbs new file mode 100644 index 0000000..2fab908 --- /dev/null +++ b/udf-runner-cpp/v2/udf_protocol.fbs @@ -0,0 +1,264 @@ +// First draft of the wire-level flatbuffer schema for the new UDF protocol. + +namespace exasol.udf.protocol; + +enum BufferTransport : uint8 { + Inline = 0, // buffers follow immediately on the same socket, unframed + Memfd = 1, // buffers are backed by a memfd passed out of band (e.g. SCM_RIGHTS) + OutOfBand = 2, // some other out-of-band mechanism +} + +// A small, self-owned subset of Apache Arrow's Schema.fbs/Message.fbs shape, +// NOT Arrow's own flatbuffer types. We define these natively so producing them +// only requires Arrow's stable public C++ API (Array::length(), +// Array::null_count(), ArrayData::buffers, Field::type(), ...) -- Arrow's own +// bare (unframed) IPC message bytes are only reachable via Arrow-internal +// headers (arrow/ipc/metadata_internal.h, what Flight itself uses), which +// aren't part of the installed public SDK. + +enum Precision : uint8 { Half = 0, Single = 1, Double = 2 } +enum DateUnit : uint8 { Day = 0, Millisecond = 1 } +enum TimeUnit : uint8 { Second = 0, Millisecond = 1, Microsecond = 2, Nanosecond = 3 } +enum IntervalUnit : uint8 { YearMonth = 0, DayTime = 1, MonthDayNano = 2 } +enum UnionMode : uint8 { Sparse = 0, Dense = 1 } + +table Null {} +table Int { bit_width: int32; is_signed: bool; } +table FloatingPoint { precision: Precision; } +table Binary {} +table Utf8 {} +table LargeBinary {} +table LargeUtf8 {} +table BinaryView {} +table Utf8View {} +table FixedSizeBinary { byte_width: int32; } +table Bool {} +table Date { unit: DateUnit; } +table Time { + unit: TimeUnit = Millisecond; + bit_width: int32 = 32; +} +table Timestamp { unit: TimeUnit; timezone: string; } +// Arrow calendar interval storage: +// YearMonth: signed int32 total months. +// DayTime: signed int32 days plus signed int32 milliseconds. +// MonthDayNano: signed int32 months, signed int32 days, and signed int64 nanoseconds. +table Interval { unit: IntervalUnit; } +table Decimal { precision: int32; scale: int32; bit_width: int32 = 128; } +table List {} +table LargeList {} +table ListView {} +table LargeListView {} +table FixedSizeList { list_size: int32; } +table Map { keys_sorted: bool; } +table Union { + mode: UnionMode; + type_ids: [int32]; +} +table Duration { unit: TimeUnit = Millisecond; } +// Trailing underscore mirrors Arrow's own Schema.fbs naming for this union member. +table Struct_ {} +// Run-End Encoded (REE): a compact encoding for runs of values (Arrow's own +// RunEndEncodedType). Has 0 buffers of its own -- no validity bitmap at this +// level, per the Arrow columnar spec -- and exactly 2 children in Field's +// children list, in fixed order: run_ends (an integer type), then values +// (any type). Reused as the storage type for extension types like a +// "range run" encoding (runs of s..s+n-1 instead of RLE's repeated value), +// where n_i is derived as run_ends[i] - run_ends[i-1] instead of being stored. +table RunEndEncoded {} + +union Type { + Null, Int, FloatingPoint, Binary, Utf8, Bool, Decimal, Date, Time, Timestamp, Interval, List, Struct_, Union, + FixedSizeBinary, FixedSizeList, Map, Duration, LargeBinary, LargeUtf8, LargeList, RunEndEncoded, BinaryView, + Utf8View, ListView, LargeListView, +} + +// Mirrors Arrow's own Schema.fbs KeyValue: a single string/string metadata +// entry. +table KeyValue { + key: string; + value: string; +} + +table Field { + name: string (required); + nullable: bool; + type: Type (required); + children: [Field]; // List/view/fixed-size-list's element type, Struct_'s + // member fields, Map's entries field, Union's members, + // or RunEndEncoded's [run_ends, values] pair + // Extension-type annotation, mirroring Arrow's own convention: a field + // representing an extension type is serialized using its storage type for + // `type` above, with the extension name/params carried here as + // ARROW:extension:name / ARROW:extension:metadata entries. A reader that + // doesn't recognize the name falls back to the plain storage type. + custom_metadata: [KeyValue]; +} + +table Schema { + fields: [Field]; +} + +struct FieldNode { + length: int64; + null_count: int64; +} + +struct Buffer { + offset: int64; + length: int64; +} + +table RecordBatchMetadata { + length: int64; + nodes: [FieldNode]; + buffers: [Buffer]; + // For each variable-buffer field in the flattened schema, in preorder, + // records the number of variable buffers belonging to that field. + // Empty when the schema has no variable-buffer fields (for example, no + // Utf8View or BinaryView fields). + variadic_buffer_counts: [int64]; +} + +table Version { + major: uint32; + minor: uint32; +} + +table ServerCapabilities { + supported_version: Version; + // TODO: high level protocol + // TODO: number of worker +} + +table KeepAlive {} + +// A JSON-encoded payload (the common case for call metadata, connection info, +// script content, etc.) or a raw binary payload (escape hatch for large/binary +// values that shouldn't be forced through JSON). +table StringPayload { + value: string; +} + +table BinaryPayload { + value: [ubyte]; +} + +union PayloadValue { + StringPayload, + BinaryPayload, +} + +// Opens a call. The enclosing connection and StreamMessage's stream_id identify +// this call for the connection's lifetime. The connection is implicit in the +// transport session, so it is not encoded in Frame. The same numeric stream_id +// on a different connection identifies a different logical stream. +// +// A call opened while another is active (e.g. a callback opened during Run) is +// just another independent call -- no parent/child link is carried on the wire. +// OpenCall carries no payload of its own: opening payloads travel as +// payloads set on the same StreamMessage instead of a duplicate field here. +// Additional payloads may also be sent later on the same connection-scoped +// stream_id while the call remains active. +table OpenCall { + call_name: string; +} + +// One named payload item sent by either side either on an active call's +// connection-scoped stream_id or on stream_id = 0 without any active call. `name` identifies the +// payload schema/meaning and therefore which parser to use for `payload` on the +// receiving side. +table Payload { + name: string; + payload: PayloadValue; +} + +// A StreamMessage may carry multiple payload items, including the opening +// payload(s) set alongside open_call on the same StreamMessage. Payloads +// may also be sent on a connection-scoped stream_id whose call is already active, or on +// stream_id = 0 without any active call. +table Payloads { + payloads: [Payload]; +} + +table Error { + code: string; + message: string; +} + +// Closes the call on this non-zero stream. When the enclosing StreamMessage +// also has error set, the close is abnormal; otherwise it is normal. +table CloseCall {} + +// Starts or acknowledges connection shutdown. This field is valid only on the +// control stream (stream_id = 0). The receiver replies with CloseConnection, +// then both peers close the underlying transport. +table CloseConnection {} + +// Grants transfer credit for one direction of a call's data stream. size is a +// byte budget for the permitted transfer window. row_id is the position to +// resume from when reset is true -- it is a seek position, not a row- +// correlation field between the two directions. +table Next { + byte_budget: uint32; + reset: bool = false; + row_id: uint64 = 0; +} + +// Sent once per direction of a call's data stream before, or in the same +// StreamMessage as, that direction's first DataRecordBatch. It contains this +// direction's column layout as our own native Schema table (see above -- not +// Arrow's own flatbuffer Schema). It is split from DataRecordBatch because the +// schema is only needed once, not repeated per batch. +// +// When present, correlation fields form an ordered prefix of schema.fields: +// group ID is field 0; row ID follows it, or is field 0 when no group ID is +// present. Call-specific rules decide which combinations are required. +table DataSchema { + schema: Schema; + has_group_id: bool = false; + has_row_id: bool = false; +} + +// Precedes the buffers for one RecordBatch in a direction of a call's data +// stream. metadata is our own native RecordBatchMetadata (row count, per- +// flattened FieldNode, per-buffer Buffer), populated from a real Arrow +// RecordBatch's public introspection API (Array::length(), null_count(), +// ArrayData::buffers) -- not Arrow's own flatbuffer RecordBatch message. The +// actual column buffers are transported separately per buffer_transport; view +// fields additionally use metadata.variadic_buffer_counts to delimit their +// variable buffer portions. +table DataRecordBatch { + buffer_transport: BufferTransport = Inline; + is_end_of_group: bool = false; + metadata: RecordBatchMetadata; +} + +// A StreamMessage is the typed composite payload carried inside a Frame. Each +// field is independently optional, so related parts such as CloseCall and +// Error can travel in one frame. Error without a close field is a non-terminal, +// stream-scoped diagnostic. stream_id = 0 is reserved for out-of-call control +// traffic such as ServerCapabilities, KeepAlive, and CloseConnection. +table StreamMessage { + server_capabilities: ServerCapabilities; + keep_alive: KeepAlive; + open_call: OpenCall; + payloads: Payloads; + close_call: CloseCall; + close_connection: CloseConnection; + next: Next; + data_schema: DataSchema; + data_record_batch: DataRecordBatch; + error: Error; +} + +// The single length-framed message type on the wire. The implicit transport +// connection and stream_id identify a logical stream. stream_id = 0 is the +// per-connection control stream used when client and server exchange messages +// independently of a call. A stream_id is not reused on its connection. +table Frame { + stream_id: uint64; + message: StreamMessage; +} + +root_type Frame; diff --git a/udf-runner-cpp/v2/udf_protocol.hpp b/udf-runner-cpp/v2/udf_protocol.hpp index e03dfd4..cd12e7d 100644 --- a/udf-runner-cpp/v2/udf_protocol.hpp +++ b/udf-runner-cpp/v2/udf_protocol.hpp @@ -14,6 +14,6 @@ namespace exasol::udf::protocol { bool VerifyFrameBuffer(const void* data, std::size_t size); -} // namespace exasol::udf::protocol +} // namespace exasol::udf::protocol -#endif // EXASOL_UDF_V2_UDF_PROTOCOL_HPP_ +#endif // EXASOL_UDF_V2_UDF_PROTOCOL_HPP_ diff --git a/udf-runner-cpp/v2/udf_protocol_static_symbol_leak_test.cc b/udf-runner-cpp/v2/udf_protocol_static_symbol_leak_test.cc index cfbf64d..599ea68 100644 --- a/udf-runner-cpp/v2/udf_protocol_static_symbol_leak_test.cc +++ b/udf-runner-cpp/v2/udf_protocol_static_symbol_leak_test.cc @@ -1,9 +1,12 @@ #include #include +#include #include #include #include +#include "nm_runner.hpp" + namespace { [[noreturn]] void fail(const std::string& message) { @@ -11,39 +14,34 @@ namespace { } void verify_symbols(const std::string& archive_path) { - const std::string command = "nm -g --defined-only -- '" + archive_path + "'"; - FILE* pipe = popen(command.c_str(), "r"); - if (pipe == nullptr) { - fail("cannot inspect protocol archive symbols"); - } - - char line[4096]; - while (std::fgets(line, sizeof(line), pipe) != nullptr) { - const std::string symbol(line); + const std::string command = exasol::udf::v2::test::run_nm("-g", archive_path); + std::istringstream lines(command); + std::string symbol; + while (std::getline(lines, symbol)) { const std::size_t name_start = symbol.find_last_of(' '); if (name_start != std::string::npos && symbol.compare(name_start + 1, 16, "_ZN11flatbuffers") == 0) { - pclose(pipe); fail("protocol archive exports a global flatbuffers symbol: " + symbol); } } - - if (pclose(pipe) != 0) { - fail("cannot complete protocol archive symbol inspection"); - } } -} // namespace +} // namespace int main(int argc, char** argv) { - assert(argc > 1); - bool found_archive = false; - for (int index = 1; index < argc; ++index) { - const std::string_view path(argv[index]); - if (path.ends_with(".a")) { - verify_symbols(std::string(path)); - found_archive = true; + try { + assert(argc > 1); + bool found_archive = false; + for (int index = 1; index < argc; ++index) { + const std::string_view path(argv[index]); + if (path.ends_with(".a")) { + verify_symbols(std::string(path)); + found_archive = true; + } } + assert(found_archive); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; } - assert(found_archive); } diff --git a/udf-runner-cpp/v2/udf_protocol_symbol_leak_test.cc b/udf-runner-cpp/v2/udf_protocol_symbol_leak_test.cc index b1d58e6..b1eb5b7 100644 --- a/udf-runner-cpp/v2/udf_protocol_symbol_leak_test.cc +++ b/udf-runner-cpp/v2/udf_protocol_symbol_leak_test.cc @@ -1,8 +1,11 @@ #include #include +#include #include #include +#include "nm_runner.hpp" + namespace { [[noreturn]] void fail(const std::string& message) { @@ -10,31 +13,26 @@ namespace { } void verify_symbols(const std::string& library_path) { - const std::string command = "nm -D --defined-only -- '" + library_path + "'"; - FILE* pipe = popen(command.c_str(), "r"); - if (pipe == nullptr) { - fail("cannot inspect protocol library symbols"); - } - - char line[4096]; - while (std::fgets(line, sizeof(line), pipe) != nullptr) { - const std::string symbol(line); + const std::string command = exasol::udf::v2::test::run_nm("-D", library_path); + std::istringstream lines(command); + std::string symbol; + while (std::getline(lines, symbol)) { const std::size_t name_start = symbol.find_last_of(' '); if (name_start != std::string::npos && symbol.compare(name_start + 1, 16, "_ZN11flatbuffers") == 0) { - pclose(pipe); fail("protocol library exports a global flatbuffers symbol: " + symbol); } } - - if (pclose(pipe) != 0) { - fail("cannot complete protocol library symbol inspection"); - } } -} // namespace +} // namespace int main(int argc, char** argv) { - assert(argc == 2); - verify_symbols(argv[1]); + try { + assert(argc == 2); + verify_symbols(argv[1]); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } } diff --git a/udf-runner-cpp/v2/udf_protocol_test.cc b/udf-runner-cpp/v2/udf_protocol_test.cc index 1d48ee7..8c9a8cf 100644 --- a/udf-runner-cpp/v2/udf_protocol_test.cc +++ b/udf-runner-cpp/v2/udf_protocol_test.cc @@ -7,13 +7,11 @@ int main() { exasol::udf::v2::third_party::flatbuffers::FlatBufferBuilder builder; const auto call_name = builder.CreateString("example"); const auto open_call = exasol::udf::protocol::CreateOpenCall(builder, call_name); - const auto message = exasol::udf::protocol::CreateStreamMessage( - builder, 0, 0, open_call); - const auto frame = exasol::udf::protocol::CreateFrame(builder, 7, message); + const auto message = exasol::udf::protocol::CreateStreamMessage(builder, 0, 0, open_call); + const auto frame = exasol::udf::protocol::CreateFrame(builder, 7, message); builder.Finish(frame); - assert(exasol::udf::protocol::VerifyFrameBuffer( - builder.GetBufferPointer(), builder.GetSize())); + assert(exasol::udf::protocol::VerifyFrameBuffer(builder.GetBufferPointer(), builder.GetSize())); const auto* decoded = exasol::udf::protocol::GetFrame(builder.GetBufferPointer()); assert(decoded->stream_id() == 7); assert(decoded->message()->open_call()->call_name()->str() == "example"); diff --git a/udf-runner-cpp/v2/waitable_queue_benchmark.cc b/udf-runner-cpp/v2/waitable_queue_benchmark.cc index 5d238cd..af44d7b 100644 --- a/udf-runner-cpp/v2/waitable_queue_benchmark.cc +++ b/udf-runner-cpp/v2/waitable_queue_benchmark.cc @@ -38,7 +38,8 @@ struct TimedItem { // for the waitable round-trip benchmark. void BM_RawSpscRoundTrip(benchmark::State& state) { exasol::udf::v2::SpscQueue queue(1024); - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); int value = 0; benchmark::DoNotOptimize(queue.enqueue(1)); benchmark::DoNotOptimize(queue.try_dequeue(value)); @@ -50,9 +51,9 @@ void BM_RawSpscRoundTrip(benchmark::State& state) { // Includes enqueue, eventfd notification draining, and dequeue. The eventfd // write is part of the measured round trip. void BM_WaitableSpscRoundTrip(benchmark::State& state) { - exasol::udf::v2::WaitableSpscQueue queue( - exasol::udf::v2::SpscQueue(1024)); - for (auto _ : state) { + exasol::udf::v2::WaitableSpscQueue queue(exasol::udf::v2::SpscQueue(1024)); + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); int value = 0; benchmark::DoNotOptimize(queue.enqueue(1)); benchmark::DoNotOptimize(queue.drain_notifications()); @@ -66,7 +67,8 @@ void BM_WaitableSpscRoundTrip(benchmark::State& state) { // benchmark measures the uncontended fast path rather than intentional waits. void BM_BlockingSpscRoundTrip(benchmark::State& state) { exasol::udf::v2::SpscCircularBuffer queue(1024); - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); int value = 0; queue.wait_enqueue(1); benchmark::DoNotOptimize(queue.try_dequeue(value)); @@ -80,7 +82,8 @@ void BM_BlockingSpscRoundTrip(benchmark::State& state) { // eventfd write; notification draining is cleanup and is not timed. void BM_RawSpscEnqueueLatency(benchmark::State& state) { exasol::udf::v2::SpscQueue queue(1024); - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); benchmark::DoNotOptimize(queue.enqueue(1)); state.PauseTiming(); int value = 0; @@ -92,9 +95,9 @@ void BM_RawSpscEnqueueLatency(benchmark::State& state) { } void BM_WaitableSpscEnqueueLatency(benchmark::State& state) { - exasol::udf::v2::WaitableSpscQueue queue( - exasol::udf::v2::SpscQueue(1024)); - for (auto _ : state) { + exasol::udf::v2::WaitableSpscQueue queue(exasol::udf::v2::SpscQueue(1024)); + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); benchmark::DoNotOptimize(queue.enqueue(1)); state.PauseTiming(); int value = 0; @@ -108,7 +111,8 @@ void BM_WaitableSpscEnqueueLatency(benchmark::State& state) { void BM_BlockingSpscEnqueueLatency(benchmark::State& state) { exasol::udf::v2::SpscCircularBuffer queue(1024); - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); queue.wait_enqueue(1); state.PauseTiming(); int value = 0; @@ -127,7 +131,8 @@ void BM_RawSpscBatch(benchmark::State& state) { const std::vector batch(batch_size, 1); exasol::udf::v2::SpscQueue queue(batch_size); - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); for (int value : batch) { benchmark::DoNotOptimize(queue.enqueue(value)); } @@ -137,16 +142,16 @@ void BM_RawSpscBatch(benchmark::State& state) { } benchmark::DoNotOptimize(value); } - state.SetItemsProcessed(state.iterations() * batch_size); + state.SetItemsProcessed(state.iterations() * static_cast(batch_size)); } void BM_WaitableSpscBatch(benchmark::State& state) { const auto batch_size = static_cast(state.range(0)); const std::vector batch(batch_size, 1); - exasol::udf::v2::WaitableSpscQueue queue{ - exasol::udf::v2::SpscQueue(batch_size)}; + exasol::udf::v2::WaitableSpscQueue queue{exasol::udf::v2::SpscQueue(batch_size)}; - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); benchmark::DoNotOptimize(queue.enqueue_batch(batch.begin(), batch.end())); benchmark::DoNotOptimize(queue.drain_notifications()); int value = 0; @@ -155,7 +160,7 @@ void BM_WaitableSpscBatch(benchmark::State& state) { } benchmark::DoNotOptimize(value); } - state.SetItemsProcessed(state.iterations() * batch_size); + state.SetItemsProcessed(state.iterations() * static_cast(batch_size)); } // Measures producer timestamp through enqueue, eventfd readiness, epoll_wait, @@ -163,16 +168,14 @@ void BM_WaitableSpscBatch(benchmark::State& state) { // wakeup latency rather than latency caused by queue backlog. The producer // handshake is outside the manually recorded interval. void BM_WaitableSpscEpollLatency(benchmark::State& state) { - exasol::udf::v2::WaitableSpscQueue queue{ - exasol::udf::v2::SpscQueue(8)}; + exasol::udf::v2::WaitableSpscQueue queue{exasol::udf::v2::SpscQueue(8)}; const int epoll_fd = ::epoll_create1(EPOLL_CLOEXEC); benchmark_check(epoll_fd != -1, "epoll_create1 failed"); epoll_event queue_event{}; - queue_event.events = EPOLLIN; + queue_event.events = EPOLLIN; queue_event.data.fd = queue.native_handle(); - benchmark_check(::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, queue.native_handle(), - &queue_event) == 0, + benchmark_check(::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, queue.native_handle(), &queue_event) == 0, "epoll_ctl failed"); std::atomic requested{0}; @@ -190,7 +193,7 @@ void BM_WaitableSpscEpollLatency(benchmark::State& state) { } TimedItem item{sequence++, std::chrono::steady_clock::now()}; - benchmark_check(queue.enqueue(std::move(item)), "queue enqueue failed"); + benchmark_check(queue.enqueue(item), "queue enqueue failed"); while (completed.load(std::memory_order_acquire) < sequence && !stop.load(std::memory_order_acquire)) { @@ -200,7 +203,8 @@ void BM_WaitableSpscEpollLatency(benchmark::State& state) { }); std::uint64_t expected_sequence = 0; - for (auto _ : state) { + for (const auto iteration : state) { + benchmark::DoNotOptimize(&iteration); requested.fetch_add(1, std::memory_order_release); epoll_event event{}; @@ -209,19 +213,16 @@ void BM_WaitableSpscEpollLatency(benchmark::State& state) { event_count = ::epoll_wait(epoll_fd, &event, 1, -1); } while (event_count == -1 && errno == EINTR); benchmark_check(event_count == 1, "epoll_wait failed"); - benchmark_check(event.data.fd == queue.native_handle(), - "unexpected epoll event"); + benchmark_check(event.data.fd == queue.native_handle(), "unexpected epoll event"); benchmark::DoNotOptimize(queue.drain_notifications()); TimedItem item{}; benchmark_check(queue.try_dequeue(item), "queue dequeue failed"); - benchmark_check(item.sequence == expected_sequence, - "unexpected item sequence"); + benchmark_check(item.sequence == expected_sequence, "unexpected item sequence"); ++expected_sequence; const auto elapsed = std::chrono::steady_clock::now() - item.sent; benchmark::DoNotOptimize(item); - state.SetIterationTime( - std::chrono::duration(elapsed).count()); + state.SetIterationTime(std::chrono::duration(elapsed).count()); completed.store(expected_sequence, std::memory_order_release); } @@ -231,7 +232,7 @@ void BM_WaitableSpscEpollLatency(benchmark::State& state) { ::close(epoll_fd); } -} // namespace +} // namespace BENCHMARK(BM_RawSpscRoundTrip); BENCHMARK(BM_WaitableSpscRoundTrip); @@ -239,14 +240,6 @@ BENCHMARK(BM_BlockingSpscRoundTrip); BENCHMARK(BM_RawSpscEnqueueLatency); BENCHMARK(BM_WaitableSpscEnqueueLatency); BENCHMARK(BM_BlockingSpscEnqueueLatency); -BENCHMARK(BM_RawSpscBatch) - ->Args({1}) - ->Args({8}) - ->Args({64}) - ->Args({256}); -BENCHMARK(BM_WaitableSpscBatch) - ->Args({1}) - ->Args({8}) - ->Args({64}) - ->Args({256}); +BENCHMARK(BM_RawSpscBatch)->Args({1})->Args({8})->Args({64})->Args({256}); +BENCHMARK(BM_WaitableSpscBatch)->Args({1})->Args({8})->Args({64})->Args({256}); BENCHMARK(BM_WaitableSpscEpollLatency)->UseManualTime(); diff --git a/udf-runner-cpp/v2/waitable_queue_test.cc b/udf-runner-cpp/v2/waitable_queue_test.cc index f3b35f6..ebfe0ca 100644 --- a/udf-runner-cpp/v2/waitable_queue_test.cc +++ b/udf-runner-cpp/v2/waitable_queue_test.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -23,10 +24,9 @@ void test_check(bool condition, const char* message) { void add_to_epoll(int epoll_fd, int fd, std::uint32_t events) { epoll_event event{}; - event.events = events; + event.events = events; event.data.fd = fd; - test_check(::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == 0, - "epoll_ctl failed"); + test_check(::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == 0, "epoll_ctl failed"); } void close_pair(const std::array& sockets) { @@ -34,63 +34,62 @@ void close_pair(const std::array& sockets) { ::close(sockets[1]); } -} // namespace +} // namespace int main() { - exasol::udf::v2::WaitableSpscQueue queue; - const int epoll_fd = ::epoll_create1(EPOLL_CLOEXEC); - test_check(epoll_fd != -1, "epoll_create1 failed"); - - std::array sockets{}; - test_check(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, - sockets.data()) == 0, - "socketpair failed"); - add_to_epoll(epoll_fd, queue.native_handle(), EPOLLIN); - add_to_epoll(epoll_fd, sockets[1], EPOLLIN); - - test_check(queue.enqueue(42), "queue enqueue failed"); - const char byte = 'x'; - test_check(::write(sockets[0], &byte, sizeof(byte)) == sizeof(byte), - "socket write failed"); - - std::array events{}; - const int event_count = ::epoll_wait(epoll_fd, events.data(), - events.size(), 1000); - test_check(event_count == 2, "epoll_wait did not report both descriptors"); - - bool queue_ready = false; - bool socket_ready = false; - for (int i = 0; i < event_count; ++i) { - queue_ready |= events[i].data.fd == queue.native_handle(); - socket_ready |= events[i].data.fd == sockets[1]; + try { + exasol::udf::v2::WaitableSpscQueue queue; + const int epoll_fd = ::epoll_create1(EPOLL_CLOEXEC); + test_check(epoll_fd != -1, "epoll_create1 failed"); + + std::array sockets{}; + test_check(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets.data()) == 0, + "socketpair failed"); + add_to_epoll(epoll_fd, queue.native_handle(), EPOLLIN); + add_to_epoll(epoll_fd, sockets[1], EPOLLIN); + + test_check(queue.enqueue(42), "queue enqueue failed"); + const char byte = 'x'; + test_check(::write(sockets[0], &byte, sizeof(byte)) == sizeof(byte), "socket write failed"); + + std::array events{}; + const int event_count = ::epoll_wait(epoll_fd, events.data(), events.size(), 1000); + test_check(event_count == 2, "epoll_wait did not report both descriptors"); + + bool queue_ready = false; + bool socket_ready = false; + for (const auto& event : std::span(events).first(static_cast(event_count))) { + queue_ready |= event.data.fd == queue.native_handle(); + socket_ready |= event.data.fd == sockets[1]; + } + test_check(queue_ready, "queue descriptor was not ready"); + test_check(socket_ready, "socket descriptor was not ready"); + + test_check(queue.drain_notifications() == 1, "unexpected queue notification count"); + int value = 0; + test_check(queue.try_dequeue(value), "queue dequeue failed"); + test_check(value == 42, "unexpected dequeued value"); + + const std::vector batch{1, 2, 3}; + test_check(queue.enqueue_batch(batch.begin(), batch.end()) == batch.size(), + "batch enqueue failed"); + test_check(queue.drain_notifications() == 1, "unexpected batch notification count"); + for (int expected : batch) { + test_check(queue.try_dequeue(value), "batch dequeue failed"); + test_check(value == expected, "unexpected batch value"); + } + test_check(!queue.try_dequeue(value), "queue should be empty"); + + exasol::udf::v2::WaitableMpmcQueue mpmc; + test_check(mpmc.enqueue(7), "MPMC queue enqueue failed"); + test_check(mpmc.drain_notifications() == 1, "unexpected MPMC notification count"); + test_check(mpmc.try_dequeue(value), "MPMC queue dequeue failed"); + test_check(value == 7, "unexpected MPMC value"); + + close_pair(sockets); + ::close(epoll_fd); + } catch (const std::exception& error) { + std::fprintf(stderr, "waitable queue test failure: %s\n", error.what()); + return 1; } - test_check(queue_ready, "queue descriptor was not ready"); - test_check(socket_ready, "socket descriptor was not ready"); - - test_check(queue.drain_notifications() == 1, - "unexpected queue notification count"); - int value = 0; - test_check(queue.try_dequeue(value), "queue dequeue failed"); - test_check(value == 42, "unexpected dequeued value"); - - const std::vector batch{1, 2, 3}; - test_check(queue.enqueue_batch(batch.begin(), batch.end()) == batch.size(), - "batch enqueue failed"); - test_check(queue.drain_notifications() == 1, - "unexpected batch notification count"); - for (int expected : batch) { - test_check(queue.try_dequeue(value), "batch dequeue failed"); - test_check(value == expected, "unexpected batch value"); - } - test_check(!queue.try_dequeue(value), "queue should be empty"); - - exasol::udf::v2::WaitableMpmcQueue mpmc; - test_check(mpmc.enqueue(7), "MPMC queue enqueue failed"); - test_check(mpmc.drain_notifications() == 1, - "unexpected MPMC notification count"); - test_check(mpmc.try_dequeue(value), "MPMC queue dequeue failed"); - test_check(value == 7, "unexpected MPMC value"); - - close_pair(sockets); - ::close(epoll_fd); }