From 485a80b56437ae9c9907c6ea56880e85b586d1cd Mon Sep 17 00:00:00 2001 From: qqiangwu Date: Sat, 3 Jun 2023 00:24:15 +0800 Subject: [PATCH] feat(build): add asan/tsan/asan/leak support --- README.md | 4 +++ cppship.toml | 10 +++++- include/cppship/core/manifest.h | 3 ++ include/cppship/core/profile.h | 6 ++++ lib/cmake/generator.cpp | 56 ++++++++++++++++++++++++++--- lib/core/manifest.cpp | 62 +++++++++++++++++++++++++++++++-- 6 files changed, 134 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f66cd01..2818f39 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,10 @@ scnlib = "1.1.2" [profile] definitions = ["BOOST_PROCESS_USE_STD_FS"] +ubsan = true +asan = true +tsan = false +leak = false [target.'cfg(not(compiler = "msvc"))'.profile] cxxflags = ["-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", "-Wno-missing-field-initializers"] diff --git a/cppship.toml b/cppship.toml index 6edb380..f31f371 100644 --- a/cppship.toml +++ b/cppship.toml @@ -1,6 +1,6 @@ [package] name = "cppship" -version = "0.6.0" +version = "0.7.0" authors = [] std = 20 @@ -20,5 +20,13 @@ definitions = ["BOOST_PROCESS_USE_STD_FS"] [target.'cfg(not(compiler = "msvc"))'.profile] cxxflags = ["-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", "-Wno-missing-field-initializers"] +[target.'cfg(not(compiler = "msvc"))'.profile.debug] +ubsan = true + +# macos with gcc11 has bugs in asan: +# `member call on address 0x60b0000001a0 which does not point to an object of type '_Sp_counted_base'` +[target.'cfg(any(os = "linux", compiler = "clang", compiler = "apple_clang"))'.profile.debug] +asan = true + [target.'cfg(compiler = "msvc")'.profile] cxxflags = ["/Zc:__cplusplus", "/Zc:preprocessor", "/MP"] \ No newline at end of file diff --git a/include/cppship/core/manifest.h b/include/cppship/core/manifest.h index 1a3e458..91e61ae 100644 --- a/include/cppship/core/manifest.h +++ b/include/cppship/core/manifest.h @@ -46,6 +46,9 @@ class Manifest { const ProfileOptions& default_profile() const { return mProfileDefault; } const ProfileOptions& profile(Profile prof) const; +private: + void set_defaults_(); + private: std::string mName; std::string mVersion; diff --git a/include/cppship/core/profile.h b/include/cppship/core/profile.h index a4c9191..3e7df70 100644 --- a/include/cppship/core/profile.h +++ b/include/cppship/core/profile.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -54,7 +55,12 @@ inline Profile parse_profile(std::string profile) struct ProfileConfig { std::vector cxxflags; + std::vector linkflags; std::vector definitions; + std::optional ubsan; + std::optional tsan; + std::optional asan; + std::optional leak; }; struct ConditionConfig { diff --git a/lib/cmake/generator.cpp b/lib/cmake/generator.cpp index 4334f4e..c4776dd 100644 --- a/lib/cmake/generator.cpp +++ b/lib/cmake/generator.cpp @@ -343,11 +343,11 @@ include(CPack) namespace { -class ProfileOptionAppender { +class ProfileOptionGen { std::ostream& mOut; public: - ProfileOptionAppender(std::ostream& out) + ProfileOptionGen(std::ostream& out) : mOut(out) { } @@ -360,6 +360,30 @@ class ProfileOptionAppender { for (const auto& def : config.definitions) { mOut << fmt::format("{}add_compile_definitions($<$:{}>)\n", indent, profile, def); } + + if (config.ubsan.value_or(false)) { + mOut << fmt::format("{}add_compile_options($<$:{}>)\n", indent, profile, "-fsanitize=undefined"); + mOut << fmt::format("{}add_link_options($<$:{}>)\n", indent, profile, "-fsanitize=undefined"); + mOut << fmt::format("{}message(STATUS \"Enable ubsan\")\n", indent); + } + + if (config.tsan.value_or(false)) { + mOut << fmt::format("{}add_compile_options($<$:{}>)\n", indent, profile, "-fsanitize=thread"); + mOut << fmt::format("{}add_link_options($<$:{}>)\n", indent, profile, "-fsanitize=thread"); + mOut << fmt::format("{}message(STATUS \"Enable tsan\")\n", indent); + } + + if (config.asan.value_or(false)) { + mOut << fmt::format("{}add_compile_options($<$:{}>)\n", indent, profile, "-fsanitize=address"); + mOut << fmt::format("{}add_link_options($<$:{}>)\n", indent, profile, "-fsanitize=address"); + mOut << fmt::format("{}message(STATUS \"Enable asan\")\n", indent); + } + + if (config.leak.value_or(false)) { + mOut << fmt::format("{}add_compile_options($<$:{}>)\n", indent, profile, "-fsanitize=leak"); + mOut << fmt::format("{}add_link_options($<$:{}>)\n", indent, profile, "-fsanitize=leak"); + mOut << fmt::format("{}message(STATUS \"Enable leak\")\n", indent); + } } void output(const ProfileConfig& config, std::string_view indent = "") @@ -370,6 +394,30 @@ class ProfileOptionAppender { for (const auto& def : config.definitions) { mOut << fmt::format("{}add_compile_definitions({})\n", indent, def); } + + if (config.ubsan.value_or(false)) { + mOut << fmt::format("{}add_compile_options({})\n", indent, "-fsanitize=undefined"); + mOut << fmt::format("{}add_link_options({})\n", indent, "-fsanitize=undefined"); + mOut << fmt::format("{}message(STATUS \"Enable ubsan\")\n", indent); + } + + if (config.tsan.value_or(false)) { + mOut << fmt::format("{}add_compile_options({})\n", indent, "-fsanitize=thread"); + mOut << fmt::format("{}add_link_options({})\n", indent, "-fsanitize=thread"); + mOut << fmt::format("{}message(STATUS \"Enable tsan\")\n", indent); + } + + if (config.asan.value_or(false)) { + mOut << fmt::format("{}add_compile_options({})\n", indent, "-fsanitize=address"); + mOut << fmt::format("{}add_link_options({})\n", indent, "-fsanitize=address"); + mOut << fmt::format("{}message(STATUS \"Enable asan\")\n", indent); + } + + if (config.leak.value_or(false)) { + mOut << fmt::format("{}add_compile_options({})\n", indent, "-fsanitize=leak"); + mOut << fmt::format("{}add_link_options({})\n", indent, "-fsanitize=leak"); + mOut << fmt::format("{}message(STATUS \"Enable leak\")\n", indent); + } } }; @@ -379,7 +427,7 @@ void CmakeGenerator::fill_default_profile_() { const auto& default_profile = mManifest.default_profile(); - ProfileOptionAppender appender(mOut); + ProfileOptionGen appender(mOut); appender.output(default_profile.config); for (const auto& [condition, config] : default_profile.conditional_configs) { @@ -394,7 +442,7 @@ void CmakeGenerator::fill_profile_(Profile profile) const auto& options = mManifest.profile(profile); const auto& profile_str = to_string(profile); - ProfileOptionAppender appender(mOut); + ProfileOptionGen appender(mOut); appender.output(profile_str, options.config); for (const auto& [condition, config] : options.conditional_configs) { diff --git a/lib/core/manifest.cpp b/lib/core/manifest.cpp index 3c73215..3eb5f7b 100644 --- a/lib/core/manifest.cpp +++ b/lib/core/manifest.cpp @@ -3,15 +3,18 @@ #include "cppship/util/fs.h" #include "cppship/util/io.h" +#include #include #include #include #include +#include #include #include using namespace cppship; +using namespace ranges; namespace { @@ -24,6 +27,20 @@ template T get(const toml::value& value, const std::stri return toml::find(value, key); } +std::optional get_bool(const toml::value& value, const std::string& key) +{ + if (value.is_uninitialized() || !value.contains(key)) { + return std::nullopt; + } + + const auto& content = value.at(key); + if (!content.is_boolean()) { + throw Error { fmt::format("invalid manifest: {} should be a bool", key) }; + } + + return content.as_boolean(); +} + std::vector get_list(const toml::value& value, const std::string& key) { if (value.is_uninitialized() || !value.contains(key)) { @@ -129,7 +146,7 @@ void check_dependency_dups(const std::vector& deps, const st { std::set package_seen; - for (const auto& dep : ranges::views::concat(deps, dev_deps)) { + for (const auto& dep : views::concat(deps, dev_deps)) { if (package_seen.contains(dep.package)) { throw Error { fmt::format("package {} already declared", dep.package) }; } @@ -142,10 +159,27 @@ ProfileConfig parse_profile_options(const toml::value& manifest, const std::stri { const auto profile = toml::find_or(manifest, key, {}); - return { + ProfileConfig config = { .cxxflags = get_list(profile, "cxxflags"), + .linkflags = get_list(profile, "linkflags"), .definitions = get_list(profile, "definitions"), + .ubsan = get_bool(profile, "ubsan"), + .tsan = get_bool(profile, "tsan"), + .asan = get_bool(profile, "asan"), + .leak = get_bool(profile, "leak"), }; + + if (config.tsan && *config.tsan) { + if (config.asan && *config.asan) { + throw Error { "tsan cannot be used with asan" }; + } + + if (config.leak && *config.leak) { + throw Error { "tsan cannot be used with leak" }; + } + } + + return config; } } @@ -204,6 +238,9 @@ Manifest::Manifest(const fs::path& file) }); } } + + // set defaults + set_defaults_(); } catch (const std::out_of_range& e) { throw Error { e.what() }; } catch (const toml::exception& e) { @@ -211,6 +248,27 @@ Manifest::Manifest(const fs::path& file) } } +void Manifest::set_defaults_() +{ + // TODO + /* + const bool ubsan_present + = any_of(std::array { &mProfileDebug, &mProfileDebug, &mProfileRelease }, [](const ProfileOptions* profile) { + return profile->config.ubsan.has_value() + || any_of(profile->conditional_configs, [](const auto& cc) { return cc.config.ubsan.has_value(); }); + }); + if (ubsan_present) { + return; + } + + // in debug profile, enable ubsan if not msvc + mProfileDebug.conditional_configs.push_back({ + .condition = core::CfgNot { { core::cfg::Compiler::msvc } }, + .config = { .ubsan = true }, + }); + */ +} + const ProfileOptions& Manifest::profile(Profile prof) const { switch (prof) {