diff options
118 files changed, 12065 insertions, 6998 deletions
diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index a6f4e1129..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "extends": [ - "eslint:all", - "plugin:@stylistic/all-extends" - ], - "globals": { - "define": false - }, - "parserOptions": { - "ecmaVersion": 2016 - }, - "plugins": [ - "@stylistic" - ], - "rules": { - "camelcase": "off", - "capitalized-comments": "off", - "curly": ["error", "multi-line"], - "func-names": "off", - "func-style": ["error", "declaration"], - "id-length": ["error", { "min": 1 }], - "line-comment-position": "off", - "logical-assignment-operators": ["error", "never"], - "max-params": ["warn", 6], - "max-statements": ["warn", 55], - "multiline-comment-style": "off", - "no-continue": "off", - "no-inline-comments": "off", - "no-magic-numbers": "off", - "no-negated-condition": "off", - "no-plusplus": "off", - "no-ternary": "off", - "object-shorthand": "off", - "one-var": ["error", { "initialized": "never" }], - "prefer-named-capture-group": "off", - "prefer-object-has-own": "off", - "prefer-spread": "off", - "prefer-template": "off", - "require-unicode-regexp": "off", - - "@stylistic/array-bracket-newline": ["error", "consistent"], - "@stylistic/array-element-newline": "off", - "@stylistic/brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "@stylistic/comma-dangle": ["error", "only-multiline"], - "@stylistic/dot-location": ["error", "property"], - "@stylistic/function-call-argument-newline": "off", - "@stylistic/max-len": ["error", { "code": 128 }], - "@stylistic/max-statements-per-line": ["error", { "max": 2 }], - "@stylistic/multiline-comment-style": "off", - "@stylistic/multiline-ternary": ["error", "always-multiline"], - "@stylistic/newline-per-chained-call": ["error", { "ignoreChainWithDepth": 5 }], - "@stylistic/no-extra-parens": ["error", "functions"], - "@stylistic/object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }], - "@stylistic/padded-blocks": ["error", "never"], - "@stylistic/quote-props" : ["error", "consistent-as-needed"], - "@stylistic/quotes": ["error", "double", { "avoidEscape": true }], - "@stylistic/semi": ["error", "always"], - "@stylistic/space-before-function-paren": ["error", { - "anonymous": "always", - "named": "never" - }], - - - // Temporarily disabled rules - "max-lines": "off", - "max-lines-per-function": "off", - "no-invalid-this": "off", - "sort-keys": "off", - - "@stylistic/function-paren-newline": "off", - "@stylistic/indent-binary-ops": "off" - } -} diff --git a/.github/workflows/ci_eslint.yml b/.github/workflows/ci_eslint.yml index e7220d468..a4fe0fc31 100644 --- a/.github/workflows/ci_eslint.yml +++ b/.github/workflows/ci_eslint.yml @@ -6,14 +6,16 @@ on: jobs: eslint: runs-on: ubuntu-latest - container: - image: node:18-alpine steps: - name: Check out source code uses: actions/checkout@v4 - - name: Install dependencies - run: npm install + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install packages + run: npm ci - name: Show installed packages run: npm ls diff --git a/.gitignore b/.gitignore index 613e9eaff..2ea0abc60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +node_modules/ # Code::TidyAll /.tidyall.d/ .idea diff --git a/CMakeLists.txt b/CMakeLists.txt index e76939da2..a9bb50642 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.12 FATAL_ERROR) SET(RSPAMD_VERSION_MAJOR 3) SET(RSPAMD_VERSION_MINOR 9) -SET(RSPAMD_VERSION_PATCH 1) +SET(RSPAMD_VERSION_PATCH 2) # Keep two digits all the time SET(RSPAMD_VERSION_MAJOR_NUM ${RSPAMD_VERSION_MAJOR}0) @@ -267,6 +267,8 @@ ADD_DEFINITIONS(-DFMT_HEADER_ONLY) # Workaround for https://github.com/onqtam/doctest/issues/356 ADD_DEFINITIONS(-DDOCTEST_CONFIG_USE_STD_HEADERS) ADD_DEFINITIONS(-DU_CHARSET_IS_UTF8) +# Disable zstd deprecation warnings, as they are not relevant for us +ADD_DEFINITIONS(-DZSTD_DISABLE_DEPRECATE_WARNINGS) # Check platform specific includes CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H) @@ -1,3 +1,13 @@ +3.9.1: 23 Jul 2024 + * [Conf] Spf: Add R_SPF_PLUSALL symbol with some score + * [Feature] Spf: Treat SPF +all in a special way + * [Minor] Ensure some safety when checking weights + * [Minor] Fix several issues with flag propagation + * [Minor] Gpt: Improve prompt and add some conversion heursitics + * [Minor] Gpt: Remove top_p reduce temperature to 0 + * [Minor] Gpt: Set response_format + * [Minor] Gpt: Use gpt-4o-mini by default + 3.9.0: 12 Jul 2024 * [CritFix] Protect regexp matcher from regexps with empty patterns * [Feature] Allow adding X-CMAE-Score header diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index 9092457de..2315d5e4d 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -84,6 +84,6 @@ IF(SUPPORT_WSUGGEST_ATTRIBUTE) ADD_COMPILE_OPTIONS("-Wno-suggest-attribute=format") ENDIF() -IF(SUPPORT_WDEPRECATED_DECLARATIONS) - ADD_COMPILE_OPTIONS("-Wno-deprecated-declarations") -ENDIF() +#IF(SUPPORT_WDEPRECATED_DECLARATIONS) +# ADD_COMPILE_OPTIONS("-Wno-deprecated-declarations") +#ENDIF() diff --git a/cmake/ProcessPackage.cmake b/cmake/ProcessPackage.cmake index a46fd85b4..316662568 100644 --- a/cmake/ProcessPackage.cmake +++ b/cmake/ProcessPackage.cmake @@ -32,7 +32,12 @@ MACRO(ProcessPackage PKG_NAME) ENDIF() IF(${PKG_NAME}_FOUND) - MESSAGE(STATUS "Found package ${PKG_NAME} in pkg-config modules ${PKG_MODULES}") + SET(_ver ${${PKG_NAME}_VERSION}) + IF(_ver) + MESSAGE(STATUS "Found package ${PKG_NAME} (${_ver}) in pkg-config modules ${PKG_MODULES}") + ELSE() + MESSAGE(STATUS "Found package ${PKG_NAME} in pkg-config modules ${PKG_MODULES}") + ENDIF() SET(WITH_${PKG_NAME} 1 CACHE INTERNAL "") IF(ENABLE_STATIC MATCHES "ON") SET(_XPREFIX "${PKG_NAME}_STATIC") diff --git a/conf/groups.conf b/conf/groups.conf index 2aeb4ed5d..4f40d865c 100644 --- a/conf/groups.conf +++ b/conf/groups.conf @@ -39,6 +39,13 @@ group "rbl" { .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/rbl_group.conf" } +# Limits the maximum score when both bl.score.senderscore.com and score.senderscore.com RBLs are enabled. +group "senderscore" { + max_score = 4.0; + .include(try=true; priority=1; duplicate=merge) "$LOCAL_CONFDIR/local.d/senderscore_group.conf" + .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/senderscore_group.conf" +} + group "statistics" { .include "$CONFDIR/scores.d/statistics_group.conf" .include(try=true; priority=1; duplicate=merge) "$LOCAL_CONFDIR/local.d/statistics_group.conf" diff --git a/conf/modules.d/gpt.conf b/conf/modules.d/gpt.conf index 7a2e11d40..c76a08c92 100644 --- a/conf/modules.d/gpt.conf +++ b/conf/modules.d/gpt.conf @@ -18,13 +18,11 @@ gpt { # Your key to access the API (add this to enable this plugin) #api_key = "xxx"; # Model name - model = "gpt-3.5-turbo"; + model = "gpt-4o-mini"; # Maximum tokens to generate max_tokens = 1000; # Temperature for sampling - temperature = 0.7; - # Top p for sampling - top_p = 0.9; + temperature = 0.0; # Timeout for requests timeout = 10s; # Prompt for the model (use default if not set) diff --git a/conf/modules.d/once_received.conf b/conf/modules.d/once_received.conf index ab0749295..6fcc35bb6 100644 --- a/conf/modules.d/once_received.conf +++ b/conf/modules.d/once_received.conf @@ -14,8 +14,7 @@ once_received { good_host = "mail"; - bad_host = "static"; - bad_host = "dynamic"; + bad_host = ["static", "dynamic"]; symbol_strict = "ONCE_RECEIVED_STRICT"; symbol = "ONCE_RECEIVED"; symbol_mx = "DIRECT_TO_MX"; diff --git a/conf/modules.d/rbl.conf b/conf/modules.d/rbl.conf index c3594dbc9..2a718e5a4 100644 --- a/conf/modules.d/rbl.conf +++ b/conf/modules.d/rbl.conf @@ -79,9 +79,55 @@ rbl { } senderscore { - symbol = "RBL_SENDERSCORE"; + # Disabled by default to prioritize the use of score.senderscore.com. + # Note: The free query limit applies to both bl.score.senderscore.com and score.senderscore.com RBLs + # (see https://knowledge.validity.com/hc/en-us/articles/20961730681243). + # Enabling this RBL is recommended for low-traffic systems or MyValidity account users who benefit from using both RBLs. + enabled = false; + symbol = "RBL_SENDERSCORE_UNKNOWN"; checks = ['from']; rbl = "bl.score.senderscore.com"; + returncodes { + RBL_SENDERSCORE_BOT = "127.0.0.1"; + RBL_SENDERSCORE_NA = "127.0.0.2"; + RBL_SENDERSCORE_NA_BOT = "127.0.0.3"; + RBL_SENDERSCORE_PRST = "127.0.0.4"; + RBL_SENDERSCORE_PRST_BOT = "127.0.0.5"; + RBL_SENDERSCORE_PRST_NA = "127.0.0.6"; + RBL_SENDERSCORE_PRST_NA_BOT = "127.0.0.7"; + RBL_SENDERSCORE_SUS_ATT = "127.0.0.8"; + RBL_SENDERSCORE_SUS_ATT_NA = "127.0.0.10"; + RBL_SENDERSCORE_SUS_ATT_NA_BOT = "127.0.0.11"; + RBL_SENDERSCORE_SUS_ATT_PRST_NA = "127.0.0.14"; + RBL_SENDERSCORE_SUS_ATT_PRST_NA_BOT = "127.0.0.15"; + RBL_SENDERSCORE_SCORE = "127.0.0.16"; + RBL_SENDERSCORE_SCORE_NA = "127.0.0.18"; + RBL_SENDERSCORE_SCORE_PRST = "127.0.0.20"; + RBL_SENDERSCORE_SCORE_PRST_NA = "127.0.0.22"; + RBL_SENDERSCORE_SCORE_SUS_ATT_NA = "127.0.0.26"; + RBL_SENDERSCORE_BLOCKED = "127.255.255.255"; + } + } + + senderscore_reputation { + symbol = "RBL_SENDERSCORE_REPUT_UNKNOWN"; + checks = ['from']; + rbl = "score.senderscore.com"; + returncodes_matcher = "luapattern"; + + returncodes { + RBL_SENDERSCORE_REPUT_0 = "127%.0%.4%.%d"; + RBL_SENDERSCORE_REPUT_1 = "127%.0%.4%.1%d"; + RBL_SENDERSCORE_REPUT_2 = "127%.0%.4%.2%d"; + RBL_SENDERSCORE_REPUT_3 = "127%.0%.4%.3%d"; + RBL_SENDERSCORE_REPUT_4 = "127%.0%.4%.4%d"; + RBL_SENDERSCORE_REPUT_5 = "127%.0%.4%.5%d"; + RBL_SENDERSCORE_REPUT_6 = "127%.0%.4%.6%d"; + RBL_SENDERSCORE_REPUT_7 = "127%.0%.4%.7%d"; + RBL_SENDERSCORE_REPUT_8 = "127%.0%.4%.8%d"; # Neutral reputation (80-89). + RBL_SENDERSCORE_REPUT_9 = ["127%.0%.4%.9%d", "127%.0%.4%.100"]; # Good reputation (90-100). + RBL_SENDERSCORE_REPUT_BLOCKED = "127%.255%.255%.255"; + } } sem { diff --git a/conf/scores.d/headers_group.conf b/conf/scores.d/headers_group.conf index 1c70ca588..972c6872a 100644 --- a/conf/scores.d/headers_group.conf +++ b/conf/scores.d/headers_group.conf @@ -50,14 +50,6 @@ symbols = { weight = 0.1; description = "One received header in a message"; } - "RDNS_NONE" { - weight = 2.0; - description = "Cannot resolve reverse DNS for sender's IP"; - } - "RDNS_DNSFAIL" { - weight = 0.0; - description = "PTR verification DNS error"; - } "ONCE_RECEIVED_STRICT" { weight = 4.0; description = "One received header with 'bad' patterns inside"; diff --git a/conf/scores.d/hfilter_group.conf b/conf/scores.d/hfilter_group.conf index 09fcfcd8d..21cd11a60 100644 --- a/conf/scores.d/hfilter_group.conf +++ b/conf/scores.d/hfilter_group.conf @@ -130,4 +130,12 @@ symbols = { weight = 2.5; description = "One line URL and text in body"; } + "RDNS_NONE" { + weight = 2.0; + description = "Cannot resolve reverse DNS for sender's IP"; + } + "RDNS_DNSFAIL" { + weight = 0.0; + description = "PTR verification DNS error"; + } } diff --git a/conf/scores.d/policies_group.conf b/conf/scores.d/policies_group.conf index 4a8bdb6b7..712c61523 100644 --- a/conf/scores.d/policies_group.conf +++ b/conf/scores.d/policies_group.conf @@ -55,6 +55,11 @@ symbols = { description = "SPF record is malformed or persistent DNS error"; groups = ["spf"]; } + "R_SPF_PLUSALL" { + weight = 4.0; + description = "SPF record allows to send from any IP"; + groups = ["spf"]; + } # DKIM "R_DKIM_REJECT" { diff --git a/conf/scores.d/rbl_group.conf b/conf/scores.d/rbl_group.conf index ef29ed2fa..6a59b865f 100644 --- a/conf/scores.d/rbl_group.conf +++ b/conf/scores.d/rbl_group.conf @@ -162,9 +162,160 @@ symbols = { groups = ["spamhaus", "blocked"]; } - "RBL_SENDERSCORE" { + "RBL_SENDERSCORE_UNKNOWN" { + weight = 0.0; + description = "Unrecognised result from SenderScore RPBL"; + } + "RBL_SENDERSCORE_BOT" { + weight = 2.0; + description = "From address is listed in SenderScore RPBL - botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_NA" { + weight = 0.0; + description = "From address is listed in SenderScore RPBL - noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_NA_BOT" { + weight = 1.0; + description = "From address is listed in SenderScore RPBL - noauth+botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_PRST" { weight = 2.0; - description = "From address is listed in senderscore.com BL"; + description = "From address is listed in SenderScore RPBL - pristine"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_PRST_BOT" { + weight = 3.0; + description = "From address is listed in SenderScore RPBL - pristine+botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_PRST_NA" { + weight = 2.0; + description = "From address is listed in SenderScore RPBL - pristine+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_PRST_NA_BOT" { + weight = 3.0; + description = "From address is listed in SenderScore RPBL - pristine+noauth+botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SUS_ATT" { + weight = 1.0; + description = "From address is listed in SenderScore RPBL - suspect_attachments"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SUS_ATT_NA" { + weight = 1.0; + description = "From address is listed in SenderScore RPBL - suspect_attachments+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SUS_ATT_NA_BOT" { + weight = 1.5; + description = "From address is listed in SenderScore RPBL - suspect_attachments+noauth+botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SUS_ATT_PRST_NA" { + weight = 3.0; + description = "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SUS_ATT_PRST_NA_BOT" { + weight = 3.5; + description = "From address is listed in SenderScore RPBL - suspect_attachments+pristine+noauth+botnet"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SCORE" { + weight = 2.0; + description = "From address is listed in SenderScore RPBL - sender_score"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SCORE_NA" { + weight = 2.0; + description = "From address is listed in SenderScore RPBL - sender_score+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SCORE_PRST" { + weight = 4.0; + description = "From address is listed in SenderScore RPBL - sender_score+pristine"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SCORE_PRST_NA" { + weight = 4.0; + description = "From address is listed in SenderScore RPBL - sender_score+pristine+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_SCORE_SUS_ATT_NA" { + weight = 3.0; + description = "From address is listed in SenderScore RPBL - sender_score+suspect_attachments+noauth"; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_BLOCKED" { + weight = 0.0; + description = "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243"; + groups = ["senderscore", "blocked"]; + } + + "RBL_SENDERSCORE_REPUT_UNKNOWN" { + weight = 0.0; + description = "Unrecognized result from SenderScore Reputation list."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_0" { + weight = 4.0; + description = "SenderScore Reputation: Very Bad (0-9)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_1" { + weight = 3.5; + description = "SenderScore Reputation: Bad (10-19)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_2" { + weight = 3.0; + description = "SenderScore Reputation: Bad (20-29)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_3" { + weight = 2.5; + description = "SenderScore Reputation: Bad (30-39)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_4" { + weight = 2.0; + description = "SenderScore Reputation: Bad (40-49)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_5" { + weight = 1.5; + description = "SenderScore Reputation: Bad (50-59)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_6" { + weight = 1.0; + description = "SenderScore Reputation: Bad (60-69)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_7" { + weight = 0.5; + description = "SenderScore Reputation: Bad (70-79)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_8" { + weight = 0.0; + description = "SenderScore Reputation: Neutral (80-89)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_9" { + weight = -1.0; + description = "SenderScore Reputation: Good (90-100)."; + groups = ["senderscore"]; + } + "RBL_SENDERSCORE_REPUT_BLOCKED" { + weight = 0.0; + description = "Excessive number of queries to SenderScore RPBL, more info: https://knowledge.validity.com/hc/en-us/articles/20961730681243"; + groups = ["senderscore", "blocked"]; } "MAILSPIKE" { diff --git a/conf/scores.d/surbl_group.conf b/conf/scores.d/surbl_group.conf index 89579ca15..255c03214 100644 --- a/conf/scores.d/surbl_group.conf +++ b/conf/scores.d/surbl_group.conf @@ -214,13 +214,13 @@ symbols = { groups = ["uribl"]; } "URIBL_RED" { - weight = 3.5; + weight = 0.5; description = "A domain in the message is listed in URIBL.com red"; one_shot = true; groups = ["uribl"]; } "URIBL_GREY" { - weight = 1.5; + weight = 2.5; description = "A domain in the message is listed in URIBL.com grey"; one_shot = true; groups = ["uribl"]; diff --git a/conf/statistic.conf b/conf/statistic.conf index 0ba8302e8..36d418935 100644 --- a/conf/statistic.conf +++ b/conf/statistic.conf @@ -19,6 +19,7 @@ # Module documentation: https://rspamd.com/doc/configuration/statistic.html classifier "bayes" { + # name = "custom"; # 'name' parameter must be set if multiple classifiers are defined tokenizer { name = "osb"; } diff --git a/contrib/DEPENDENCY_INFO.md b/contrib/DEPENDENCY_INFO.md index 2b57f9796..ccdde60bb 100644 --- a/contrib/DEPENDENCY_INFO.md +++ b/contrib/DEPENDENCY_INFO.md @@ -33,7 +33,7 @@ | expected | v1.0 | Public Domain / CC0 | NO | | | frozen | 1.0.1 | Apache 2 | NO | | | fmt | 11.0.0 | MIT | NO | | -| doctest | 2.4.6 | MIT | NO | | +| doctest | 2.4.11 | MIT | NO | | | function2 | 4.1.0 | Boost | NO | | | ankerl/svector | 1.0.2 | MIT | NO | | | ankerl/unordered_dense | 4.4.0 | MIT | NO | | diff --git a/contrib/aho-corasick/acism.c b/contrib/aho-corasick/acism.c index b0cee0d66..7451a8791 100644 --- a/contrib/aho-corasick/acism.c +++ b/contrib/aho-corasick/acism.c @@ -38,8 +38,6 @@ #include "_acism.h" #include "unix-std.h" -#define BACK ((SYMBOL) 0) -#define ROOT ((STATE) 0) extern const unsigned char lc_map[256]; int acism_lookup(ac_trie_t const *psp, const char *text, size_t len, diff --git a/contrib/doctest/CMakeLists.txt b/contrib/doctest/CMakeLists.txt index 4a17708ff..c6b3f48ee 100644 --- a/contrib/doctest/CMakeLists.txt +++ b/contrib/doctest/CMakeLists.txt @@ -8,7 +8,8 @@ endif() ## DOCTEST ################################################################################ -project(doctest VERSION 2.4.6 LANGUAGES CXX) +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/version.txt ver) +project(doctest VERSION ${ver} LANGUAGES CXX) # Determine if doctest is built as a subproject (using add_subdirectory) or if it is the main project. set(MAIN_PROJECT OFF) @@ -18,7 +19,7 @@ endif() option(DOCTEST_WITH_TESTS "Build tests/examples" ${MAIN_PROJECT}) option(DOCTEST_WITH_MAIN_IN_STATIC_LIB "Build a static lib (cmake target) with a default main entry point" ON) -option(DOCTEST_NO_INSTALL "Skip the installation process" OFF) +option(DOCTEST_NO_INSTALL "Skip the installation process" ON) option(DOCTEST_USE_STD_HEADERS "Use std headers" OFF) add_library(${PROJECT_NAME} INTERFACE) @@ -52,6 +53,14 @@ else() target_include_directories(${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>) endif() +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if (NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -gdwarf-4 ") + endif() + endif() +endif() + # hack to support building on XCode 6 and 7 - propagate the definition to everything if(DEFINED DOCTEST_THREAD_LOCAL) target_compile_definitions(${PROJECT_NAME} INTERFACE @@ -61,3 +70,99 @@ endif() if(DOCTEST_USE_STD_HEADERS) target_compile_definitions(${PROJECT_NAME} INTERFACE DOCTEST_CONFIG_USE_STD_HEADERS) endif() + +################################################################################ +## TESTS/EXAMPLES/HELPERS +################################################################################ + +if(${DOCTEST_WITH_MAIN_IN_STATIC_LIB}) + add_library(${PROJECT_NAME}_with_main STATIC EXCLUDE_FROM_ALL ${doctest_parts_folder}/doctest.cpp) + add_library(${PROJECT_NAME}::${PROJECT_NAME}_with_main ALIAS ${PROJECT_NAME}_with_main) + target_compile_definitions(${PROJECT_NAME}_with_main PRIVATE + DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) + set_target_properties(${PROJECT_NAME}_with_main PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) + target_link_libraries(${PROJECT_NAME}_with_main PUBLIC ${PROJECT_NAME}) +endif() + +if(MAIN_PROJECT AND DOCTEST_WITH_TESTS) + include(scripts/cmake/common.cmake) + + add_subdirectory(examples/all_features) + add_subdirectory(examples/exe_with_static_libs) + add_subdirectory(examples/executable_dll_and_plugin) + add_subdirectory(examples/combining_the_same_tests_built_differently_in_multiple_shared_objects) + add_subdirectory(scripts/playground) + add_subdirectory(examples/mpi) +endif() + +################################################################################ +## PACKAGE SUPPORT +################################################################################ + +set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + include(GNUInstallDirs) + set(include_install_dir ${CMAKE_INSTALL_INCLUDEDIR}) + set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +else() + set(include_install_dir "include") + set(config_install_dir "lib/cmake/${PROJECT_NAME}") +endif() + + +set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") +set(targets_export_name "${PROJECT_NAME}Targets") +set(namespace "${PROJECT_NAME}::") + +include(CMakePackageConfigHelpers) + +# CMake automatically adds an architecture compatibility check to make sure +# 32 and 64 bit code is not accidentally mixed. For a header-only library this +# is not required. The check can be disabled by temporarily unsetting +# CMAKE_SIZEOF_VOID_P. In CMake 3.14 and later this can be achieved more cleanly +# with write_basic_package_version_file(ARCH_INDEPENDENT). +# TODO: Use this once a newer CMake can be required. +set(DOCTEST_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) +unset(CMAKE_SIZEOF_VOID_P) +write_basic_package_version_file( + "${version_config}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion +) +set(CMAKE_SIZEOF_VOID_P ${DOCTEST_SIZEOF_VOID_P}) + +configure_file("scripts/cmake/Config.cmake.in" "${project_config}" @ONLY) + +if(NOT ${DOCTEST_NO_INSTALL}) + install( + TARGETS ${PROJECT_NAME} + EXPORT "${targets_export_name}" + INCLUDES DESTINATION "${include_install_dir}" + ) + + install( + FILES "doctest/doctest.h" + DESTINATION "${include_install_dir}/doctest" + ) + install( + DIRECTORY "doctest/extensions" + DESTINATION "${include_install_dir}/doctest" + FILES_MATCHING PATTERN "*.h" + ) + + install( + FILES "${project_config}" "${version_config}" + DESTINATION "${config_install_dir}" + ) + + install( + FILES "scripts/cmake/doctest.cmake" "scripts/cmake/doctestAddTests.cmake" + DESTINATION "${config_install_dir}" + ) + + install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${config_install_dir}" + ) +endif() diff --git a/contrib/doctest/doctest/doctest.h b/contrib/doctest/doctest/doctest.h index 42eb03997..5c754cde0 100644 --- a/contrib/doctest/doctest/doctest.h +++ b/contrib/doctest/doctest/doctest.h @@ -4,14 +4,14 @@ // // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD // -// Copyright (c) 2016-2021 Viktor Kirilov +// Copyright (c) 2016-2023 Viktor Kirilov // // Distributed under the MIT Software License // See accompanying file LICENSE.txt or copy at // https://opensource.org/licenses/MIT // // The documentation can be found at the library's page: -// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md // // ================================================================================================= // ================================================================================================= @@ -48,8 +48,16 @@ #define DOCTEST_VERSION_MAJOR 2 #define DOCTEST_VERSION_MINOR 4 -#define DOCTEST_VERSION_PATCH 6 -#define DOCTEST_VERSION_STR "2.4.6" +#define DOCTEST_VERSION_PATCH 11 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) #define DOCTEST_VERSION \ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) @@ -60,6 +68,12 @@ // ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect +#ifdef _MSC_VER +#define DOCTEST_CPLUSPLUS _MSVC_LANG +#else +#define DOCTEST_CPLUSPLUS __cplusplus +#endif + #define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) // GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... @@ -71,12 +85,15 @@ DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) #endif // MSVC #endif // MSVC -#if defined(__clang__) && defined(__clang_minor__) +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) #define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ !defined(__INTEL_COMPILER) #define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #endif // GCC +#if defined(__INTEL_COMPILER) +#define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif // ICC #ifndef DOCTEST_MSVC #define DOCTEST_MSVC 0 @@ -87,12 +104,15 @@ #ifndef DOCTEST_GCC #define DOCTEST_GCC 0 #endif // DOCTEST_GCC +#ifndef DOCTEST_ICC +#define DOCTEST_ICC 0 +#endif // DOCTEST_ICC // ================================================================================================= // == COMPILER WARNINGS HELPERS ==================================================================== // ================================================================================================= -#if DOCTEST_CLANG +#if DOCTEST_CLANG && !DOCTEST_ICC #define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) #define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") #define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) @@ -137,85 +157,94 @@ // == COMPILER WARNINGS ============================================================================ // ================================================================================================= +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* common ones */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' - -// 4548 - expression before comma has no effect; expected expression with side - effect -// 4265 - class has virtual functions, but destructor is not virtual -// 4986 - exception specification does not match previous declaration -// 4350 - behavior change: 'member1' called instead of 'member2' -// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' -// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch -// 4774 - format string expected in argument 'x' is not a string literal -// 4820 - padding in structs - -// only 4 should be disabled globally: -// - 4514 # unreferenced inline function has been removed -// - 4571 # SEH related -// - 4710 # function not inlined -// - 4711 # function 'x' selected for automatic inline expansion #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5105) + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -228,6 +257,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' // GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html // MSVC version table: // https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) // MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) // MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) @@ -237,6 +267,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY #if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) #define DOCTEST_CONFIG_WINDOWS_SEH #endif // MSVC @@ -245,7 +279,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #endif // DOCTEST_CONFIG_NO_WINDOWS_SEH #if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ - !defined(__EMSCRIPTEN__) + !defined(__EMSCRIPTEN__) && !defined(__wasi__) #define DOCTEST_CONFIG_POSIX_SIGNALS #endif // _WIN32 #if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) @@ -253,7 +287,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \ + || defined(__wasi__) #define DOCTEST_CONFIG_NO_EXCEPTIONS #endif // no exceptions #endif // DOCTEST_CONFIG_NO_EXCEPTIONS @@ -268,6 +303,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#ifdef __wasi__ +#define DOCTEST_CONFIG_NO_MULTITHREADING +#endif + #if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) #define DOCTEST_CONFIG_IMPLEMENT #endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN @@ -295,6 +334,16 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_INTERFACE #endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +// needed for extern template instantiations +// see https://github.com/fmtlib/fmt/issues/2228 +#if DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL +#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE +#else // DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE +#define DOCTEST_INTERFACE_DEF +#endif // DOCTEST_MSVC + #define DOCTEST_EMPTY #if DOCTEST_MSVC @@ -311,18 +360,61 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) #endif +#ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE +#define DOCTEST_INLINE_NOINLINE inline +#else +#define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE +#endif + #ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC #define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC #endif // DOCTEST_NORETURN #ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC #define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC #endif // DOCTEST_NOEXCEPT +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#define DOCTEST_CONSTEXPR_FUNC inline +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#define DOCTEST_CONSTEXPR_FUNC constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR + +#ifndef DOCTEST_NO_SANITIZE_INTEGER +#if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0) +#define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define DOCTEST_NO_SANITIZE_INTEGER +#endif +#endif // DOCTEST_NO_SANITIZE_INTEGER + // ================================================================================================= // == FEATURE DETECTION END ======================================================================== // ================================================================================================= +#define DOCTEST_DECLARE_INTERFACE(name) \ + virtual ~name(); \ + name() = default; \ + name(const name&) = delete; \ + name(name&&) = delete; \ + name& operator=(const name&) = delete; \ + name& operator=(name&&) = delete; + +#define DOCTEST_DEFINE_INTERFACE(name) \ + name::~name() = default; + // internal macros for string concatenation and anonymous variable name generation #define DOCTEST_CAT_IMPL(s1, s2) s1##s2 #define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) @@ -332,8 +424,6 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) #endif // __COUNTER__ -#define DOCTEST_TOSTR(x) #x - #ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE #define DOCTEST_REF_WRAP(x) x& #else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE @@ -347,31 +437,39 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_PLATFORM_IPHONE #elif defined(_WIN32) #define DOCTEST_PLATFORM_WINDOWS +#elif defined(__wasi__) +#define DOCTEST_PLATFORM_WASI #else // DOCTEST_PLATFORM #define DOCTEST_PLATFORM_LINUX #endif // DOCTEST_PLATFORM -#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ - static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) -#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP #ifndef DOCTEST_BREAK_INTO_DEBUGGER // should probably take a look at https://github.com/scottt/debugbreak #ifdef DOCTEST_PLATFORM_LINUX #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) // Break at the location of the failing check if possible -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) #else #include <signal.h> #define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) #endif #elif defined(DOCTEST_PLATFORM_MAC) #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#elif defined(__ppc__) || defined(__ppc64__) +// https://www.cocoawithlove.com/2008/03/break-into-debugger.html +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler) #else -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler) #endif #elif DOCTEST_MSVC #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() @@ -387,54 +485,67 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP // this is kept here for backwards compatibility since the config option was changed #ifdef DOCTEST_CONFIG_USE_IOSFWD +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS #define DOCTEST_CONFIG_USE_STD_HEADERS +#endif #endif // DOCTEST_CONFIG_USE_IOSFWD +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 +#if DOCTEST_CLANG +#include <ciso646> +#endif // clang + +#ifdef _LIBCPP_VERSION +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // _LIBCPP_VERSION + #ifdef DOCTEST_CONFIG_USE_STD_HEADERS #ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#include <iosfwd> +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <cstddef> #include <ostream> +#include <istream> +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #else // DOCTEST_CONFIG_USE_STD_HEADERS -#if DOCTEST_CLANG -// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) -#include <ciso646> -#endif // clang - -#ifdef _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD -#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD -#else // _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { -#define DOCTEST_STD_NAMESPACE_END } -#endif // _LIBCPP_VERSION - // Forward declaring 'X' in namespace std is not permitted by the C++ Standard. DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) -DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) -typedef decltype(nullptr) nullptr_t; +namespace std { // NOLINT(cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using) +typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using) template <class charT> struct char_traits; template <> struct char_traits<char>; template <class charT, class traits> -class basic_ostream; -typedef basic_ostream<char, char_traits<char>> ostream; +class basic_ostream; // NOLINT(fuchsia-virtual-inheritance) +typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using) +template<class traits> +// NOLINTNEXTLINE +basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>&, const char*); +template <class charT, class traits> +class basic_istream; +typedef basic_istream<char, char_traits<char>> istream; // NOLINT(modernize-use-using) template <class... Types> class tuple; #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -template <class _Ty> +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template <class Ty> class allocator; -template <class _Elem, class _Traits, class _Alloc> +template <class Elem, class Traits, class Alloc> class basic_string; using string = basic_string<char, char_traits<char>, allocator<char>>; #endif // VS 2019 -DOCTEST_STD_NAMESPACE_END +} // namespace std DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -446,8 +557,14 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP namespace doctest { +using std::size_t; + DOCTEST_INTERFACE extern bool is_running_in_test; +#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE +#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned +#endif + // A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length // of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: // - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) @@ -460,7 +577,6 @@ DOCTEST_INTERFACE extern bool is_running_in_test; // TODO: // - optimizations - like not deleting memory unnecessarily in operator= and etc. // - resize/reserve/clear -// - substr // - replace // - back/front // - iterator stuff @@ -470,63 +586,84 @@ DOCTEST_INTERFACE extern bool is_running_in_test; // - relational operators as free functions - taking const char* as one of the params class DOCTEST_INTERFACE String { - static const unsigned len = 24; //!OCLINT avoid private static members - static const unsigned last = len - 1; //!OCLINT avoid private static members +public: + using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE; + +private: + static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members + static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members struct view // len should be more than sizeof(view) - because of the final byte for flags { char* ptr; - unsigned size; - unsigned capacity; + size_type size; + size_type capacity; }; union { - char buf[len]; + char buf[len]; // NOLINT(*-avoid-c-arrays) view data; }; - bool isOnStack() const { return (buf[last] & 128) == 0; } - void setOnHeap(); - void setLast(unsigned in = last); + char* allocate(size_type sz); + + bool isOnStack() const noexcept { return (buf[last] & 128) == 0; } + void setOnHeap() noexcept; + void setLast(size_type in = last) noexcept; + void setSize(size_type sz) noexcept; void copy(const String& other); public: - String(); + static DOCTEST_CONSTEXPR size_type npos = static_cast<size_type>(-1); + + String() noexcept; ~String(); // cppcheck-suppress noExplicitConstructor String(const char* in); - String(const char* in, unsigned in_size); + String(const char* in, size_type in_size); + + String(std::istream& in, size_type in_size); String(const String& other); String& operator=(const String& other); String& operator+=(const String& other); - String operator+(const String& other) const; - String(String&& other); - String& operator=(String&& other); + String(String&& other) noexcept; + String& operator=(String&& other) noexcept; - char operator[](unsigned i) const; - char& operator[](unsigned i); + char operator[](size_type i) const; + char& operator[](size_type i); // the only functions I'm willing to leave in the interface - available for inlining const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT char* c_str() { - if(isOnStack()) + if (isOnStack()) { return reinterpret_cast<char*>(buf); + } return data.ptr; } - unsigned size() const; - unsigned capacity() const; + size_type size() const; + size_type capacity() const; + + String substr(size_type pos, size_type cnt = npos) &&; + String substr(size_type pos, size_type cnt = npos) const &; + + size_type find(char ch, size_type pos = 0) const; + size_type rfind(char ch, size_type pos = npos) const; int compare(const char* other, bool no_case = false) const; int compare(const String& other, bool no_case = false) const; + +friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); }; +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); @@ -534,7 +671,21 @@ DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); +class DOCTEST_INTERFACE Contains { +public: + explicit Contains(const String& string); + + bool checkWith(const String& other) const; + + String string; +}; + +DOCTEST_INTERFACE String toString(const Contains& in); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs); namespace Color { enum Enum @@ -607,7 +758,7 @@ namespace assertType { DT_WARN_THROWS_WITH = is_throws_with | is_warn, DT_CHECK_THROWS_WITH = is_throws_with | is_check, DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, - + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, @@ -688,9 +839,27 @@ struct DOCTEST_INTERFACE AssertData String m_decomp; // for specific exception-related asserts - bool m_threw_as; - const char* m_exception_type; - const char* m_exception_string; + bool m_threw_as; + const char* m_exception_type; + + class DOCTEST_INTERFACE StringContains { + private: + Contains content; + bool isContains; + + public: + StringContains(const String& str) : content(str), isContains(false) { } + StringContains(Contains cntn) : content(static_cast<Contains&&>(cntn)), isContains(true) { } + + bool check(const String& str) { return isContains ? (content == str) : (content.string == str); } + + operator const String&() const { return content.string; } + + const char* c_str() const { return content.string.c_str(); } + } m_exception_string; + + AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string); }; struct DOCTEST_INTERFACE MessageData @@ -707,13 +876,13 @@ struct DOCTEST_INTERFACE SubcaseSignature const char* m_file; int m_line; + bool operator==(const SubcaseSignature& other) const; bool operator<(const SubcaseSignature& other) const; }; struct DOCTEST_INTERFACE IContextScope { - IContextScope(); - virtual ~IContextScope(); + DOCTEST_DECLARE_INTERFACE(IContextScope) virtual void stringify(std::ostream*) const = 0; }; @@ -723,9 +892,8 @@ namespace detail { struct ContextOptions //!OCLINT too many fields { - std::ostream* cout; // stdout stream - std::cout by default - std::ostream* cerr; // stderr stream - std::cerr by default - String binary_name; // the test binary name + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name const detail::TestCase* currentTest = nullptr; @@ -744,9 +912,12 @@ struct ContextOptions //!OCLINT too many fields bool case_sensitive; // if filtering should be case sensitive bool exit; // if the program should be exited after the tests are ran/whatever bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output bool no_throw; // to skip exceptions-related assertion macros bool no_exitcode; // if the framework should return 0 as the exitcode bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework bool no_version; // to not print the version of the framework bool no_colors; // if output to the console should be colorized bool force_colors; // forces the use of colors even when a tty cannot be detected @@ -768,150 +939,189 @@ struct ContextOptions //!OCLINT too many fields }; namespace detail { - template <bool CONDITION, typename TYPE = void> - struct enable_if - {}; + namespace types { +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + using namespace std; +#else + template <bool COND, typename T = void> + struct enable_if { }; - template <typename TYPE> - struct enable_if<true, TYPE> - { typedef TYPE type; }; + template <typename T> + struct enable_if<true, T> { using type = T; }; - // clang-format off - template<class T> struct remove_reference { typedef T type; }; - template<class T> struct remove_reference<T&> { typedef T type; }; - template<class T> struct remove_reference<T&&> { typedef T type; }; + struct true_type { static DOCTEST_CONSTEXPR bool value = true; }; + struct false_type { static DOCTEST_CONSTEXPR bool value = false; }; + + template <typename T> struct remove_reference { using type = T; }; + template <typename T> struct remove_reference<T&> { using type = T; }; + template <typename T> struct remove_reference<T&&> { using type = T; }; + + template <typename T> struct is_rvalue_reference : false_type { }; + template <typename T> struct is_rvalue_reference<T&&> : true_type { }; - template<typename T, typename U = T&&> U declval(int); + template<typename T> struct remove_const { using type = T; }; + template <typename T> struct remove_const<const T> { using type = T; }; - template<typename T> T declval(long); + // Compiler intrinsics + template <typename T> struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); }; + template <typename T> struct underlying_type { using type = __underlying_type(T); }; - template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ; + template <typename T> struct is_pointer : false_type { }; + template <typename T> struct is_pointer<T*> : true_type { }; - template<class T> struct is_lvalue_reference { const static bool value=false; }; - template<class T> struct is_lvalue_reference<T&> { const static bool value=true; }; + template <typename T> struct is_array : false_type { }; + // NOLINTNEXTLINE(*-avoid-c-arrays) + template <typename T, size_t SIZE> struct is_array<T[SIZE]> : true_type { }; +#endif + } + + // <utility> + template <typename T> + T&& declval(); template <class T> - inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT - { + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type& t) DOCTEST_NOEXCEPT { return static_cast<T&&>(t); } template <class T> - inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT - { - static_assert(!is_lvalue_reference<T>::value, - "Can not forward an rvalue as an lvalue."); + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type&& t) DOCTEST_NOEXCEPT { return static_cast<T&&>(t); } - template<class T> struct remove_const { typedef T type; }; - template<class T> struct remove_const<const T> { typedef T type; }; -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template<class T> struct is_enum : public std::is_enum<T> {}; - template<class T> struct underlying_type : public std::underlying_type<T> {}; -#else - // Use compiler intrinsics - template<class T> struct is_enum { constexpr static bool value = __is_enum(T); }; - template<class T> struct underlying_type { typedef __underlying_type(T) type; }; -#endif - // clang-format on + template <typename T> + struct deferred_false : types::false_type { }; + +// MSVS 2015 :( +#if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900 + template <typename T, typename = void> + struct has_global_insertion_operator : types::false_type { }; template <typename T> - struct deferred_false - // cppcheck-suppress unusedStructMember - { static const bool value = false; }; - - namespace has_insertion_operator_impl { - std::ostream &os(); - template<class T> - DOCTEST_REF_WRAP(T) val(); - - template<class, class = void> - struct check { - static constexpr bool value = false; - }; + struct has_global_insertion_operator<T, decltype(::operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { }; - template<class T> - struct check<T, decltype(os() << val<T>(), void())> { - static constexpr bool value = true; - }; - } // namespace has_insertion_operator_impl + template <typename T, typename = void> + struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator<T>::value; }; + + template <typename T, bool global> + struct insert_hack; + + template <typename T> + struct insert_hack<T, true> { + static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); } + }; + + template <typename T> + struct insert_hack<T, false> { + static void insert(std::ostream& os, const T& t) { operator<<(os, t); } + }; + + template <typename T> + using insert_hack_t = insert_hack<T, has_global_insertion_operator<T>::value>; +#else + template <typename T, typename = void> + struct has_insertion_operator : types::false_type { }; +#endif - template<class T> - using has_insertion_operator = has_insertion_operator_impl::check<const T>; + template <typename T> + struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { }; - DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + template <typename T> + struct should_stringify_as_underlying_type { + static DOCTEST_CONSTEXPR bool value = detail::types::is_enum<T>::value && !doctest::detail::has_insertion_operator<T>::value; + }; - DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream - DOCTEST_INTERFACE String getTlsOssResult(); + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); template <bool C> - struct StringMakerBase - { + struct StringMakerBase { template <typename T> static String convert(const DOCTEST_REF_WRAP(T)) { +#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES + static_assert(deferred_false<T>::value, "No stringification detected for type T. See string conversion manual"); +#endif return "{?}"; } }; - template <> - struct StringMakerBase<true> - { - template <typename T> - static String convert(const DOCTEST_REF_WRAP(T) in) { - *getTlsOss() << in; - return getTlsOssResult(); - } - }; - - DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + template <typename T> + struct filldata; template <typename T> - String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { - return rawMemoryToString(&object, sizeof(object)); + void filloss(std::ostream* stream, const T& in) { + filldata<T>::fill(stream, in); + } + + template <typename T, size_t N> + void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays) + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filloss<typename types::remove_reference<decltype(in)>::type>(stream, in); } template <typename T> - const char* type_to_string() { - return "<>"; + String toStream(const T& in) { + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); } + + template <> + struct StringMakerBase<true> { + template <typename T> + static String convert(const DOCTEST_REF_WRAP(T) in) { + return toStream(in); + } + }; } // namespace detail template <typename T> -struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value> +struct StringMaker : public detail::StringMakerBase< + detail::has_insertion_operator<T>::value || detail::types::is_pointer<T>::value || detail::types::is_array<T>::value> {}; -template <typename T> -struct StringMaker<T*> -{ - template <typename U> - static String convert(U* p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; +#ifndef DOCTEST_STRINGIFY +#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY +#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__)) +#else +#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__) +#endif +#endif -template <typename R, typename C> -struct StringMaker<R C::*> -{ - static String convert(R C::*p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; +template <typename T> +String toString() { +#if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0 + String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void) + String::size_type beginPos = ret.find('<'); + return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)"))); +#else + String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE] + String::size_type begin = ret.find('=') + 2; + return ret.substr(begin, ret.size() - begin - 1); +#endif +} -template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true> +template <typename T, typename detail::types::enable_if<!detail::should_stringify_as_underlying_type<T>::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { return StringMaker<T>::convert(value); } #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(char* in); DOCTEST_INTERFACE String toString(const char* in); #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +DOCTEST_INTERFACE String toString(String in); + +DOCTEST_INTERFACE String toString(std::nullptr_t); + DOCTEST_INTERFACE String toString(bool in); + DOCTEST_INTERFACE String toString(float in); DOCTEST_INTERFACE String toString(double in); DOCTEST_INTERFACE String toString(double long in); @@ -919,40 +1129,95 @@ DOCTEST_INTERFACE String toString(double long in); DOCTEST_INTERFACE String toString(char in); DOCTEST_INTERFACE String toString(char signed in); DOCTEST_INTERFACE String toString(char unsigned in); -DOCTEST_INTERFACE String toString(int short in); -DOCTEST_INTERFACE String toString(int short unsigned in); -DOCTEST_INTERFACE String toString(int in); -DOCTEST_INTERFACE String toString(int unsigned in); -DOCTEST_INTERFACE String toString(int long in); -DOCTEST_INTERFACE String toString(int long unsigned in); -DOCTEST_INTERFACE String toString(int long long in); -DOCTEST_INTERFACE String toString(int long long unsigned in); -DOCTEST_INTERFACE String toString(std::nullptr_t in); - -template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true> +DOCTEST_INTERFACE String toString(short in); +DOCTEST_INTERFACE String toString(short unsigned in); +DOCTEST_INTERFACE String toString(signed in); +DOCTEST_INTERFACE String toString(unsigned in); +DOCTEST_INTERFACE String toString(long in); +DOCTEST_INTERFACE String toString(long unsigned in); +DOCTEST_INTERFACE String toString(long long in); +DOCTEST_INTERFACE String toString(long long unsigned in); + +template <typename T, typename detail::types::enable_if<detail::should_stringify_as_underlying_type<T>::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { - typedef typename detail::underlying_type<T>::type UT; - return toString(static_cast<UT>(value)); + using UT = typename detail::types::underlying_type<T>::type; + return (DOCTEST_STRINGIFY(static_cast<UT>(value))); } -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -DOCTEST_INTERFACE String toString(const std::string& in); -#endif // VS 2019 +namespace detail { + template <typename T> + struct filldata + { + static void fill(std::ostream* stream, const T& in) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + insert_hack_t<T>::insert(*stream, in); +#else + operator<<(*stream, in); +#endif + } + }; + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) +// NOLINTBEGIN(*-avoid-c-arrays) + template <typename T, size_t N> + struct filldata<T[N]> { + static void fill(std::ostream* stream, const T(&in)[N]) { + *stream << "["; + for (size_t i = 0; i < N; i++) { + if (i != 0) { *stream << ", "; } + *stream << (DOCTEST_STRINGIFY(in[i])); + } + *stream << "]"; + } + }; +// NOLINTEND(*-avoid-c-arrays) +DOCTEST_MSVC_SUPPRESS_WARNING_POP + + // Specialized since we don't want the terminating null byte! +// NOLINTBEGIN(*-avoid-c-arrays) + template <size_t N> + struct filldata<const char[N]> { + static void fill(std::ostream* stream, const char (&in)[N]) { + *stream << String(in, in[N - 1] ? N : N - 1); + } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + }; +// NOLINTEND(*-avoid-c-arrays) + + template <> + struct filldata<const void*> { + static void fill(std::ostream* stream, const void* in); + }; + + template <typename T> + struct filldata<T*> { +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180) + static void fill(std::ostream* stream, const T* in) { +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast") + filldata<const void*>::fill(stream, +#if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0) + reinterpret_cast<const void*>(in) +#else + *reinterpret_cast<const void* const*>(&in) +#endif + ); +DOCTEST_CLANG_SUPPRESS_WARNING_POP + } + }; +} -class DOCTEST_INTERFACE Approx +struct DOCTEST_INTERFACE Approx { -public: - explicit Approx(double value); + Approx(double value); Approx operator()(double value) const; #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> explicit Approx(const T& value, - typename detail::enable_if<std::is_constructible<double, T>::value>::type* = + typename detail::types::enable_if<std::is_constructible<double, T>::value>::type* = static_cast<T*>(nullptr)) { - *this = Approx(static_cast<double>(value)); + *this = static_cast<double>(value); } #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS @@ -960,7 +1225,7 @@ public: #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> - typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( + typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( const T& newEpsilon) { m_epsilon = static_cast<double>(newEpsilon); return *this; @@ -971,7 +1236,7 @@ public: #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> - typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( + typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( const T& newScale) { m_scale = static_cast<double>(newScale); return *this; @@ -992,30 +1257,27 @@ public: DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend String toString(const Approx& in); - #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_APPROX_PREFIX \ - template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type + template <typename T> friend typename std::enable_if<std::is_constructible<double, T>::value, bool>::type - DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast<double>(lhs), rhs); } DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) && lhs != rhs; } #undef DOCTEST_APPROX_PREFIX #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format on -private: double m_epsilon; double m_scale; double m_value; @@ -1025,18 +1287,35 @@ DOCTEST_INTERFACE String toString(const Approx& in); DOCTEST_INTERFACE const ContextOptions* getContextOptions(); -#if !defined(DOCTEST_CONFIG_DISABLE) +template <typename F> +struct DOCTEST_INTERFACE_DECL IsNaN +{ + F value; bool flipped; + IsNaN(F f, bool flip = false) : value(f), flipped(flip) { } + IsNaN<F> operator!() const { return { value, !flipped }; } + operator bool() const; +}; +#ifndef __MINGW32__ +extern template struct DOCTEST_INTERFACE_DECL IsNaN<float>; +extern template struct DOCTEST_INTERFACE_DECL IsNaN<double>; +extern template struct DOCTEST_INTERFACE_DECL IsNaN<long double>; +#endif +DOCTEST_INTERFACE String toString(IsNaN<float> in); +DOCTEST_INTERFACE String toString(IsNaN<double> in); +DOCTEST_INTERFACE String toString(IsNaN<double long> in); + +#ifndef DOCTEST_CONFIG_DISABLE namespace detail { // clang-format off #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - template<class T> struct decay_array { typedef T type; }; - template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; }; - template<class T> struct decay_array<T[]> { typedef T* type; }; + template<class T> struct decay_array { using type = T; }; + template<class T, unsigned N> struct decay_array<T[N]> { using type = T*; }; + template<class T> struct decay_array<T[]> { using type = T*; }; - template<class T> struct not_char_pointer { enum { value = 1 }; }; - template<> struct not_char_pointer<char*> { enum { value = 0 }; }; - template<> struct not_char_pointer<const char*> { enum { value = 0 }; }; + template<class T> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 1; }; + template<> struct not_char_pointer<char*> { static DOCTEST_CONSTEXPR int value = 0; }; + template<> struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR int value = 0; }; template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {}; #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING @@ -1059,16 +1338,22 @@ namespace detail { bool m_entered = false; Subcase(const String& name, const char* file, int line); + Subcase(const Subcase&) = delete; + Subcase(Subcase&&) = delete; + Subcase& operator=(const Subcase&) = delete; + Subcase& operator=(Subcase&&) = delete; ~Subcase(); operator bool() const; + + private: + bool checkFilters(); }; template <typename L, typename R> String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, const DOCTEST_REF_WRAP(R) rhs) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return toString(lhs) + op + toString(rhs); + return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs)); } #if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) @@ -1079,12 +1364,16 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") // If not it doesn't find the operator or if the operator at global scope is defined after // this template, the template won't be instantiated due to SFINAE. Once the template is not // instantiated it can look for global operator using normal conversions. -#define SFINAE_OP(ret,op) decltype(doctest::detail::declval<L>() op doctest::detail::declval<R>(),static_cast<ret>(0)) +#ifdef __NVCC__ +#define SFINAE_OP(ret,op) ret +#else +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{}) +#endif #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ template <typename R> \ - DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ - bool res = op_macro(doctest::detail::forward<L>(lhs), doctest::detail::forward<R>(rhs)); \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<R>(rhs)); \ if(m_at & assertType::is_false) \ res = !res; \ if(!res || doctest::getContextOptions()->success) \ @@ -1103,11 +1392,12 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") return *this; \ } - struct DOCTEST_INTERFACE Result + struct DOCTEST_INTERFACE Result // NOLINT(*-member-init) { bool m_passed; String m_decomp; + Result() = default; // TODO: Why do we need this? (To remove NOLINT) Result(bool passed, const String& decomposition = String()); // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence @@ -1164,8 +1454,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") #ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE bool #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } @@ -1213,26 +1502,26 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") assertType::Enum m_at; explicit Expression_lhs(L&& in, assertType::Enum at) - : lhs(doctest::detail::forward<L>(in)) + : lhs(static_cast<L&&>(in)) , m_at(at) {} DOCTEST_NOINLINE operator Result() { -// this is needed only foc MSVC 2015: -// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202 +// this is needed only for MSVC 2015 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool bool res = static_cast<bool>(lhs); DOCTEST_MSVC_SUPPRESS_WARNING_POP - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional res = !res; + } - if(!res || getContextOptions()->success) - return Result(res, toString(lhs)); - return Result(res); + if(!res || getContextOptions()->success) { + return { res, (DOCTEST_STRINGIFY(lhs)) }; + } + return { res }; } - /* This is required for user-defined conversions from Expression_lhs to L */ - //operator L() const { return lhs; } - operator L() const { return lhs; } + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } // clang-format off DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional @@ -1289,22 +1578,27 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 template <typename L> - Expression_lhs<L> operator<<(L &&operand) { - return Expression_lhs<L>(doctest::detail::forward<L>(operand), m_at); + Expression_lhs<L> operator<<(L&& operand) { + return Expression_lhs<L>(static_cast<L&&>(operand), m_at); + } + + template <typename L,typename types::enable_if<!doctest::detail::types::is_rvalue_reference<L>::value,void >::type* = nullptr> + Expression_lhs<const L&> operator<<(const L &operand) { + return Expression_lhs<const L&>(operand, m_at); } }; struct DOCTEST_INTERFACE TestSuite { - const char* m_test_suite; - const char* m_description; - bool m_skip; - bool m_no_breaks; - bool m_no_output; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; TestSuite& operator*(const char* in); @@ -1315,25 +1609,28 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP } }; - typedef void (*funcType)(); + using funcType = void (*)(); struct DOCTEST_INTERFACE TestCase : public TestCaseData { funcType m_test; // a function pointer to the test case - const char* m_type; // for templated test cases - gets appended to the real name + String m_type; // for templated test cases - gets appended to the real name int m_template_id; // an ID used to distinguish between the different versions of a templated test case String m_full_name; // contains the name (only for templated test cases!) + the template type TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type = "", int template_id = -1); + const String& type = String(), int template_id = -1); TestCase(const TestCase& other); + TestCase(TestCase&&) = delete; DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function TestCase& operator=(const TestCase& other); DOCTEST_MSVC_SUPPRESS_WARNING_POP + TestCase& operator=(TestCase&&) = delete; + TestCase& operator*(const char* in); template <typename T> @@ -1343,6 +1640,8 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP } bool operator<(const TestCase& other) const; + + ~TestCase() = default; }; // forward declarations of functions used by the macros @@ -1382,27 +1681,36 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP struct DOCTEST_INTERFACE ResultBuilder : public AssertData { ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type = "", const char* exception_string = ""); + const char* exception_type = "", const String& exception_string = ""); + + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string); void setResult(const Result& res); template <int comparison, typename L, typename R> - DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); - if(m_failed || getContextOptions()->success) + if (m_failed || getContextOptions()->success) { m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + return !m_failed; } template <typename L> - DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { m_failed = !val; - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional m_failed = !m_failed; + } + + if (m_failed || getContextOptions()->success) { + m_decomp = (DOCTEST_STRINGIFY(val)); + } - if(m_failed || getContextOptions()->success) - m_decomp = toString(val); + return !m_failed; } void translateException(); @@ -1422,8 +1730,8 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); - DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, - const char* expr, Result result); + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, const Result& result); #define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ do { \ @@ -1438,7 +1746,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP if(checkIfShouldThrow(at)) \ throwException(); \ } \ - return; \ + return !failed; \ } \ } while(false) @@ -1453,7 +1761,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP throwException() template <int comparison, typename L, typename R> - DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); @@ -1464,10 +1772,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; } template <typename L> - DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) val) { bool failed = !val; @@ -1478,14 +1787,14 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); - DOCTEST_ASSERT_IN_TESTS(toString(val)); + DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val))); + DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val))); + return !failed; } struct DOCTEST_INTERFACE IExceptionTranslator { - IExceptionTranslator(); - virtual ~IExceptionTranslator(); + DOCTEST_DECLARE_INTERFACE(IExceptionTranslator) virtual bool translate(String&) const = 0; }; @@ -1501,7 +1810,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP try { throw; // lgtm [cpp/rethrow-no-exception] // cppcheck-suppress catchExceptionByValue - } catch(T ex) { // NOLINT + } catch(const T& ex) { res = m_translateFunction(ex); //!OCLINT parameter reassignment return true; } catch(...) {} //!OCLINT - empty catch statement @@ -1516,95 +1825,70 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); - template <bool C> - struct StringStreamBase - { - template <typename T> - static void convert(std::ostream* s, const T& in) { - *s << toString(in); - } - - // always treat char* as a string in this context - no matter - // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined - static void convert(std::ostream* s, const char* in) { *s << String(in); } - }; - - template <> - struct StringStreamBase<true> - { - template <typename T> - static void convert(std::ostream* s, const T& in) { - *s << in; - } - }; + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + ContextScopeBase(const ContextScopeBase&) = delete; - template <typename T> - struct StringStream : public StringStreamBase<has_insertion_operator<T>::value> - {}; + ContextScopeBase& operator=(const ContextScopeBase&) = delete; + ContextScopeBase& operator=(ContextScopeBase&&) = delete; - template <typename T> - void toStream(std::ostream* s, const T& value) { - StringStream<T>::convert(s, value); - } + ~ContextScopeBase() override = default; -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); - DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); - DOCTEST_INTERFACE void toStream(std::ostream* s, float in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); - - DOCTEST_INTERFACE void toStream(std::ostream* s, char in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); - - // ContextScope base class used to allow implementing methods of ContextScope - // that don't depend on the template parameter in doctest.cpp. - class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { protected: ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other) noexcept; void destroy(); + bool need_to_destroy{true}; }; template <typename L> class ContextScope : public ContextScopeBase { - const L lambda_; + L lambda_; public: explicit ContextScope(const L &lambda) : lambda_(lambda) {} + explicit ContextScope(L&& lambda) : lambda_(static_cast<L&&>(lambda)) { } - ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + ContextScope(const ContextScope&) = delete; + ContextScope(ContextScope&&) noexcept = default; + + ContextScope& operator=(const ContextScope&) = delete; + ContextScope& operator=(ContextScope&&) = delete; void stringify(std::ostream* s) const override { lambda_(s); } - ~ContextScope() override { destroy(); } + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } }; struct DOCTEST_INTERFACE MessageBuilder : public MessageData { std::ostream* m_stream; + bool logged = false; MessageBuilder(const char* file, int line, assertType::Enum severity); - MessageBuilder() = delete; + + MessageBuilder(const MessageBuilder&) = delete; + MessageBuilder(MessageBuilder&&) = delete; + + MessageBuilder& operator=(const MessageBuilder&) = delete; + MessageBuilder& operator=(MessageBuilder&&) = delete; + ~MessageBuilder(); // the preferred way of chaining parameters for stringification +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) template <typename T> MessageBuilder& operator,(const T& in) { - toStream(m_stream, in); + *m_stream << (DOCTEST_STRINGIFY(in)); return *this; } +DOCTEST_MSVC_SUPPRESS_WARNING_POP // kept here just for backwards-compatibility - the comma operator should be preferred now template <typename T> @@ -1620,7 +1904,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP bool log(); void react(); }; - + template <typename L> ContextScope<L> MakeContextScope(const L &lambda) { return ContextScope<L>(lambda); @@ -1673,7 +1957,7 @@ int registerExceptionTranslator(String (*)(T)) { #endif // DOCTEST_CONFIG_DISABLE namespace detail { - typedef void (*assert_handler)(const AssertData&); + using assert_handler = void (*)(const AssertData&); struct ContextState; } // namespace detail @@ -1686,12 +1970,19 @@ class DOCTEST_INTERFACE Context public: explicit Context(int argc = 0, const char* const* argv = nullptr); - ~Context(); + Context(const Context&) = delete; + Context(Context&&) = delete; + + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + ~Context(); // NOLINT(performance-trivially-destructible) void applyCommandLine(int argc, const char* const* argv); void addFilter(const char* filter, const char* value); void clearFilters(); + void setOption(const char* option, bool value); void setOption(const char* option, int value); void setOption(const char* option, const char* value); @@ -1701,6 +1992,8 @@ public: void setAssertHandler(detail::assert_handler ah); + void setCout(std::ostream* out); + int run(); }; @@ -1727,6 +2020,7 @@ struct DOCTEST_INTERFACE CurrentTestCaseStats int numAssertsFailedCurrentTest; double seconds; int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; }; struct DOCTEST_INTERFACE TestCaseException @@ -1790,8 +2084,7 @@ struct DOCTEST_INTERFACE IReporter // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) virtual void test_case_skipped(const TestCaseData&) = 0; - // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have - virtual ~IReporter(); + DOCTEST_DECLARE_INTERFACE(IReporter) // can obtain all currently active contexts and stringify them if one wishes to do so static int get_num_active_contexts(); @@ -1803,7 +2096,7 @@ struct DOCTEST_INTERFACE IReporter }; namespace detail { - typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + using reporterCreatorFunc = IReporter* (*)(const ContextOptions&); DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); @@ -1820,14 +2113,30 @@ int registerReporter(const char* name, int priority, bool isReporter) { } } // namespace doctest +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_EMPTY [] { return false; }() +#else +#define DOCTEST_FUNC_EMPTY (void)0 +#endif + // if registering is not disabled -#if !defined(DOCTEST_CONFIG_DISABLE) +#ifndef DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_SCOPE_BEGIN [&] +#define DOCTEST_FUNC_SCOPE_END () +#define DOCTEST_FUNC_SCOPE_RET(v) return v +#else +#define DOCTEST_FUNC_SCOPE_BEGIN do +#define DOCTEST_FUNC_SCOPE_END while(false) +#define DOCTEST_FUNC_SCOPE_RET(v) (void)0 +#endif // common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ - if(b.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - b.react() +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ + if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react(); \ + DOCTEST_FUNC_SCOPE_RET(!b.m_failed) #ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #define DOCTEST_WRAP_IN_TRY(x) x; @@ -1835,7 +2144,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_WRAP_IN_TRY(x) \ try { \ x; \ - } catch(...) { _DOCTEST_RB.translateException(); } + } catch(...) { DOCTEST_RB.translateException(); } #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS @@ -1849,27 +2158,26 @@ int registerReporter(const char* name, int priority, bool isReporter) { // registers the test by initializing a dummy var with a function #define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ - global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \ doctest::detail::regTest( \ doctest::detail::TestCase( \ f, __FILE__, __LINE__, \ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() + decorators)) #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ - namespace { \ + namespace { /* NOLINT */ \ struct der : public base \ { \ void f(); \ }; \ - static void func() { \ + static DOCTEST_INLINE_NOINLINE void func() { \ der v; \ v.f(); \ } \ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ } \ - inline DOCTEST_NOINLINE void der::f() + DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers) #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ static void f(); \ @@ -1878,18 +2186,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ static doctest::detail::funcType proxy() { return f; } \ - DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ static void f() // for registering tests #define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for registering tests in classes - requires C++17 for inline variables! -#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#if DOCTEST_CPLUSPLUS >= 201703L #define DOCTEST_TEST_CASE_CLASS(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ decorators) #else // DOCTEST_TEST_CASE_CLASS #define DOCTEST_TEST_CASE_CLASS(...) \ @@ -1898,26 +2206,25 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for converting types to strings without the <typeinfo> header and demangling -#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ - template <> \ - inline const char* type_to_string<__VA_ARGS__>() { \ - return "<" #__VA_ARGS__ ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(...) \ - namespace doctest { namespace detail { \ - DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \ + namespace doctest { \ + template <> \ + inline String toString<__VA_ARGS__>() { \ + return str; \ } \ } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + static_assert(true, "") + +#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__) #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ template <typename T> \ static void func(); \ - namespace { \ + namespace { /* NOLINT */ \ template <typename Tuple> \ struct iter; \ template <typename Type, typename... Rest> \ @@ -1926,7 +2233,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { iter(const char* file, unsigned line, int index) { \ doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \ doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string<Type>(), \ + doctest::toString<Type>(), \ int(line) * 1000 + index) \ * dec); \ iter<std::tuple<Rest...>>(file, line, index + 1); \ @@ -1943,20 +2250,20 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) #define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ - doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ - DOCTEST_GLOBAL_NO_WARNINGS_END() + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) #define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ @@ -1965,17 +2272,17 @@ int registerReporter(const char* name, int priority, bool isReporter) { static void anon() #define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) // for subcases #define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ doctest::detail::Subcase(name, __FILE__, __LINE__)) // for grouping tests in test suites by using code blocks #define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ namespace ns_name { namespace doctest_detail_test_suite_ns { \ - static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ @@ -1995,53 +2302,53 @@ int registerReporter(const char* name, int priority, bool isReporter) { namespace ns_name #define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) // for starting a testsuite block #define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") // for ending a testsuite block #define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int // for registering exception translators #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ inline doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ - doctest::registerExceptionTranslator(translatorName); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerExceptionTranslator(translatorName)) \ doctest::String translatorName(signature) #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ signature) // for registering reporters #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter<reporter>(name, priority, true); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter<reporter>(name, priority, true)) \ + static_assert(true, "") // for registering listeners #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter<reporter>(name, priority, false); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter<reporter>(name, priority, false)) \ + static_assert(true, "") -// for logging +// clang-format off +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 #define DOCTEST_INFO(...) \ - DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ __VA_ARGS__) +// clang-format on #define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ - auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ [&](std::ostream* s_name) { \ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ mb_name.m_stream = s_name; \ @@ -2051,16 +2358,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) #define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ mb * __VA_ARGS__; \ - DOCTEST_ASSERT_LOG_AND_REACT(mb); \ - } while(false) + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + } DOCTEST_FUNC_SCOPE_END // clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) // clang-format on #define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) @@ -2073,18 +2382,37 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - } while(false) + } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2098,6 +2426,14 @@ int registerReporter(const char* name, int priority, bool isReporter) { doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + #endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS #define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) @@ -2108,51 +2444,83 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) // clang-format off -#define DOCTEST_WARN_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) -#define DOCTEST_CHECK_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END // clang-format on +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #expr, #__VA_ARGS__, message); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(const typename doctest::detail::remove_const< \ - typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ - _DOCTEST_RB.translateException(); \ - _DOCTEST_RB.m_threw_as = true; \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(const typename doctest::detail::types::remove_const< \ + typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ } \ - } while(false) + } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, expr_str, "", __VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ } \ - } while(false) + } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END // clang-format off #define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") @@ -2175,166 +2543,23 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) #define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } while(false) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } while(false) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } while(false) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } while(false) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } while(false) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END // clang-format on -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \ - __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ - doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ - #__VA_ARGS__, __VA_ARGS__) - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) -#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) -#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) -#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) -#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) -#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) -#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) -#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) -#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) -#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) -#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) -#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) -#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) -#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) -#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) -#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) -#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) -#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) - -#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) -#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS - -#undef DOCTEST_WARN_THROWS -#undef DOCTEST_CHECK_THROWS -#undef DOCTEST_REQUIRE_THROWS -#undef DOCTEST_WARN_THROWS_AS -#undef DOCTEST_CHECK_THROWS_AS -#undef DOCTEST_REQUIRE_THROWS_AS -#undef DOCTEST_WARN_THROWS_WITH -#undef DOCTEST_CHECK_THROWS_WITH -#undef DOCTEST_REQUIRE_THROWS_WITH -#undef DOCTEST_WARN_THROWS_WITH_AS -#undef DOCTEST_CHECK_THROWS_WITH_AS -#undef DOCTEST_REQUIRE_THROWS_WITH_AS -#undef DOCTEST_WARN_NOTHROW -#undef DOCTEST_CHECK_NOTHROW -#undef DOCTEST_REQUIRE_NOTHROW - -#undef DOCTEST_WARN_THROWS_MESSAGE -#undef DOCTEST_CHECK_THROWS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_MESSAGE -#undef DOCTEST_WARN_THROWS_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_WARN_NOTHROW_MESSAGE -#undef DOCTEST_CHECK_NOTHROW_MESSAGE -#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) - -#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#undef DOCTEST_REQUIRE -#undef DOCTEST_REQUIRE_FALSE -#undef DOCTEST_REQUIRE_MESSAGE -#undef DOCTEST_REQUIRE_FALSE_MESSAGE -#undef DOCTEST_REQUIRE_EQ -#undef DOCTEST_REQUIRE_NE -#undef DOCTEST_REQUIRE_GT -#undef DOCTEST_REQUIRE_LT -#undef DOCTEST_REQUIRE_GE -#undef DOCTEST_REQUIRE_LE -#undef DOCTEST_REQUIRE_UNARY -#undef DOCTEST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // ================================================================================================= @@ -2344,7 +2569,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #else // DOCTEST_CONFIG_DISABLE #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ - namespace { \ + namespace /* NOLINT */ { \ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ struct der : public base \ { void f(); }; \ @@ -2358,51 +2583,48 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests #define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests in classes #define DOCTEST_TEST_CASE_CLASS(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for converting types to strings without the <typeinfo> header and demangling -#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#define DOCTEST_TYPE_TO_STRING_IMPL(...) +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "") +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") // for typed tests #define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ template <typename type> \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ template <typename type> \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") // for subcases #define DOCTEST_SUBCASE(name) // for a testsuite block -#define DOCTEST_TEST_SUITE(name) namespace +#define DOCTEST_TEST_SUITE(name) namespace // NOLINT // for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") // for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ - static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) @@ -2416,80 +2638,253 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0)) #define DOCTEST_FAIL(...) (static_cast<void>(0)) -#define DOCTEST_WARN(...) (static_cast<void>(0)) -#define DOCTEST_CHECK(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_FALSE(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) - -#define DOCTEST_WARN_EQ(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_EQ(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_EQ(...) (static_cast<void>(0)) -#define DOCTEST_WARN_NE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_GT(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_GT(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_GT(...) (static_cast<void>(0)) -#define DOCTEST_WARN_LT(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_LT(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_LT(...) (static_cast<void>(0)) -#define DOCTEST_WARN_GE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_GE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_GE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_LE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_LE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_LE(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_WARN_UNARY_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_UNARY_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) (static_cast<void>(0)) +#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \ + && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES) + +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() + +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template <typename L, typename R> \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest + +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }() +#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED #endif // DOCTEST_CONFIG_DISABLE +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \ + "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }() + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + // clang-format off // KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS #define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ @@ -2536,11 +2931,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { // clang-format on // == SHORT VERSIONS OF THE MACROS -#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) +#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES #define TEST_CASE(name) DOCTEST_TEST_CASE(name) #define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) #define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__) #define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) #define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) #define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) @@ -2673,39 +3069,19 @@ int registerReporter(const char* name, int priority, bool isReporter) { #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES -#if !defined(DOCTEST_CONFIG_DISABLE) +#ifndef DOCTEST_CONFIG_DISABLE // this is here to clear the 'current test suite' for the current translation unit - at the top DOCTEST_TEST_SUITE_END(); -// add stringification for primitive/fundamental types -namespace doctest { namespace detail { - DOCTEST_TYPE_TO_STRING_IMPL(bool) - DOCTEST_TYPE_TO_STRING_IMPL(float) - DOCTEST_TYPE_TO_STRING_IMPL(double) - DOCTEST_TYPE_TO_STRING_IMPL(long double) - DOCTEST_TYPE_TO_STRING_IMPL(char) - DOCTEST_TYPE_TO_STRING_IMPL(signed char) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) -#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) - DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) -#endif // not MSVC or wchar_t support enabled - DOCTEST_TYPE_TO_STRING_IMPL(short int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) - DOCTEST_TYPE_TO_STRING_IMPL(int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) - DOCTEST_TYPE_TO_STRING_IMPL(long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) - DOCTEST_TYPE_TO_STRING_IMPL(long long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) -}} // namespace doctest::detail - #endif // DOCTEST_CONFIG_DISABLE DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_INCLUDED #ifndef DOCTEST_SINGLE_HEADER @@ -2725,13 +3101,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") @@ -2739,65 +3113,35 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C -DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN @@ -2805,7 +3149,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <ctime> #include <cmath> #include <climits> -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 #ifdef __BORLANDC__ #include <math.h> #endif // __BORLANDC__ @@ -2817,20 +3161,33 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <utility> #include <fstream> #include <sstream> +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include <iostream> +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include <algorithm> #include <iomanip> #include <vector> +#ifndef DOCTEST_CONFIG_NO_MULTITHREADING #include <atomic> #include <mutex> +#define DOCTEST_DECLARE_MUTEX(name) std::mutex name; +#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name); +#else // DOCTEST_CONFIG_NO_MULTITHREADING +#define DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_DECLARE_STATIC_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) +#endif // DOCTEST_CONFIG_NO_MULTITHREADING #include <set> #include <map> +#include <unordered_set> #include <exception> #include <stdexcept> #include <csignal> #include <cfloat> #include <cctype> #include <cstdint> +#include <string> #ifdef DOCTEST_PLATFORM_MAC #include <sys/types.h> @@ -2843,9 +3200,11 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN // defines for a leaner windows.h #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX +#define DOCTEST_UNDEF_NOMINMAX #endif // NOMINMAX // not sure what AfxWin.h is for - here I do what Catch does @@ -2863,7 +3222,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #endif // DOCTEST_PLATFORM_WINDOWS -// this is a fix for https://github.com/onqtam/doctest/issues/348 +// this is a fix for https://github.com/doctest/doctest/issues/348 // https://mail.gnome.org/archives/xml/2012-January/msg00000.html #if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) #define STDOUT_FILENO fileno(stdout) @@ -2885,8 +3244,12 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #endif #ifndef DOCTEST_THREAD_LOCAL +#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC #define DOCTEST_THREAD_LOCAL thread_local -#endif +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL #ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES #define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 @@ -2906,12 +3269,40 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS #endif +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + namespace doctest { bool is_running_in_test = false; namespace { using namespace detail; + + template <typename Ex> + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS +#ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION + DOCTEST_CONFIG_HANDLE_EXCEPTION(e); +#else // DOCTEST_CONFIG_HANDLE_EXCEPTION +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#endif // DOCTEST_CONFIG_HANDLE_EXCEPTION + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + // case insensitive strcmp int stricmp(const char* a, const char* b) { for(;; a++, b++) { @@ -2921,20 +3312,6 @@ namespace { } } - template <typename T> - String fpToString(T value, int precision) { - std::ostringstream oss; - oss << std::setprecision(precision) << std::fixed << value; - std::string d = oss.str(); - size_t i = d.find_last_not_of('0'); - if(i != std::string::npos && i != d.size() - 1) { - if(d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d.c_str(); - } - struct Endianness { enum Arch @@ -2955,58 +3332,56 @@ namespace { } // namespace namespace detail { - void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } - - String rawMemoryToString(const void* object, unsigned size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast<int>(size), inc = 1; - if(Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; - } - - unsigned const char* bytes = static_cast<unsigned const char*>(object); - std::ostringstream oss; - oss << "0x" << std::setfill('0') << std::hex; - for(; i != end; i += inc) - oss << std::setw(2) << static_cast<unsigned>(bytes[i]); - return oss.str().c_str(); - } + DOCTEST_THREAD_LOCAL class + { + std::vector<std::streampos> stack; + std::stringstream ss; - DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; + } + + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); + + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast<unsigned>(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; - std::ostream* getTlsOss() { - g_oss.clear(); // there shouldn't be anything worth clearing in the flags - g_oss.str(""); // the slow way of resetting a string stream - //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 - return &g_oss; + std::ostream* tlssPush() { + return g_oss.push(); } - String getTlsOssResult() { - //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 - return g_oss.str().c_str(); + String tlssPop() { + return g_oss.pop(); } #ifndef DOCTEST_CONFIG_DISABLE namespace timer_large_integer { - + #if defined(DOCTEST_PLATFORM_WINDOWS) - typedef ULONGLONG type; + using type = ULONGLONG; #else // DOCTEST_PLATFORM_WINDOWS - using namespace std; - typedef uint64_t type; + using type = std::uint64_t; #endif // DOCTEST_PLATFORM_WINDOWS } -typedef timer_large_integer::type ticks_t; +using ticks_t = timer_large_integer::type; #ifdef DOCTEST_CONFIG_GETCURRENTTICKS ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } #elif defined(DOCTEST_PLATFORM_WINDOWS) ticks_t getCurrentTicks() { - static LARGE_INTEGER hz = {0}, hzo = {0}; + static LARGE_INTEGER hz = { {0} }, hzo = { {0} }; if(!hz.QuadPart) { QueryPerformanceFrequency(&hz); QueryPerformanceCounter(&hzo); @@ -3038,9 +3413,17 @@ typedef timer_large_integer::type ticks_t; ticks_t m_ticks = 0; }; -#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#ifdef DOCTEST_CONFIG_NO_MULTITHREADING + template <typename T> + using Atomic = T; +#else // DOCTEST_CONFIG_NO_MULTITHREADING + template <typename T> + using Atomic = std::atomic<T>; +#endif // DOCTEST_CONFIG_NO_MULTITHREADING + +#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING) template <typename T> - using AtomicOrMultiLaneAtomic = std::atomic<T>; + using MultiLaneAtomic = Atomic<T>; #else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // Provides a multilane implementation of an atomic variable that supports add, sub, load, // store. Instead of using a single atomic variable, this splits up into multiple ones, @@ -3057,8 +3440,8 @@ typedef timer_large_integer::type ticks_t; { struct CacheLineAlignedAtomic { - std::atomic<T> atomic{}; - char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)]; + Atomic<T> atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic<T>)]; }; CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; @@ -3088,7 +3471,7 @@ typedef timer_large_integer::type ticks_t; return result; } - T operator=(T desired) DOCTEST_NOEXCEPT { + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] store(desired); return desired; } @@ -3103,7 +3486,7 @@ typedef timer_large_integer::type ticks_t; private: // Each thread has a different atomic that it operates on. If more than NumLanes threads - // use this, some will use the same atomic. So performance will degrate a bit, but still + // use this, some will use the same atomic. So performance will degrade a bit, but still // everything will work. // // The logic here is a bit tricky. The call should be as fast as possible, so that there @@ -3114,24 +3497,21 @@ typedef timer_large_integer::type ticks_t; // assigned in a round-robin fashion. // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with // little overhead. - std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT { - static std::atomic<size_t> laneCounter; + Atomic<T>& myAtomic() DOCTEST_NOEXCEPT { + static Atomic<size_t> laneCounter; DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; return m_atomics[tlsLaneIdx].atomic; } }; - - template <typename T> - using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>; #endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // this holds both parameters from the command line and runtime data for tests struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats { - AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic; - AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic; + MultiLaneAtomic<int> numAssertsCurrentTest_atomic; + MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic; std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters @@ -3144,11 +3524,12 @@ typedef timer_large_integer::type ticks_t; std::vector<String> stringifiedContexts; // logging from INFO() due to an exception // stuff for subcases - std::vector<SubcaseSignature> subcasesStack; - std::set<decltype(subcasesStack)> subcasesPassed; - int subcasesCurrentMaxLevel; - bool should_reenter; - std::atomic<bool> shouldLogCurrentException; + bool reachedLeaf; + std::vector<SubcaseSignature> subcaseStack; + std::vector<SubcaseSignature> nextSubcaseStack; + std::unordered_set<unsigned long long> fullyTraversedSubcases; + size_t currentSubcaseDepth; + Atomic<bool> shouldLogCurrentException; void resetRunData() { numTestCases = 0; @@ -3198,7 +3579,8 @@ typedef timer_large_integer::type ticks_t; (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); // if any subcase has failed - the whole test case has failed - if(failure_flags && !ok_to_fail) + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) numTestCasesFailed++; } }; @@ -3213,23 +3595,37 @@ typedef timer_large_integer::type ticks_t; #endif // DOCTEST_CONFIG_DISABLE } // namespace detail -void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } -void String::setLast(unsigned in) { buf[last] = char(in); } +char* String::allocate(size_type sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + +void String::setOnHeap() noexcept { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } +void String::setLast(size_type in) noexcept { buf[last] = char(in); } +void String::setSize(size_type sz) noexcept { + if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); } + else { data.ptr[sz] = '\0'; data.size = sz; } +} void String::copy(const String& other) { - using namespace std; if(other.isOnStack()) { memcpy(buf, other.buf, len); } else { - setOnHeap(); - data.size = other.data.size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, other.data.ptr, data.size + 1); + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); } } -String::String() { +String::String() noexcept { buf[0] = '\0'; setLast(); } @@ -3237,26 +3633,17 @@ String::String() { String::~String() { if(!isOnStack()) delete[] data.ptr; - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) String::String(const char* in) : String(in, strlen(in)) {} -String::String(const char* in, unsigned in_size) { - using namespace std; - if(in_size <= last) { - memcpy(buf, in, in_size); - buf[in_size] = '\0'; - setLast(last - in_size); - } else { - setOnHeap(); - data.size = in_size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, in, in_size); - data.ptr[in_size] = '\0'; - } +String::String(const char* in, size_type in_size) { + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, size_type in_size) { + in.read(allocate(in_size), in_size); } String::String(const String& other) { copy(other); } @@ -3273,10 +3660,9 @@ String& String::operator=(const String& other) { } String& String::operator+=(const String& other) { - const unsigned my_old_size = size(); - const unsigned other_size = other.size(); - const unsigned total_size = my_old_size + other_size; - using namespace std; + const size_type my_old_size = size(); + const size_type other_size = other.size(); + const size_type total_size = my_old_size + other_size; if(isOnStack()) { if(total_size < len) { // append to the current stack space @@ -3323,18 +3709,13 @@ String& String::operator+=(const String& other) { return *this; } -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -String String::operator+(const String& other) const { return String(*this) += other; } - -String::String(String&& other) { - using namespace std; +String::String(String&& other) noexcept { memcpy(buf, other.buf, len); other.buf[0] = '\0'; other.setLast(); } -String& String::operator=(String&& other) { - using namespace std; +String& String::operator=(String&& other) noexcept { if(this != &other) { if(!isOnStack()) delete[] data.ptr; @@ -3345,30 +3726,60 @@ String& String::operator=(String&& other) { return *this; } -char String::operator[](unsigned i) const { - return const_cast<String*>(this)->operator[](i); // NOLINT +char String::operator[](size_type i) const { + return const_cast<String*>(this)->operator[](i); } -char& String::operator[](unsigned i) { +char& String::operator[](size_type i) { if(isOnStack()) return reinterpret_cast<char*>(buf)[i]; return data.ptr[i]; } DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") -unsigned String::size() const { +String::size_type String::size() const { if(isOnStack()) - return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32 return data.size; } DOCTEST_GCC_SUPPRESS_WARNING_POP -unsigned String::capacity() const { +String::size_type String::capacity() const { if(isOnStack()) return len; return data.capacity; } +String String::substr(size_type pos, size_type cnt) && { + cnt = std::min(cnt, size() - 1 - pos); + char* cptr = c_str(); + memmove(cptr, cptr + pos, cnt); + setSize(cnt); + return std::move(*this); +} + +String String::substr(size_type pos, size_type cnt) const & { + cnt = std::min(cnt, size() - 1 - pos); + return String{ c_str() + pos, cnt }; +} + +String::size_type String::find(char ch, size_type pos) const { + const char* begin = c_str(); + const char* end = begin + size(); + const char* it = begin + pos; + for (; it < end && *it != ch; it++); + if (it < end) { return static_cast<size_type>(it - begin); } + else { return npos; } +} + +String::size_type String::rfind(char ch, size_type pos) const { + const char* begin = c_str(); + const char* it = begin + std::min(pos, size() - 1); + for (; it >= begin && *it != ch; it--); + if (it >= begin) { return static_cast<size_type>(it - begin); } + else { return npos; } +} + int String::compare(const char* other, bool no_case) const { if(no_case) return doctest::stricmp(c_str(), other); @@ -3379,17 +3790,32 @@ int String::compare(const String& other, bool no_case) const { return compare(other.c_str(), no_case); } -// clang-format off +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } -// clang-format on std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } +Contains::Contains(const String& str) : string(str) { } + +bool Contains::checkWith(const String& other) const { + return strstr(other.c_str(), string.c_str()) != nullptr; +} + +String toString(const Contains& in) { + return "Contains( " + in.string + " )"; +} + +bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); } +bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); } +bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); } +bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); } + namespace { void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) } // namespace @@ -3403,64 +3829,42 @@ namespace Color { // clang-format off const char* assertString(assertType::Enum at) { - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled - switch(at) { //!OCLINT missing default in switch statements - case assertType::DT_WARN : return "WARN"; - case assertType::DT_CHECK : return "CHECK"; - case assertType::DT_REQUIRE : return "REQUIRE"; - - case assertType::DT_WARN_FALSE : return "WARN_FALSE"; - case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; - case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; - - case assertType::DT_WARN_THROWS : return "WARN_THROWS"; - case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; - case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; - - case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; - case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; - case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; - - case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; - case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; - case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; - - case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; - case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; - case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; - - case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; - case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; - case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; - - case assertType::DT_WARN_EQ : return "WARN_EQ"; - case assertType::DT_CHECK_EQ : return "CHECK_EQ"; - case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; - case assertType::DT_WARN_NE : return "WARN_NE"; - case assertType::DT_CHECK_NE : return "CHECK_NE"; - case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; - case assertType::DT_WARN_GT : return "WARN_GT"; - case assertType::DT_CHECK_GT : return "CHECK_GT"; - case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; - case assertType::DT_WARN_LT : return "WARN_LT"; - case assertType::DT_CHECK_LT : return "CHECK_LT"; - case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; - case assertType::DT_WARN_GE : return "WARN_GE"; - case assertType::DT_CHECK_GE : return "CHECK_GE"; - case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; - case assertType::DT_WARN_LE : return "WARN_LE"; - case assertType::DT_CHECK_LE : return "CHECK_LE"; - case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; - - case assertType::DT_WARN_UNARY : return "WARN_UNARY"; - case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; - case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; - case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; - case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; - case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled + #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type + #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type) + switch(at) { + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE); + + default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!"); } DOCTEST_MSVC_SUPPRESS_WARNING_POP - return ""; } // clang-format on @@ -3494,6 +3898,12 @@ const char* skipPathFromFilename(const char* file) { DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +bool SubcaseSignature::operator==(const SubcaseSignature& other) const { + return m_line == other.m_line + && std::strcmp(m_file, other.m_file) == 0 + && m_name == other.m_name; +} + bool SubcaseSignature::operator<(const SubcaseSignature& other) const { if(m_line != other.m_line) return m_line < other.m_line; @@ -3502,45 +3912,53 @@ bool SubcaseSignature::operator<(const SubcaseSignature& other) const { return m_name.compare(other.m_name) < 0; } -IContextScope::IContextScope() = default; -IContextScope::~IContextScope() = default; +DOCTEST_DEFINE_INTERFACE(IContextScope) -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(char* in) { return toString(static_cast<const char*>(in)); } -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(bool in) { return in ? "true" : "false"; } -String toString(float in) { return fpToString(in, 5) + "f"; } -String toString(double in) { return fpToString(in, 10); } -String toString(double long in) { return fpToString(in, 15); } - -#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ - String toString(type in) { \ - char buf[64]; \ - std::sprintf(buf, fmt, in); \ - return buf; \ +namespace detail { + void filldata<const void*>::fill(std::ostream* stream, const void* in) { + if (in) { *stream << in; } + else { *stream << "nullptr"; } } -DOCTEST_TO_STRING_OVERLOAD(char, "%d") -DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") -DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int short, "%d") -DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int, "%d") -DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") -DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") -DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") -DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + template <typename T> + String toStreamLit(T t) { + std::ostream* os = tlssPush(); + os->operator<<(t); + return tlssPop(); + } +} -String toString(std::nullptr_t) { return "NULL"; } +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 String toString(const std::string& in) { return in.c_str(); } #endif // VS 2019 +String toString(String in) { return in; } + +String toString(std::nullptr_t) { return "nullptr"; } + +String toString(bool in) { return in ? "true" : "false"; } + +String toString(float in) { return toStreamLit(in); } +String toString(double in) { return toStreamLit(in); } +String toString(double long in) { return toStreamLit(in); } + +String toString(char in) { return toStreamLit(static_cast<signed>(in)); } +String toString(char signed in) { return toStreamLit(static_cast<signed>(in)); } +String toString(char unsigned in) { return toStreamLit(static_cast<unsigned>(in)); } +String toString(short in) { return toStreamLit(in); } +String toString(short unsigned in) { return toStreamLit(in); } +String toString(signed in) { return toStreamLit(in); } +String toString(unsigned in) { return toStreamLit(in); } +String toString(long in) { return toStreamLit(in); } +String toString(long unsigned in) { return toStreamLit(in); } +String toString(long long in) { return toStreamLit(in); } +String toString(long long unsigned in) { return toStreamLit(in); } + Approx::Approx(double value) : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100) , m_scale(1.0) @@ -3580,11 +3998,25 @@ bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } String toString(const Approx& in) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return String("Approx( ") + doctest::toString(in.m_value) + " )"; + return "Approx( " + doctest::toString(in.m_value) + " )"; } const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738) +template <typename F> +IsNaN<F>::operator bool() const { + return std::isnan(value) ^ flipped; +} +DOCTEST_MSVC_SUPPRESS_WARNING_POP +template struct DOCTEST_INTERFACE_DEF IsNaN<float>; +template struct DOCTEST_INTERFACE_DEF IsNaN<double>; +template struct DOCTEST_INTERFACE_DEF IsNaN<long double>; +template <typename F> +String toString(IsNaN<F> in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; } +String toString(IsNaN<float> in) { return toString<float>(in); } +String toString(IsNaN<double> in) { return toString<double>(in); } +String toString(IsNaN<double long> in) { return toString<double long>(in); } + } // namespace doctest #ifdef DOCTEST_CONFIG_DISABLE @@ -3594,15 +4026,15 @@ Context::~Context() = default; void Context::applyCommandLine(int, const char* const*) {} void Context::addFilter(const char*, const char*) {} void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} void Context::setOption(const char*, int) {} void Context::setOption(const char*, const char*) {} bool Context::shouldExit() { return false; } void Context::setAsDefaultForAssertsOutOfTestCases() {} void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream*) {} int Context::run() { return 0; } -IReporter::~IReporter() = default; - int IReporter::get_num_active_contexts() { return 0; } const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } int IReporter::get_num_stringified_contexts() { return 0; } @@ -3635,7 +4067,7 @@ namespace doctest { namespace { // the int (priority) is part of the key for automatic sorting - sadly one can register a // reporter with a duplicate name and a different priority but hopefully that won't happen often :| - typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap; + using reporterMap = std::map<std::pair<int, String>, reporterCreatorFunc>; reporterMap& getReporters() { static reporterMap data; @@ -3667,8 +4099,8 @@ namespace detail { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_NORETURN void throwException() { g_cs->shouldLogCurrentException = false; - throw TestFailureException(); - } // NOLINT(cert-err60-cpp) + throw TestFailureException(); // NOLINT(hicpp-exception-baseclass) + } #else // DOCTEST_CONFIG_NO_EXCEPTIONS void throwException() {} #endif // DOCTEST_CONFIG_NO_EXCEPTIONS @@ -3714,91 +4146,134 @@ namespace { return !*wild; } - //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html - //unsigned hashStr(unsigned const char* str) { - // unsigned long hash = 5381; - // char c; - // while((c = *str++)) - // hash = ((hash << 5) + hash) + c; // hash * 33 + c - // return hash; - //} - // checks if the name matches any of the filters (and can be configured what to do when empty) bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty, - bool caseSensitive) { - if(filters.empty() && matchEmpty) + bool caseSensitive) { + if (filters.empty() && matchEmpty) return true; - for(auto& curr : filters) - if(wildcmp(name, curr.c_str(), caseSensitive)) + for (auto& curr : filters) + if (wildcmp(name, curr.c_str(), caseSensitive)) return true; return false; } -} // namespace -namespace detail { - Subcase::Subcase(const String& name, const char* file, int line) - : m_signature({name, file, line}) { - auto* s = g_cs; + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(unsigned long long a, unsigned long long b) { + return (a << 5) + b; + } - // check subcase filters - if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { - if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) - return; - if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) - return; - } - - // if a Subcase on the same level has already been entered - if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { - s->should_reenter = true; - return; - } + // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(const char* str) { + unsigned long long hash = 5381; + char c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; + } - // push the current signature to the stack so we can check if the - // current stack + the current new subcase have been traversed - s->subcasesStack.push_back(m_signature); - if(s->subcasesPassed.count(s->subcasesStack) != 0) { - // pop - revert to previous stack since we've already passed this - s->subcasesStack.pop_back(); - return; + unsigned long long hash(const SubcaseSignature& sig) { + return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line); + } + + unsigned long long hash(const std::vector<SubcaseSignature>& sigs, size_t count) { + unsigned long long running = 0; + auto end = sigs.begin() + count; + for (auto it = sigs.begin(); it != end; it++) { + running = hash(running, hash(*it)); } + return running; + } - s->subcasesCurrentMaxLevel = s->subcasesStack.size(); - m_entered = true; + unsigned long long hash(const std::vector<SubcaseSignature>& sigs) { + unsigned long long running = 0; + for (const SubcaseSignature& sig : sigs) { + running = hash(running, hash(sig)); + } + return running; + } +} // namespace +namespace detail { + bool Subcase::checkFilters() { + if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) { + if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive)) + return true; + if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive)) + return true; + } + return false; + } - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + if (!g_cs->reachedLeaf) { + if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size() + || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) { + // Going down. + if (checkFilters()) { return; } + + g_cs->subcaseStack.push_back(m_signature); + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + } else { + if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) { + // This subcase is reentered via control flow. + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth + && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature))) + == g_cs->fullyTraversedSubcases.end()) { + if (checkFilters()) { return; } + // This subcase is part of the one to be executed next. + g_cs->nextSubcaseStack.clear(); + g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(), + g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth); + g_cs->nextSubcaseStack.push_back(m_signature); + } + } } - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") Subcase::~Subcase() { - if(m_entered) { - // only mark the subcase stack as passed if no subcases have been skipped - if(g_cs->should_reenter == false) - g_cs->subcasesPassed.insert(g_cs->subcasesStack); - g_cs->subcasesStack.pop_back(); + if (m_entered) { + g_cs->currentSubcaseDepth--; + + if (!g_cs->reachedLeaf) { + // Leaf. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + g_cs->nextSubcaseStack.clear(); + g_cs->reachedLeaf = true; + } else if (g_cs->nextSubcaseStack.empty()) { + // All children are finished. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + } #if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0 #else if(std::uncaught_exception() #endif - && g_cs->shouldLogCurrentException) { + && g_cs->shouldLogCurrentException) { DOCTEST_ITERATE_THROUGH_REPORTERS( test_case_exception, {"exception thrown in subcase - will translate later " - "when the whole test case has been exited (cannot " - "translate while there is an active exception)", - false}); + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); g_cs->shouldLogCurrentException = false; } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP Subcase::operator bool() const { return m_entered; } @@ -3812,20 +4287,11 @@ namespace detail { TestSuite& TestSuite::operator*(const char* in) { m_test_suite = in; - // clear state - m_description = nullptr; - m_skip = false; - m_no_breaks = false; - m_no_output = false; - m_may_fail = false; - m_should_fail = false; - m_expected_failures = 0; - m_timeout = 0; return *this; } TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type, int template_id) { + const String& type, int template_id) { m_file = file; m_line = line; m_name = nullptr; // will be later overridden in operator* @@ -3850,10 +4316,8 @@ namespace detail { } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice TestCase& TestCase::operator=(const TestCase& other) { - static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other); - + TestCaseData::operator=(other); m_test = other.m_test; m_type = other.m_type; m_template_id = other.m_template_id; @@ -3869,7 +4333,7 @@ namespace detail { m_name = in; // make a new name with an appended type for templated test case if(m_template_id != -1) { - m_full_name = String(m_name) + m_type; + m_full_name = String(m_name) + "<" + m_type + ">"; // redirect the name to point to the newly constructed full name m_name = m_full_name.c_str(); } @@ -3925,29 +4389,6 @@ namespace { return suiteOrderComparator(lhs, rhs); } -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - HANDLE g_stdoutHandle; - WORD g_origFgAttrs; - WORD g_origBgAttrs; - bool g_attrsInitted = false; - - int colors_init() { - if(!g_attrsInitted) { - g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - g_attrsInitted = true; - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); - g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | - BACKGROUND_BLUE | BACKGROUND_INTENSITY); - g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | - FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - return 0; - } - - int dumy_init_console_colors = colors_init(); -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") void color_to_stream(std::ostream& s, Color::Enum code) { static_cast<void>(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS @@ -3981,10 +4422,26 @@ namespace { #ifdef DOCTEST_CONFIG_COLORS_WINDOWS if(g_no_colors || - (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) return; -#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) // clang-format off switch (code) { @@ -4001,7 +4458,7 @@ namespace { case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; case Color::None: case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(g_origFgAttrs); + default: DOCTEST_SET_ATTR(ch.origFgAttrs); } // clang-format on #endif // DOCTEST_CONFIG_COLORS_WINDOWS @@ -4118,35 +4575,22 @@ namespace detail { getExceptionTranslators().push_back(et); } -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, char* in) { *s << in; } - void toStream(std::ostream* s, const char* in) { *s << in; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } - void toStream(std::ostream* s, float in) { *s << in; } - void toStream(std::ostream* s, double in) { *s << in; } - void toStream(std::ostream* s, double long in) { *s << in; } - - void toStream(std::ostream* s, char in) { *s << in; } - void toStream(std::ostream* s, char signed in) { *s << in; } - void toStream(std::ostream* s, char unsigned in) { *s << in; } - void toStream(std::ostream* s, int short in) { *s << in; } - void toStream(std::ostream* s, int short unsigned in) { *s << in; } - void toStream(std::ostream* s, int in) { *s << in; } - void toStream(std::ostream* s, int unsigned in) { *s << in; } - void toStream(std::ostream* s, int long in) { *s << in; } - void toStream(std::ostream* s, int long unsigned in) { *s << in; } - void toStream(std::ostream* s, int long long in) { *s << in; } - void toStream(std::ostream* s, int long long unsigned in) { *s << in; } - DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO() ContextScopeBase::ContextScopeBase() { g_infoContexts.push_back(this); } - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // destroy cannot be inlined into the destructor because that would mean calling stringify after @@ -4165,8 +4609,8 @@ namespace detail { g_infoContexts.pop_back(); } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP } // namespace detail namespace { @@ -4207,10 +4651,10 @@ namespace { static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the // console just once no matter how many threads have crashed. - static std::mutex mutex; + DOCTEST_DECLARE_STATIC_MUTEX(mutex) static bool execute = true; { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) if(execute) { bool reported = false; for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { @@ -4313,7 +4757,7 @@ namespace { static unsigned int prev_abort_behavior; static int prev_report_mode; static _HFILE prev_report_file; - static void (*prev_sigabrt_handler)(int); + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); static std::terminate_handler original_terminate_handler; static bool isSet; static ULONG guaranteeSize; @@ -4325,7 +4769,7 @@ namespace { unsigned int FatalConditionHandler::prev_abort_behavior; int FatalConditionHandler::prev_report_mode; _HFILE FatalConditionHandler::prev_report_file; - void (*FatalConditionHandler::prev_sigabrt_handler)(int); + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); std::terminate_handler FatalConditionHandler::original_terminate_handler; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; @@ -4383,7 +4827,7 @@ namespace { sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = {}; - sa.sa_handler = handleSignal; // NOLINT + sa.sa_handler = handleSignal; sa.sa_flags = SA_ONSTACK; for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); @@ -4422,7 +4866,7 @@ namespace { #define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) #else // TODO: integration with XCode and other IDEs -#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#define DOCTEST_OUTPUT_DEBUG_STRING(text) #endif // Platform void addAssert(assertType::Enum at) { @@ -4441,8 +4885,8 @@ namespace { DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); - while(g_cs->subcasesStack.size()) { - g_cs->subcasesStack.pop_back(); + while (g_cs->subcaseStack.size()) { + g_cs->subcaseStack.pop_back(); DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } @@ -4454,25 +4898,26 @@ namespace { } #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH } // namespace -namespace detail { - ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type, const char* exception_string) { - m_test_case = g_cs->currentTest; - m_at = at; - m_file = file; - m_line = line; - m_expr = expr; - m_failed = true; - m_threw = false; - m_threw_as = false; - m_exception_type = exception_type; - m_exception_string = exception_string; +AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string) + : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr), + m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type), + m_exception_string(exception_string) { #if DOCTEST_MSVC - if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC - ++m_expr; + if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; #endif // MSVC - } +} + +namespace detail { + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const String& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } void ResultBuilder::setResult(const Result& res) { m_decomp = res.m_decomp; @@ -4488,17 +4933,17 @@ namespace detail { if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional m_failed = !m_threw; } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT - m_failed = !m_threw_as || (m_exception != m_exception_string); + m_failed = !m_threw_as || !m_exception_string.check(m_exception); } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional m_failed = !m_threw_as; } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - m_failed = m_exception != m_exception_string; + m_failed = !m_exception_string.check(m_exception); } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional m_failed = m_threw; } if(m_exception.size()) - m_exception = String("\"") + m_exception + "\""; + m_exception = "\"" + m_exception + "\""; if(is_running_in_test) { addAssert(m_at); @@ -4526,8 +4971,8 @@ namespace detail { std::abort(); } - void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, - Result result) { + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + const Result& result) { bool failed = !result.m_passed; // ################################################################################### @@ -4536,21 +4981,29 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); DOCTEST_ASSERT_IN_TESTS(result.m_decomp); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return !failed; } MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { - m_stream = getTlsOss(); + m_stream = tlssPush(); m_file = file; m_line = line; m_severity = severity; } - IExceptionTranslator::IExceptionTranslator() = default; - IExceptionTranslator::~IExceptionTranslator() = default; + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + + DOCTEST_DEFINE_INTERFACE(IExceptionTranslator) bool MessageBuilder::log() { - m_string = getTlsOssResult(); + if (!logged) { + m_string = tlssPop(); + logged = true; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); const bool isWarn = m_severity & assertType::is_warn; @@ -4569,29 +5022,10 @@ namespace detail { if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional throwException(); } - - MessageBuilder::~MessageBuilder() = default; } // namespace detail namespace { using namespace detail; - template <typename Ex> - DOCTEST_NORETURN void throw_exception(Ex const& e) { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw e; -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - std::cerr << "doctest will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - -#ifndef DOCTEST_INTERNAL_ERROR -#define DOCTEST_INTERNAL_ERROR(msg) \ - throw_exception(std::logic_error( \ - __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) -#endif // DOCTEST_INTERNAL_ERROR - // clang-format off // ================================================================================================= @@ -4638,7 +5072,11 @@ namespace { mutable XmlWriter* m_writer = nullptr; }; +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM XmlWriter( std::ostream& os = std::cout ); +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os ); +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM ~XmlWriter(); XmlWriter( XmlWriter const& ) = delete; @@ -4673,10 +5111,10 @@ namespace { void ensureTagClosed(); - private: - void writeDeclaration(); + private: + void newlineIfNecessary(); bool m_tagIsOpen = false; @@ -4865,7 +5303,7 @@ namespace { XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) { - writeDeclaration(); + // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627 } XmlWriter::~XmlWriter() { @@ -4976,8 +5414,8 @@ namespace { struct XmlReporter : public IReporter { - XmlWriter xml; - std::mutex mutex; + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; @@ -5054,7 +5492,8 @@ namespace { xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) .writeAttribute("testsuite", in.data[i]->m_test_suite) .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) - .writeAttribute("line", line(in.data[i]->m_line)); + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); } xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); @@ -5070,6 +5509,8 @@ namespace { } void test_run_start() override { + xml.writeDeclaration(); + // remove .exe extension - mainly to have the same output on UNIX and Windows std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); #ifdef DOCTEST_PLATFORM_WINDOWS @@ -5117,14 +5558,15 @@ namespace { test_case_start_impl(in); xml.ensureTagClosed(); } - + void test_case_reenter(const TestCaseData&) override {} void test_case_end(const CurrentTestCaseStats& st) override { xml.startElement("OverallResultsAsserts") .writeAttribute("successes", st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) - .writeAttribute("failures", st.numAssertsFailedCurrentTest); + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); if(opt.duration) xml.writeAttribute("duration", st.seconds); if(tc->m_expected_failures) @@ -5135,7 +5577,7 @@ namespace { } void test_case_exception(const TestCaseException& e) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.scopedElement("Exception") .writeAttribute("crash", e.is_crash) @@ -5143,8 +5585,6 @@ namespace { } void subcase_start(const SubcaseSignature& in) override { - std::lock_guard<std::mutex> lock(mutex); - xml.startElement("SubCase") .writeAttribute("name", in.m_name) .writeAttribute("filename", skipPathFromFilename(in.m_file)) @@ -5158,7 +5598,7 @@ namespace { if(!rb.m_failed && !opt.success) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Expression") .writeAttribute("success", !rb.m_failed) @@ -5174,7 +5614,7 @@ namespace { if(rb.m_at & assertType::is_throws_as) xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); if(rb.m_at & assertType::is_throws_with) - xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str()); if((rb.m_at & assertType::is_normal) && !rb.m_threw) xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); @@ -5184,7 +5624,7 @@ namespace { } void log_message(const MessageData& mb) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Message") .writeAttribute("type", failureString(mb.m_severity)) @@ -5220,7 +5660,8 @@ namespace { } else if((rb.m_at & assertType::is_throws_as) && (rb.m_at & assertType::is_throws_with)) { //!OCLINT s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + << rb.m_exception_string.c_str() + << "\", " << rb.m_exception_type << " ) " << Color::None; if(rb.m_threw) { if(!rb.m_failed) { s << "threw as expected!\n"; @@ -5241,7 +5682,8 @@ namespace { } else if(rb.m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None + << rb.m_exception_string.c_str() + << "\" ) " << Color::None << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : "threw a DIFFERENT exception: ") : "did NOT throw at all!") @@ -5266,8 +5708,8 @@ namespace { // - more attributes in tags struct JUnitReporter : public IReporter { - XmlWriter xml; - std::mutex mutex; + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) Timer timer; std::vector<String> deepestSubcaseStackNames; @@ -5363,9 +5805,13 @@ namespace { // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= - void report_query(const QueryData&) override {} + void report_query(const QueryData&) override { + xml.writeDeclaration(); + } - void test_run_start() override {} + void test_run_start() override { + xml.writeDeclaration(); + } void test_run_end(const TestRunStats& p) override { // remove .exe extension - mainly to have the same output on UNIX and Windows @@ -5435,12 +5881,11 @@ namespace { } void test_case_exception(const TestCaseException& e) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) testCaseData.addError("exception", e.error_string.c_str()); } void subcase_start(const SubcaseSignature& in) override { - std::lock_guard<std::mutex> lock(mutex); deepestSubcaseStackNames.push_back(in.m_name); } @@ -5450,7 +5895,7 @@ namespace { if(!rb.m_failed) // report only failures & ignore the `success` option return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) std::ostringstream os; os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") @@ -5461,7 +5906,22 @@ namespace { testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); } - void log_message(const MessageData&) override {} + void log_message(const MessageData& mb) override { + if(mb.m_severity & assertType::is_warn) // report only failures + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + os << mb.m_string.c_str() << "\n"; + log_contexts(os); + + testCaseData.addFailure(mb.m_string.c_str(), + mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str()); + } void test_case_skipped(const TestCaseData&) override {} @@ -5501,7 +5961,7 @@ namespace { bool hasLoggedCurrentTestStart; std::vector<SubcaseSignature> subcasesStack; size_t currentSubcaseLevel; - std::mutex mutex; + DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; @@ -5606,9 +6066,11 @@ namespace { } void printIntro() { - printVersion(); - s << Color::Cyan << "[doctest] " << Color::None - << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } } void printHelp() { @@ -5693,12 +6155,18 @@ namespace { << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> " << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool> " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool> " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> " << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> " << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> " << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool> " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> " << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> " @@ -5736,22 +6204,6 @@ namespace { printReporters(getReporters(), "reporters"); } - void list_query_results() { - separator_to_stream(); - if(opt.count || opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= @@ -5797,15 +6249,21 @@ namespace { } } - void test_run_start() override { printIntro(); } + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } void test_run_end(const TestRunStats& p) override { + if(opt.minimal && p.numTestCasesFailed == 0) + return; + separator_to_stream(); s << std::dec; - auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1))); - auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1))); - auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1))); + auto totwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1))); const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | " @@ -5837,7 +6295,7 @@ namespace { subcasesStack.clear(); currentSubcaseLevel = 0; } - + void test_case_reenter(const TestCaseData&) override { subcasesStack.clear(); } @@ -5849,7 +6307,7 @@ namespace { // log the preamble of the test case only if there is something // else to print - something other than that an assert has failed if(opt.duration || - (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure))) logTestStart(); if(opt.duration) @@ -5880,6 +6338,7 @@ namespace { } void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) if(tc->m_no_output) return; @@ -5904,14 +6363,12 @@ namespace { } void subcase_start(const SubcaseSignature& subc) override { - std::lock_guard<std::mutex> lock(mutex); subcasesStack.push_back(subc); ++currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void subcase_end() override { - std::lock_guard<std::mutex> lock(mutex); --currentSubcaseLevel; hasLoggedCurrentTestStart = false; } @@ -5920,7 +6377,7 @@ namespace { if((!rb.m_failed && !opt.success) || tc->m_no_output) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) logTestStart(); @@ -5936,7 +6393,7 @@ namespace { if(tc->m_no_output) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) logTestStart(); @@ -6047,18 +6504,42 @@ namespace { std::vector<String>& res) { String filtersString; if(parseOption(argc, argv, pattern, &filtersString)) { - // tokenize with "," as a separator - // cppcheck-suppress strtokCalled - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string - while(pch != nullptr) { - if(strlen(pch)) - res.push_back(pch); - // uses the strtok() internal state to go to the next token - // cppcheck-suppress strtokCalled - pch = std::strtok(nullptr, ","); + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',' || character == '\\') { + s.put(character); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } + } + + if(seenBackslash) { + s.put('\\'); } - DOCTEST_CLANG_SUPPRESS_WARNING_POP + flush(); return true; } return false; @@ -6077,30 +6558,30 @@ namespace { if(!parseOption(argc, argv, pattern, &parsedValue)) return false; - if(type == 0) { + if(type) { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); + if (theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } else { // boolean - const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 - const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1 + const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1 // if the value matches any of the positive/negative possibilities - for(unsigned i = 0; i < 4; i++) { - if(parsedValue.compare(positive[i], true) == 0) { + for (unsigned i = 0; i < 4; i++) { + if (parsedValue.compare(positive[i], true) == 0) { res = 1; //!OCLINT parameter reassignment return true; } - if(parsedValue.compare(negative[i], true) == 0) { + if (parsedValue.compare(negative[i], true) == 0) { res = 0; //!OCLINT parameter reassignment return true; } } - } else { - // integer - // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... - int theInt = std::atoi(parsedValue.c_str()); // NOLINT - if(theInt != 0) { - res = theInt; //!OCLINT parameter reassignment - return true; - } } return false; } @@ -6191,9 +6672,12 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); @@ -6257,10 +6741,14 @@ void Context::clearFilters() { curr.clear(); } -// allows the user to override procedurally the int/bool options from the command line +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line void Context::setOption(const char* option, int value) { setOption(option, toString(value).c_str()); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } // allows the user to override procedurally the string options from the command line @@ -6277,6 +6765,31 @@ void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + // the main function that does all the filtering and test running int Context::run() { using namespace detail; @@ -6290,15 +6803,22 @@ int Context::run() { g_no_colors = p->no_colors; p->resetRunData(); - // stdout by default - p->cout = &std::cout; - p->cerr = &std::cerr; - - // or to a file if specified std::fstream fstr; - if(p->out.size()) { - fstr.open(p->out.c_str(), std::fstream::out); - p->cout = &fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + // stdout by default + p->cout = &std::cout; +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + return EXIT_FAILURE; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + } } FatalConditionHandler::allocateAltStackMem(); @@ -6370,7 +6890,7 @@ int Context::run() { // random_shuffle implementation const auto first = &testArray[0]; for(size_t i = testArray.size() - 1; i > 0; --i) { - int idxToSwap = std::rand() % (i + 1); // NOLINT + int idxToSwap = std::rand() % (i + 1); const auto temp = first[i]; @@ -6457,19 +6977,20 @@ int Context::run() { p->numAssertsFailedCurrentTest_atomic = 0; p->numAssertsCurrentTest_atomic = 0; - p->subcasesPassed.clear(); + p->fullyTraversedSubcases.clear(); DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); p->timer.start(); - + bool run_test = true; do { // reset some of the fields for subcases (except for the set of fully passed ones) - p->should_reenter = false; - p->subcasesCurrentMaxLevel = 0; - p->subcasesStack.clear(); + p->reachedLeaf = false; + // May not be empty if previous subcase exited via exception. + p->subcaseStack.clear(); + p->currentSubcaseDepth = 0; p->shouldLogCurrentException = true; @@ -6502,10 +7023,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP run_test = false; p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; } - - if(p->should_reenter && run_test) + + if(!p->nextSubcaseStack.empty() && run_test) DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); - if(!p->should_reenter) + if(p->nextSubcaseStack.empty()) run_test = false; } while(run_test); @@ -6531,17 +7052,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); } - // see these issues on the reasoning for this: - // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 - // - https://github.com/onqtam/doctest/issues/126 - auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE - { std::cout << std::string(); }; - DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); - return cleanup_and_return(); } -IReporter::~IReporter() = default; +DOCTEST_DEFINE_INTERFACE(IReporter) int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } const IContextScope* const* IReporter::get_active_contexts() { @@ -6576,5 +7090,17 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_IMPLEMENTATION #endif // DOCTEST_CONFIG_IMPLEMENT + +#ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN + +#ifdef DOCTEST_UNDEF_NOMINMAX +#undef NOMINMAX +#undef DOCTEST_UNDEF_NOMINMAX +#endif // DOCTEST_UNDEF_NOMINMAX diff --git a/contrib/doctest/doctest/parts/doctest.cpp b/contrib/doctest/doctest/parts/doctest.cpp index 5b7442086..5a2dd4599 100644 --- a/contrib/doctest/doctest/parts/doctest.cpp +++ b/contrib/doctest/doctest/parts/doctest.cpp @@ -11,13 +11,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") @@ -25,65 +23,35 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C -DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN @@ -91,7 +59,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <ctime> #include <cmath> #include <climits> -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 #ifdef __BORLANDC__ #include <math.h> #endif // __BORLANDC__ @@ -103,20 +71,33 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <utility> #include <fstream> #include <sstream> +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include <iostream> +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include <algorithm> #include <iomanip> #include <vector> +#ifndef DOCTEST_CONFIG_NO_MULTITHREADING #include <atomic> #include <mutex> +#define DOCTEST_DECLARE_MUTEX(name) std::mutex name; +#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name); +#else // DOCTEST_CONFIG_NO_MULTITHREADING +#define DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_DECLARE_STATIC_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) +#endif // DOCTEST_CONFIG_NO_MULTITHREADING #include <set> #include <map> +#include <unordered_set> #include <exception> #include <stdexcept> #include <csignal> #include <cfloat> #include <cctype> #include <cstdint> +#include <string> #ifdef DOCTEST_PLATFORM_MAC #include <sys/types.h> @@ -129,9 +110,11 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN // defines for a leaner windows.h #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX +#define DOCTEST_UNDEF_NOMINMAX #endif // NOMINMAX // not sure what AfxWin.h is for - here I do what Catch does @@ -149,7 +132,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #endif // DOCTEST_PLATFORM_WINDOWS -// this is a fix for https://github.com/onqtam/doctest/issues/348 +// this is a fix for https://github.com/doctest/doctest/issues/348 // https://mail.gnome.org/archives/xml/2012-January/msg00000.html #if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) #define STDOUT_FILENO fileno(stdout) @@ -171,8 +154,12 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #endif #ifndef DOCTEST_THREAD_LOCAL +#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC #define DOCTEST_THREAD_LOCAL thread_local -#endif +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL #ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES #define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 @@ -192,12 +179,40 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS #endif +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + namespace doctest { bool is_running_in_test = false; namespace { using namespace detail; + + template <typename Ex> + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS +#ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION + DOCTEST_CONFIG_HANDLE_EXCEPTION(e); +#else // DOCTEST_CONFIG_HANDLE_EXCEPTION +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#endif // DOCTEST_CONFIG_HANDLE_EXCEPTION + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + // case insensitive strcmp int stricmp(const char* a, const char* b) { for(;; a++, b++) { @@ -207,20 +222,6 @@ namespace { } } - template <typename T> - String fpToString(T value, int precision) { - std::ostringstream oss; - oss << std::setprecision(precision) << std::fixed << value; - std::string d = oss.str(); - size_t i = d.find_last_not_of('0'); - if(i != std::string::npos && i != d.size() - 1) { - if(d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d.c_str(); - } - struct Endianness { enum Arch @@ -241,58 +242,56 @@ namespace { } // namespace namespace detail { - void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + DOCTEST_THREAD_LOCAL class + { + std::vector<std::streampos> stack; + std::stringstream ss; - String rawMemoryToString(const void* object, unsigned size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast<int>(size), inc = 1; - if(Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; } - unsigned const char* bytes = static_cast<unsigned const char*>(object); - std::ostringstream oss; - oss << "0x" << std::setfill('0') << std::hex; - for(; i != end; i += inc) - oss << std::setw(2) << static_cast<unsigned>(bytes[i]); - return oss.str().c_str(); - } + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); - DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast<unsigned>(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; - std::ostream* getTlsOss() { - g_oss.clear(); // there shouldn't be anything worth clearing in the flags - g_oss.str(""); // the slow way of resetting a string stream - //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 - return &g_oss; + std::ostream* tlssPush() { + return g_oss.push(); } - String getTlsOssResult() { - //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 - return g_oss.str().c_str(); + String tlssPop() { + return g_oss.pop(); } #ifndef DOCTEST_CONFIG_DISABLE namespace timer_large_integer { - + #if defined(DOCTEST_PLATFORM_WINDOWS) - typedef ULONGLONG type; + using type = ULONGLONG; #else // DOCTEST_PLATFORM_WINDOWS - using namespace std; - typedef uint64_t type; + using type = std::uint64_t; #endif // DOCTEST_PLATFORM_WINDOWS } -typedef timer_large_integer::type ticks_t; +using ticks_t = timer_large_integer::type; #ifdef DOCTEST_CONFIG_GETCURRENTTICKS ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } #elif defined(DOCTEST_PLATFORM_WINDOWS) ticks_t getCurrentTicks() { - static LARGE_INTEGER hz = {0}, hzo = {0}; + static LARGE_INTEGER hz = { {0} }, hzo = { {0} }; if(!hz.QuadPart) { QueryPerformanceFrequency(&hz); QueryPerformanceCounter(&hzo); @@ -324,9 +323,17 @@ typedef timer_large_integer::type ticks_t; ticks_t m_ticks = 0; }; -#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#ifdef DOCTEST_CONFIG_NO_MULTITHREADING + template <typename T> + using Atomic = T; +#else // DOCTEST_CONFIG_NO_MULTITHREADING + template <typename T> + using Atomic = std::atomic<T>; +#endif // DOCTEST_CONFIG_NO_MULTITHREADING + +#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING) template <typename T> - using AtomicOrMultiLaneAtomic = std::atomic<T>; + using MultiLaneAtomic = Atomic<T>; #else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // Provides a multilane implementation of an atomic variable that supports add, sub, load, // store. Instead of using a single atomic variable, this splits up into multiple ones, @@ -343,8 +350,8 @@ typedef timer_large_integer::type ticks_t; { struct CacheLineAlignedAtomic { - std::atomic<T> atomic{}; - char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)]; + Atomic<T> atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic<T>)]; }; CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; @@ -374,7 +381,7 @@ typedef timer_large_integer::type ticks_t; return result; } - T operator=(T desired) DOCTEST_NOEXCEPT { + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] store(desired); return desired; } @@ -389,7 +396,7 @@ typedef timer_large_integer::type ticks_t; private: // Each thread has a different atomic that it operates on. If more than NumLanes threads - // use this, some will use the same atomic. So performance will degrate a bit, but still + // use this, some will use the same atomic. So performance will degrade a bit, but still // everything will work. // // The logic here is a bit tricky. The call should be as fast as possible, so that there @@ -400,24 +407,21 @@ typedef timer_large_integer::type ticks_t; // assigned in a round-robin fashion. // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with // little overhead. - std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT { - static std::atomic<size_t> laneCounter; + Atomic<T>& myAtomic() DOCTEST_NOEXCEPT { + static Atomic<size_t> laneCounter; DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; return m_atomics[tlsLaneIdx].atomic; } }; - - template <typename T> - using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>; #endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // this holds both parameters from the command line and runtime data for tests struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats { - AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic; - AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic; + MultiLaneAtomic<int> numAssertsCurrentTest_atomic; + MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic; std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters @@ -430,11 +434,12 @@ typedef timer_large_integer::type ticks_t; std::vector<String> stringifiedContexts; // logging from INFO() due to an exception // stuff for subcases - std::vector<SubcaseSignature> subcasesStack; - std::set<decltype(subcasesStack)> subcasesPassed; - int subcasesCurrentMaxLevel; - bool should_reenter; - std::atomic<bool> shouldLogCurrentException; + bool reachedLeaf; + std::vector<SubcaseSignature> subcaseStack; + std::vector<SubcaseSignature> nextSubcaseStack; + std::unordered_set<unsigned long long> fullyTraversedSubcases; + size_t currentSubcaseDepth; + Atomic<bool> shouldLogCurrentException; void resetRunData() { numTestCases = 0; @@ -484,7 +489,8 @@ typedef timer_large_integer::type ticks_t; (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); // if any subcase has failed - the whole test case has failed - if(failure_flags && !ok_to_fail) + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) numTestCasesFailed++; } }; @@ -499,23 +505,37 @@ typedef timer_large_integer::type ticks_t; #endif // DOCTEST_CONFIG_DISABLE } // namespace detail -void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } -void String::setLast(unsigned in) { buf[last] = char(in); } +char* String::allocate(size_type sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + +void String::setOnHeap() noexcept { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } +void String::setLast(size_type in) noexcept { buf[last] = char(in); } +void String::setSize(size_type sz) noexcept { + if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); } + else { data.ptr[sz] = '\0'; data.size = sz; } +} void String::copy(const String& other) { - using namespace std; if(other.isOnStack()) { memcpy(buf, other.buf, len); } else { - setOnHeap(); - data.size = other.data.size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, other.data.ptr, data.size + 1); + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); } } -String::String() { +String::String() noexcept { buf[0] = '\0'; setLast(); } @@ -523,26 +543,17 @@ String::String() { String::~String() { if(!isOnStack()) delete[] data.ptr; - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) String::String(const char* in) : String(in, strlen(in)) {} -String::String(const char* in, unsigned in_size) { - using namespace std; - if(in_size <= last) { - memcpy(buf, in, in_size); - buf[in_size] = '\0'; - setLast(last - in_size); - } else { - setOnHeap(); - data.size = in_size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, in, in_size); - data.ptr[in_size] = '\0'; - } +String::String(const char* in, size_type in_size) { + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, size_type in_size) { + in.read(allocate(in_size), in_size); } String::String(const String& other) { copy(other); } @@ -559,10 +570,9 @@ String& String::operator=(const String& other) { } String& String::operator+=(const String& other) { - const unsigned my_old_size = size(); - const unsigned other_size = other.size(); - const unsigned total_size = my_old_size + other_size; - using namespace std; + const size_type my_old_size = size(); + const size_type other_size = other.size(); + const size_type total_size = my_old_size + other_size; if(isOnStack()) { if(total_size < len) { // append to the current stack space @@ -609,18 +619,13 @@ String& String::operator+=(const String& other) { return *this; } -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) -String String::operator+(const String& other) const { return String(*this) += other; } - -String::String(String&& other) { - using namespace std; +String::String(String&& other) noexcept { memcpy(buf, other.buf, len); other.buf[0] = '\0'; other.setLast(); } -String& String::operator=(String&& other) { - using namespace std; +String& String::operator=(String&& other) noexcept { if(this != &other) { if(!isOnStack()) delete[] data.ptr; @@ -631,30 +636,60 @@ String& String::operator=(String&& other) { return *this; } -char String::operator[](unsigned i) const { - return const_cast<String*>(this)->operator[](i); // NOLINT +char String::operator[](size_type i) const { + return const_cast<String*>(this)->operator[](i); } -char& String::operator[](unsigned i) { +char& String::operator[](size_type i) { if(isOnStack()) return reinterpret_cast<char*>(buf)[i]; return data.ptr[i]; } DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") -unsigned String::size() const { +String::size_type String::size() const { if(isOnStack()) - return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32 return data.size; } DOCTEST_GCC_SUPPRESS_WARNING_POP -unsigned String::capacity() const { +String::size_type String::capacity() const { if(isOnStack()) return len; return data.capacity; } +String String::substr(size_type pos, size_type cnt) && { + cnt = std::min(cnt, size() - 1 - pos); + char* cptr = c_str(); + memmove(cptr, cptr + pos, cnt); + setSize(cnt); + return std::move(*this); +} + +String String::substr(size_type pos, size_type cnt) const & { + cnt = std::min(cnt, size() - 1 - pos); + return String{ c_str() + pos, cnt }; +} + +String::size_type String::find(char ch, size_type pos) const { + const char* begin = c_str(); + const char* end = begin + size(); + const char* it = begin + pos; + for (; it < end && *it != ch; it++); + if (it < end) { return static_cast<size_type>(it - begin); } + else { return npos; } +} + +String::size_type String::rfind(char ch, size_type pos) const { + const char* begin = c_str(); + const char* it = begin + std::min(pos, size() - 1); + for (; it >= begin && *it != ch; it--); + if (it >= begin) { return static_cast<size_type>(it - begin); } + else { return npos; } +} + int String::compare(const char* other, bool no_case) const { if(no_case) return doctest::stricmp(c_str(), other); @@ -665,17 +700,32 @@ int String::compare(const String& other, bool no_case) const { return compare(other.c_str(), no_case); } -// clang-format off +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } -// clang-format on std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } +Contains::Contains(const String& str) : string(str) { } + +bool Contains::checkWith(const String& other) const { + return strstr(other.c_str(), string.c_str()) != nullptr; +} + +String toString(const Contains& in) { + return "Contains( " + in.string + " )"; +} + +bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); } +bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); } +bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); } +bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); } + namespace { void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) } // namespace @@ -689,64 +739,42 @@ namespace Color { // clang-format off const char* assertString(assertType::Enum at) { - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled - switch(at) { //!OCLINT missing default in switch statements - case assertType::DT_WARN : return "WARN"; - case assertType::DT_CHECK : return "CHECK"; - case assertType::DT_REQUIRE : return "REQUIRE"; - - case assertType::DT_WARN_FALSE : return "WARN_FALSE"; - case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; - case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; - - case assertType::DT_WARN_THROWS : return "WARN_THROWS"; - case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; - case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; - - case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; - case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; - case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; - - case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; - case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; - case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; - - case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; - case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; - case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; - - case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; - case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; - case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; - - case assertType::DT_WARN_EQ : return "WARN_EQ"; - case assertType::DT_CHECK_EQ : return "CHECK_EQ"; - case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; - case assertType::DT_WARN_NE : return "WARN_NE"; - case assertType::DT_CHECK_NE : return "CHECK_NE"; - case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; - case assertType::DT_WARN_GT : return "WARN_GT"; - case assertType::DT_CHECK_GT : return "CHECK_GT"; - case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; - case assertType::DT_WARN_LT : return "WARN_LT"; - case assertType::DT_CHECK_LT : return "CHECK_LT"; - case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; - case assertType::DT_WARN_GE : return "WARN_GE"; - case assertType::DT_CHECK_GE : return "CHECK_GE"; - case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; - case assertType::DT_WARN_LE : return "WARN_LE"; - case assertType::DT_CHECK_LE : return "CHECK_LE"; - case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; - - case assertType::DT_WARN_UNARY : return "WARN_UNARY"; - case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; - case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; - case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; - case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; - case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled + #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type + #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type) + switch(at) { + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE); + + default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!"); } DOCTEST_MSVC_SUPPRESS_WARNING_POP - return ""; } // clang-format on @@ -780,6 +808,12 @@ const char* skipPathFromFilename(const char* file) { DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +bool SubcaseSignature::operator==(const SubcaseSignature& other) const { + return m_line == other.m_line + && std::strcmp(m_file, other.m_file) == 0 + && m_name == other.m_name; +} + bool SubcaseSignature::operator<(const SubcaseSignature& other) const { if(m_line != other.m_line) return m_line < other.m_line; @@ -788,45 +822,53 @@ bool SubcaseSignature::operator<(const SubcaseSignature& other) const { return m_name.compare(other.m_name) < 0; } -IContextScope::IContextScope() = default; -IContextScope::~IContextScope() = default; +DOCTEST_DEFINE_INTERFACE(IContextScope) + +namespace detail { + void filldata<const void*>::fill(std::ostream* stream, const void* in) { + if (in) { *stream << in; } + else { *stream << "nullptr"; } + } + + template <typename T> + String toStreamLit(T t) { + std::ostream* os = tlssPush(); + os->operator<<(t); + return tlssPop(); + } +} #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(char* in) { return toString(static_cast<const char*>(in)); } -// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(bool in) { return in ? "true" : "false"; } -String toString(float in) { return fpToString(in, 5) + "f"; } -String toString(double in) { return fpToString(in, 10); } -String toString(double long in) { return fpToString(in, 15); } - -#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ - String toString(type in) { \ - char buf[64]; \ - std::sprintf(buf, fmt, in); \ - return buf; \ - } - -DOCTEST_TO_STRING_OVERLOAD(char, "%d") -DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") -DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int short, "%d") -DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int, "%d") -DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") -DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") -DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") -DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") - -String toString(std::nullptr_t) { return "NULL"; } #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 String toString(const std::string& in) { return in.c_str(); } #endif // VS 2019 +String toString(String in) { return in; } + +String toString(std::nullptr_t) { return "nullptr"; } + +String toString(bool in) { return in ? "true" : "false"; } + +String toString(float in) { return toStreamLit(in); } +String toString(double in) { return toStreamLit(in); } +String toString(double long in) { return toStreamLit(in); } + +String toString(char in) { return toStreamLit(static_cast<signed>(in)); } +String toString(char signed in) { return toStreamLit(static_cast<signed>(in)); } +String toString(char unsigned in) { return toStreamLit(static_cast<unsigned>(in)); } +String toString(short in) { return toStreamLit(in); } +String toString(short unsigned in) { return toStreamLit(in); } +String toString(signed in) { return toStreamLit(in); } +String toString(unsigned in) { return toStreamLit(in); } +String toString(long in) { return toStreamLit(in); } +String toString(long unsigned in) { return toStreamLit(in); } +String toString(long long in) { return toStreamLit(in); } +String toString(long long unsigned in) { return toStreamLit(in); } + Approx::Approx(double value) : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100) , m_scale(1.0) @@ -866,11 +908,25 @@ bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } String toString(const Approx& in) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return String("Approx( ") + doctest::toString(in.m_value) + " )"; + return "Approx( " + doctest::toString(in.m_value) + " )"; } const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738) +template <typename F> +IsNaN<F>::operator bool() const { + return std::isnan(value) ^ flipped; +} +DOCTEST_MSVC_SUPPRESS_WARNING_POP +template struct DOCTEST_INTERFACE_DEF IsNaN<float>; +template struct DOCTEST_INTERFACE_DEF IsNaN<double>; +template struct DOCTEST_INTERFACE_DEF IsNaN<long double>; +template <typename F> +String toString(IsNaN<F> in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; } +String toString(IsNaN<float> in) { return toString<float>(in); } +String toString(IsNaN<double> in) { return toString<double>(in); } +String toString(IsNaN<double long> in) { return toString<double long>(in); } + } // namespace doctest #ifdef DOCTEST_CONFIG_DISABLE @@ -880,15 +936,15 @@ Context::~Context() = default; void Context::applyCommandLine(int, const char* const*) {} void Context::addFilter(const char*, const char*) {} void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} void Context::setOption(const char*, int) {} void Context::setOption(const char*, const char*) {} bool Context::shouldExit() { return false; } void Context::setAsDefaultForAssertsOutOfTestCases() {} void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream*) {} int Context::run() { return 0; } -IReporter::~IReporter() = default; - int IReporter::get_num_active_contexts() { return 0; } const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } int IReporter::get_num_stringified_contexts() { return 0; } @@ -921,7 +977,7 @@ namespace doctest { namespace { // the int (priority) is part of the key for automatic sorting - sadly one can register a // reporter with a duplicate name and a different priority but hopefully that won't happen often :| - typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap; + using reporterMap = std::map<std::pair<int, String>, reporterCreatorFunc>; reporterMap& getReporters() { static reporterMap data; @@ -953,8 +1009,8 @@ namespace detail { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_NORETURN void throwException() { g_cs->shouldLogCurrentException = false; - throw TestFailureException(); - } // NOLINT(cert-err60-cpp) + throw TestFailureException(); // NOLINT(hicpp-exception-baseclass) + } #else // DOCTEST_CONFIG_NO_EXCEPTIONS void throwException() {} #endif // DOCTEST_CONFIG_NO_EXCEPTIONS @@ -1000,91 +1056,134 @@ namespace { return !*wild; } - //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html - //unsigned hashStr(unsigned const char* str) { - // unsigned long hash = 5381; - // char c; - // while((c = *str++)) - // hash = ((hash << 5) + hash) + c; // hash * 33 + c - // return hash; - //} - // checks if the name matches any of the filters (and can be configured what to do when empty) bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty, - bool caseSensitive) { - if(filters.empty() && matchEmpty) + bool caseSensitive) { + if (filters.empty() && matchEmpty) return true; - for(auto& curr : filters) - if(wildcmp(name, curr.c_str(), caseSensitive)) + for (auto& curr : filters) + if (wildcmp(name, curr.c_str(), caseSensitive)) return true; return false; } -} // namespace -namespace detail { - Subcase::Subcase(const String& name, const char* file, int line) - : m_signature({name, file, line}) { - auto* s = g_cs; + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(unsigned long long a, unsigned long long b) { + return (a << 5) + b; + } - // check subcase filters - if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { - if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) - return; - if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) - return; - } - - // if a Subcase on the same level has already been entered - if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { - s->should_reenter = true; - return; - } + // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(const char* str) { + unsigned long long hash = 5381; + char c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; + } - // push the current signature to the stack so we can check if the - // current stack + the current new subcase have been traversed - s->subcasesStack.push_back(m_signature); - if(s->subcasesPassed.count(s->subcasesStack) != 0) { - // pop - revert to previous stack since we've already passed this - s->subcasesStack.pop_back(); - return; + unsigned long long hash(const SubcaseSignature& sig) { + return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line); + } + + unsigned long long hash(const std::vector<SubcaseSignature>& sigs, size_t count) { + unsigned long long running = 0; + auto end = sigs.begin() + count; + for (auto it = sigs.begin(); it != end; it++) { + running = hash(running, hash(*it)); } + return running; + } - s->subcasesCurrentMaxLevel = s->subcasesStack.size(); - m_entered = true; + unsigned long long hash(const std::vector<SubcaseSignature>& sigs) { + unsigned long long running = 0; + for (const SubcaseSignature& sig : sigs) { + running = hash(running, hash(sig)); + } + return running; + } +} // namespace +namespace detail { + bool Subcase::checkFilters() { + if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) { + if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive)) + return true; + if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive)) + return true; + } + return false; + } - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + if (!g_cs->reachedLeaf) { + if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size() + || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) { + // Going down. + if (checkFilters()) { return; } + + g_cs->subcaseStack.push_back(m_signature); + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + } else { + if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) { + // This subcase is reentered via control flow. + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth + && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature))) + == g_cs->fullyTraversedSubcases.end()) { + if (checkFilters()) { return; } + // This subcase is part of the one to be executed next. + g_cs->nextSubcaseStack.clear(); + g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(), + g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth); + g_cs->nextSubcaseStack.push_back(m_signature); + } + } } - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") Subcase::~Subcase() { - if(m_entered) { - // only mark the subcase stack as passed if no subcases have been skipped - if(g_cs->should_reenter == false) - g_cs->subcasesPassed.insert(g_cs->subcasesStack); - g_cs->subcasesStack.pop_back(); + if (m_entered) { + g_cs->currentSubcaseDepth--; + + if (!g_cs->reachedLeaf) { + // Leaf. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + g_cs->nextSubcaseStack.clear(); + g_cs->reachedLeaf = true; + } else if (g_cs->nextSubcaseStack.empty()) { + // All children are finished. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + } #if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0 #else if(std::uncaught_exception() #endif - && g_cs->shouldLogCurrentException) { + && g_cs->shouldLogCurrentException) { DOCTEST_ITERATE_THROUGH_REPORTERS( test_case_exception, {"exception thrown in subcase - will translate later " - "when the whole test case has been exited (cannot " - "translate while there is an active exception)", - false}); + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); g_cs->shouldLogCurrentException = false; } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP Subcase::operator bool() const { return m_entered; } @@ -1098,20 +1197,11 @@ namespace detail { TestSuite& TestSuite::operator*(const char* in) { m_test_suite = in; - // clear state - m_description = nullptr; - m_skip = false; - m_no_breaks = false; - m_no_output = false; - m_may_fail = false; - m_should_fail = false; - m_expected_failures = 0; - m_timeout = 0; return *this; } TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type, int template_id) { + const String& type, int template_id) { m_file = file; m_line = line; m_name = nullptr; // will be later overridden in operator* @@ -1136,10 +1226,8 @@ namespace detail { } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice TestCase& TestCase::operator=(const TestCase& other) { - static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other); - + TestCaseData::operator=(other); m_test = other.m_test; m_type = other.m_type; m_template_id = other.m_template_id; @@ -1155,7 +1243,7 @@ namespace detail { m_name = in; // make a new name with an appended type for templated test case if(m_template_id != -1) { - m_full_name = String(m_name) + m_type; + m_full_name = String(m_name) + "<" + m_type + ">"; // redirect the name to point to the newly constructed full name m_name = m_full_name.c_str(); } @@ -1211,29 +1299,6 @@ namespace { return suiteOrderComparator(lhs, rhs); } -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - HANDLE g_stdoutHandle; - WORD g_origFgAttrs; - WORD g_origBgAttrs; - bool g_attrsInitted = false; - - int colors_init() { - if(!g_attrsInitted) { - g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - g_attrsInitted = true; - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); - g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | - BACKGROUND_BLUE | BACKGROUND_INTENSITY); - g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | - FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - return 0; - } - - int dumy_init_console_colors = colors_init(); -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") void color_to_stream(std::ostream& s, Color::Enum code) { static_cast<void>(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS @@ -1267,10 +1332,26 @@ namespace { #ifdef DOCTEST_CONFIG_COLORS_WINDOWS if(g_no_colors || - (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) return; -#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) // clang-format off switch (code) { @@ -1287,7 +1368,7 @@ namespace { case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; case Color::None: case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(g_origFgAttrs); + default: DOCTEST_SET_ATTR(ch.origFgAttrs); } // clang-format on #endif // DOCTEST_CONFIG_COLORS_WINDOWS @@ -1404,35 +1485,22 @@ namespace detail { getExceptionTranslators().push_back(et); } -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, char* in) { *s << in; } - void toStream(std::ostream* s, const char* in) { *s << in; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } - void toStream(std::ostream* s, float in) { *s << in; } - void toStream(std::ostream* s, double in) { *s << in; } - void toStream(std::ostream* s, double long in) { *s << in; } - - void toStream(std::ostream* s, char in) { *s << in; } - void toStream(std::ostream* s, char signed in) { *s << in; } - void toStream(std::ostream* s, char unsigned in) { *s << in; } - void toStream(std::ostream* s, int short in) { *s << in; } - void toStream(std::ostream* s, int short unsigned in) { *s << in; } - void toStream(std::ostream* s, int in) { *s << in; } - void toStream(std::ostream* s, int unsigned in) { *s << in; } - void toStream(std::ostream* s, int long in) { *s << in; } - void toStream(std::ostream* s, int long unsigned in) { *s << in; } - void toStream(std::ostream* s, int long long in) { *s << in; } - void toStream(std::ostream* s, int long long unsigned in) { *s << in; } - DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO() ContextScopeBase::ContextScopeBase() { g_infoContexts.push_back(this); } - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // destroy cannot be inlined into the destructor because that would mean calling stringify after @@ -1451,8 +1519,8 @@ namespace detail { g_infoContexts.pop_back(); } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP } // namespace detail namespace { @@ -1493,10 +1561,10 @@ namespace { static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the // console just once no matter how many threads have crashed. - static std::mutex mutex; + DOCTEST_DECLARE_STATIC_MUTEX(mutex) static bool execute = true; { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) if(execute) { bool reported = false; for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { @@ -1599,7 +1667,7 @@ namespace { static unsigned int prev_abort_behavior; static int prev_report_mode; static _HFILE prev_report_file; - static void (*prev_sigabrt_handler)(int); + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); static std::terminate_handler original_terminate_handler; static bool isSet; static ULONG guaranteeSize; @@ -1611,7 +1679,7 @@ namespace { unsigned int FatalConditionHandler::prev_abort_behavior; int FatalConditionHandler::prev_report_mode; _HFILE FatalConditionHandler::prev_report_file; - void (*FatalConditionHandler::prev_sigabrt_handler)(int); + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); std::terminate_handler FatalConditionHandler::original_terminate_handler; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; @@ -1669,7 +1737,7 @@ namespace { sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = {}; - sa.sa_handler = handleSignal; // NOLINT + sa.sa_handler = handleSignal; sa.sa_flags = SA_ONSTACK; for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); @@ -1708,7 +1776,7 @@ namespace { #define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) #else // TODO: integration with XCode and other IDEs -#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#define DOCTEST_OUTPUT_DEBUG_STRING(text) #endif // Platform void addAssert(assertType::Enum at) { @@ -1727,8 +1795,8 @@ namespace { DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); - while(g_cs->subcasesStack.size()) { - g_cs->subcasesStack.pop_back(); + while (g_cs->subcaseStack.size()) { + g_cs->subcaseStack.pop_back(); DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } @@ -1740,25 +1808,26 @@ namespace { } #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH } // namespace -namespace detail { - ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type, const char* exception_string) { - m_test_case = g_cs->currentTest; - m_at = at; - m_file = file; - m_line = line; - m_expr = expr; - m_failed = true; - m_threw = false; - m_threw_as = false; - m_exception_type = exception_type; - m_exception_string = exception_string; +AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string) + : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr), + m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type), + m_exception_string(exception_string) { #if DOCTEST_MSVC - if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC - ++m_expr; + if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; #endif // MSVC - } +} + +namespace detail { + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const String& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } void ResultBuilder::setResult(const Result& res) { m_decomp = res.m_decomp; @@ -1774,17 +1843,17 @@ namespace detail { if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional m_failed = !m_threw; } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT - m_failed = !m_threw_as || (m_exception != m_exception_string); + m_failed = !m_threw_as || !m_exception_string.check(m_exception); } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional m_failed = !m_threw_as; } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - m_failed = m_exception != m_exception_string; + m_failed = !m_exception_string.check(m_exception); } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional m_failed = m_threw; } if(m_exception.size()) - m_exception = String("\"") + m_exception + "\""; + m_exception = "\"" + m_exception + "\""; if(is_running_in_test) { addAssert(m_at); @@ -1812,8 +1881,8 @@ namespace detail { std::abort(); } - void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, - Result result) { + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + const Result& result) { bool failed = !result.m_passed; // ################################################################################### @@ -1822,21 +1891,29 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); DOCTEST_ASSERT_IN_TESTS(result.m_decomp); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return !failed; } MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { - m_stream = getTlsOss(); + m_stream = tlssPush(); m_file = file; m_line = line; m_severity = severity; } - IExceptionTranslator::IExceptionTranslator() = default; - IExceptionTranslator::~IExceptionTranslator() = default; + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + + DOCTEST_DEFINE_INTERFACE(IExceptionTranslator) bool MessageBuilder::log() { - m_string = getTlsOssResult(); + if (!logged) { + m_string = tlssPop(); + logged = true; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); const bool isWarn = m_severity & assertType::is_warn; @@ -1855,29 +1932,10 @@ namespace detail { if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional throwException(); } - - MessageBuilder::~MessageBuilder() = default; } // namespace detail namespace { using namespace detail; - template <typename Ex> - DOCTEST_NORETURN void throw_exception(Ex const& e) { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw e; -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - std::cerr << "doctest will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - -#ifndef DOCTEST_INTERNAL_ERROR -#define DOCTEST_INTERNAL_ERROR(msg) \ - throw_exception(std::logic_error( \ - __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) -#endif // DOCTEST_INTERNAL_ERROR - // clang-format off // ================================================================================================= @@ -1924,7 +1982,11 @@ namespace { mutable XmlWriter* m_writer = nullptr; }; +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM XmlWriter( std::ostream& os = std::cout ); +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os ); +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM ~XmlWriter(); XmlWriter( XmlWriter const& ) = delete; @@ -1959,10 +2021,10 @@ namespace { void ensureTagClosed(); - private: - void writeDeclaration(); + private: + void newlineIfNecessary(); bool m_tagIsOpen = false; @@ -2151,7 +2213,7 @@ namespace { XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) { - writeDeclaration(); + // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627 } XmlWriter::~XmlWriter() { @@ -2262,8 +2324,8 @@ namespace { struct XmlReporter : public IReporter { - XmlWriter xml; - std::mutex mutex; + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; @@ -2340,7 +2402,8 @@ namespace { xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) .writeAttribute("testsuite", in.data[i]->m_test_suite) .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) - .writeAttribute("line", line(in.data[i]->m_line)); + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); } xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); @@ -2356,6 +2419,8 @@ namespace { } void test_run_start() override { + xml.writeDeclaration(); + // remove .exe extension - mainly to have the same output on UNIX and Windows std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); #ifdef DOCTEST_PLATFORM_WINDOWS @@ -2403,14 +2468,15 @@ namespace { test_case_start_impl(in); xml.ensureTagClosed(); } - + void test_case_reenter(const TestCaseData&) override {} void test_case_end(const CurrentTestCaseStats& st) override { xml.startElement("OverallResultsAsserts") .writeAttribute("successes", st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) - .writeAttribute("failures", st.numAssertsFailedCurrentTest); + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); if(opt.duration) xml.writeAttribute("duration", st.seconds); if(tc->m_expected_failures) @@ -2421,7 +2487,7 @@ namespace { } void test_case_exception(const TestCaseException& e) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.scopedElement("Exception") .writeAttribute("crash", e.is_crash) @@ -2429,8 +2495,6 @@ namespace { } void subcase_start(const SubcaseSignature& in) override { - std::lock_guard<std::mutex> lock(mutex); - xml.startElement("SubCase") .writeAttribute("name", in.m_name) .writeAttribute("filename", skipPathFromFilename(in.m_file)) @@ -2444,7 +2508,7 @@ namespace { if(!rb.m_failed && !opt.success) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Expression") .writeAttribute("success", !rb.m_failed) @@ -2460,7 +2524,7 @@ namespace { if(rb.m_at & assertType::is_throws_as) xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); if(rb.m_at & assertType::is_throws_with) - xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str()); if((rb.m_at & assertType::is_normal) && !rb.m_threw) xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); @@ -2470,7 +2534,7 @@ namespace { } void log_message(const MessageData& mb) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Message") .writeAttribute("type", failureString(mb.m_severity)) @@ -2506,7 +2570,8 @@ namespace { } else if((rb.m_at & assertType::is_throws_as) && (rb.m_at & assertType::is_throws_with)) { //!OCLINT s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + << rb.m_exception_string.c_str() + << "\", " << rb.m_exception_type << " ) " << Color::None; if(rb.m_threw) { if(!rb.m_failed) { s << "threw as expected!\n"; @@ -2527,7 +2592,8 @@ namespace { } else if(rb.m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None + << rb.m_exception_string.c_str() + << "\" ) " << Color::None << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : "threw a DIFFERENT exception: ") : "did NOT throw at all!") @@ -2552,8 +2618,8 @@ namespace { // - more attributes in tags struct JUnitReporter : public IReporter { - XmlWriter xml; - std::mutex mutex; + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) Timer timer; std::vector<String> deepestSubcaseStackNames; @@ -2649,9 +2715,13 @@ namespace { // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= - void report_query(const QueryData&) override {} + void report_query(const QueryData&) override { + xml.writeDeclaration(); + } - void test_run_start() override {} + void test_run_start() override { + xml.writeDeclaration(); + } void test_run_end(const TestRunStats& p) override { // remove .exe extension - mainly to have the same output on UNIX and Windows @@ -2721,12 +2791,11 @@ namespace { } void test_case_exception(const TestCaseException& e) override { - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) testCaseData.addError("exception", e.error_string.c_str()); } void subcase_start(const SubcaseSignature& in) override { - std::lock_guard<std::mutex> lock(mutex); deepestSubcaseStackNames.push_back(in.m_name); } @@ -2736,7 +2805,7 @@ namespace { if(!rb.m_failed) // report only failures & ignore the `success` option return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) std::ostringstream os; os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") @@ -2747,7 +2816,22 @@ namespace { testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); } - void log_message(const MessageData&) override {} + void log_message(const MessageData& mb) override { + if(mb.m_severity & assertType::is_warn) // report only failures + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + os << mb.m_string.c_str() << "\n"; + log_contexts(os); + + testCaseData.addFailure(mb.m_string.c_str(), + mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str()); + } void test_case_skipped(const TestCaseData&) override {} @@ -2787,7 +2871,7 @@ namespace { bool hasLoggedCurrentTestStart; std::vector<SubcaseSignature> subcasesStack; size_t currentSubcaseLevel; - std::mutex mutex; + DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; @@ -2892,9 +2976,11 @@ namespace { } void printIntro() { - printVersion(); - s << Color::Cyan << "[doctest] " << Color::None - << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } } void printHelp() { @@ -2979,12 +3065,18 @@ namespace { << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> " << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool> " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool> " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> " << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> " << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> " << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool> " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> " << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> " @@ -3022,22 +3114,6 @@ namespace { printReporters(getReporters(), "reporters"); } - void list_query_results() { - separator_to_stream(); - if(opt.count || opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= @@ -3083,15 +3159,21 @@ namespace { } } - void test_run_start() override { printIntro(); } + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } void test_run_end(const TestRunStats& p) override { + if(opt.minimal && p.numTestCasesFailed == 0) + return; + separator_to_stream(); s << std::dec; - auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1))); - auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1))); - auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1))); + auto totwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1))); const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | " @@ -3123,7 +3205,7 @@ namespace { subcasesStack.clear(); currentSubcaseLevel = 0; } - + void test_case_reenter(const TestCaseData&) override { subcasesStack.clear(); } @@ -3135,7 +3217,7 @@ namespace { // log the preamble of the test case only if there is something // else to print - something other than that an assert has failed if(opt.duration || - (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure))) logTestStart(); if(opt.duration) @@ -3166,6 +3248,7 @@ namespace { } void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) if(tc->m_no_output) return; @@ -3190,14 +3273,12 @@ namespace { } void subcase_start(const SubcaseSignature& subc) override { - std::lock_guard<std::mutex> lock(mutex); subcasesStack.push_back(subc); ++currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void subcase_end() override { - std::lock_guard<std::mutex> lock(mutex); --currentSubcaseLevel; hasLoggedCurrentTestStart = false; } @@ -3206,7 +3287,7 @@ namespace { if((!rb.m_failed && !opt.success) || tc->m_no_output) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) logTestStart(); @@ -3222,7 +3303,7 @@ namespace { if(tc->m_no_output) return; - std::lock_guard<std::mutex> lock(mutex); + DOCTEST_LOCK_MUTEX(mutex) logTestStart(); @@ -3333,18 +3414,42 @@ namespace { std::vector<String>& res) { String filtersString; if(parseOption(argc, argv, pattern, &filtersString)) { - // tokenize with "," as a separator - // cppcheck-suppress strtokCalled - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string - while(pch != nullptr) { - if(strlen(pch)) - res.push_back(pch); - // uses the strtok() internal state to go to the next token - // cppcheck-suppress strtokCalled - pch = std::strtok(nullptr, ","); + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',' || character == '\\') { + s.put(character); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } + } + + if(seenBackslash) { + s.put('\\'); } - DOCTEST_CLANG_SUPPRESS_WARNING_POP + flush(); return true; } return false; @@ -3363,30 +3468,30 @@ namespace { if(!parseOption(argc, argv, pattern, &parsedValue)) return false; - if(type == 0) { + if(type) { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); + if (theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } else { // boolean - const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 - const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1 + const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1 // if the value matches any of the positive/negative possibilities - for(unsigned i = 0; i < 4; i++) { - if(parsedValue.compare(positive[i], true) == 0) { + for (unsigned i = 0; i < 4; i++) { + if (parsedValue.compare(positive[i], true) == 0) { res = 1; //!OCLINT parameter reassignment return true; } - if(parsedValue.compare(negative[i], true) == 0) { + if (parsedValue.compare(negative[i], true) == 0) { res = 0; //!OCLINT parameter reassignment return true; } } - } else { - // integer - // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... - int theInt = std::atoi(parsedValue.c_str()); // NOLINT - if(theInt != 0) { - res = theInt; //!OCLINT parameter reassignment - return true; - } } return false; } @@ -3477,9 +3582,12 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); @@ -3543,10 +3651,14 @@ void Context::clearFilters() { curr.clear(); } -// allows the user to override procedurally the int/bool options from the command line +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line void Context::setOption(const char* option, int value) { setOption(option, toString(value).c_str()); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } // allows the user to override procedurally the string options from the command line @@ -3563,6 +3675,31 @@ void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + // the main function that does all the filtering and test running int Context::run() { using namespace detail; @@ -3576,15 +3713,22 @@ int Context::run() { g_no_colors = p->no_colors; p->resetRunData(); - // stdout by default - p->cout = &std::cout; - p->cerr = &std::cerr; - - // or to a file if specified std::fstream fstr; - if(p->out.size()) { - fstr.open(p->out.c_str(), std::fstream::out); - p->cout = &fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + // stdout by default + p->cout = &std::cout; +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + return EXIT_FAILURE; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + } } FatalConditionHandler::allocateAltStackMem(); @@ -3656,7 +3800,7 @@ int Context::run() { // random_shuffle implementation const auto first = &testArray[0]; for(size_t i = testArray.size() - 1; i > 0; --i) { - int idxToSwap = std::rand() % (i + 1); // NOLINT + int idxToSwap = std::rand() % (i + 1); const auto temp = first[i]; @@ -3743,19 +3887,20 @@ int Context::run() { p->numAssertsFailedCurrentTest_atomic = 0; p->numAssertsCurrentTest_atomic = 0; - p->subcasesPassed.clear(); + p->fullyTraversedSubcases.clear(); DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); p->timer.start(); - + bool run_test = true; do { // reset some of the fields for subcases (except for the set of fully passed ones) - p->should_reenter = false; - p->subcasesCurrentMaxLevel = 0; - p->subcasesStack.clear(); + p->reachedLeaf = false; + // May not be empty if previous subcase exited via exception. + p->subcaseStack.clear(); + p->currentSubcaseDepth = 0; p->shouldLogCurrentException = true; @@ -3788,10 +3933,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP run_test = false; p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; } - - if(p->should_reenter && run_test) + + if(!p->nextSubcaseStack.empty() && run_test) DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); - if(!p->should_reenter) + if(p->nextSubcaseStack.empty()) run_test = false; } while(run_test); @@ -3817,17 +3962,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); } - // see these issues on the reasoning for this: - // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 - // - https://github.com/onqtam/doctest/issues/126 - auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE - { std::cout << std::string(); }; - DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); - return cleanup_and_return(); } -IReporter::~IReporter() = default; +DOCTEST_DEFINE_INTERFACE(IReporter) int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } const IContextScope* const* IReporter::get_active_contexts() { @@ -3862,5 +4000,17 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_IMPLEMENTATION #endif // DOCTEST_CONFIG_IMPLEMENT + +#ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN + +#ifdef DOCTEST_UNDEF_NOMINMAX +#undef NOMINMAX +#undef DOCTEST_UNDEF_NOMINMAX +#endif // DOCTEST_UNDEF_NOMINMAX diff --git a/contrib/doctest/doctest/parts/doctest_fwd.h b/contrib/doctest/doctest/parts/doctest_fwd.h index e83b0ca7e..3e08e81b9 100644 --- a/contrib/doctest/doctest/parts/doctest_fwd.h +++ b/contrib/doctest/doctest/parts/doctest_fwd.h @@ -1,14 +1,14 @@ // // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD // -// Copyright (c) 2016-2021 Viktor Kirilov +// Copyright (c) 2016-2023 Viktor Kirilov // // Distributed under the MIT Software License // See accompanying file LICENSE.txt or copy at // https://opensource.org/licenses/MIT // // The documentation can be found at the library's page: -// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md // // ================================================================================================= // ================================================================================================= @@ -45,8 +45,16 @@ #define DOCTEST_VERSION_MAJOR 2 #define DOCTEST_VERSION_MINOR 4 -#define DOCTEST_VERSION_PATCH 6 -#define DOCTEST_VERSION_STR "2.4.6" +#define DOCTEST_VERSION_PATCH 11 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) #define DOCTEST_VERSION \ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) @@ -57,6 +65,12 @@ // ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect +#ifdef _MSC_VER +#define DOCTEST_CPLUSPLUS _MSVC_LANG +#else +#define DOCTEST_CPLUSPLUS __cplusplus +#endif + #define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) // GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... @@ -68,12 +82,15 @@ DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) #endif // MSVC #endif // MSVC -#if defined(__clang__) && defined(__clang_minor__) +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) #define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ !defined(__INTEL_COMPILER) #define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #endif // GCC +#if defined(__INTEL_COMPILER) +#define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif // ICC #ifndef DOCTEST_MSVC #define DOCTEST_MSVC 0 @@ -84,12 +101,15 @@ #ifndef DOCTEST_GCC #define DOCTEST_GCC 0 #endif // DOCTEST_GCC +#ifndef DOCTEST_ICC +#define DOCTEST_ICC 0 +#endif // DOCTEST_ICC // ================================================================================================= // == COMPILER WARNINGS HELPERS ==================================================================== // ================================================================================================= -#if DOCTEST_CLANG +#if DOCTEST_CLANG && !DOCTEST_ICC #define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) #define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") #define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) @@ -134,85 +154,94 @@ // == COMPILER WARNINGS ============================================================================ // ================================================================================================= +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* common ones */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' - -// 4548 - expression before comma has no effect; expected expression with side - effect -// 4265 - class has virtual functions, but destructor is not virtual -// 4986 - exception specification does not match previous declaration -// 4350 - behavior change: 'member1' called instead of 'member2' -// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' -// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch -// 4774 - format string expected in argument 'x' is not a string literal -// 4820 - padding in structs - -// only 4 should be disabled globally: -// - 4514 # unreferenced inline function has been removed -// - 4571 # SEH related -// - 4710 # function not inlined -// - 4711 # function 'x' selected for automatic inline expansion #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5105) + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -225,6 +254,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' // GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html // MSVC version table: // https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) // MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) // MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) @@ -234,6 +264,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY #if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) #define DOCTEST_CONFIG_WINDOWS_SEH #endif // MSVC @@ -242,7 +276,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #endif // DOCTEST_CONFIG_NO_WINDOWS_SEH #if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ - !defined(__EMSCRIPTEN__) + !defined(__EMSCRIPTEN__) && !defined(__wasi__) #define DOCTEST_CONFIG_POSIX_SIGNALS #endif // _WIN32 #if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) @@ -250,7 +284,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \ + || defined(__wasi__) #define DOCTEST_CONFIG_NO_EXCEPTIONS #endif // no exceptions #endif // DOCTEST_CONFIG_NO_EXCEPTIONS @@ -265,6 +300,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#ifdef __wasi__ +#define DOCTEST_CONFIG_NO_MULTITHREADING +#endif + #if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) #define DOCTEST_CONFIG_IMPLEMENT #endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN @@ -292,6 +331,16 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_INTERFACE #endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +// needed for extern template instantiations +// see https://github.com/fmtlib/fmt/issues/2228 +#if DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL +#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE +#else // DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE +#define DOCTEST_INTERFACE_DEF +#endif // DOCTEST_MSVC + #define DOCTEST_EMPTY #if DOCTEST_MSVC @@ -308,18 +357,61 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) #endif +#ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE +#define DOCTEST_INLINE_NOINLINE inline +#else +#define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE +#endif + #ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC #define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC #endif // DOCTEST_NORETURN #ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC #define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC #endif // DOCTEST_NOEXCEPT +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#define DOCTEST_CONSTEXPR_FUNC inline +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#define DOCTEST_CONSTEXPR_FUNC constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR + +#ifndef DOCTEST_NO_SANITIZE_INTEGER +#if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0) +#define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define DOCTEST_NO_SANITIZE_INTEGER +#endif +#endif // DOCTEST_NO_SANITIZE_INTEGER + // ================================================================================================= // == FEATURE DETECTION END ======================================================================== // ================================================================================================= +#define DOCTEST_DECLARE_INTERFACE(name) \ + virtual ~name(); \ + name() = default; \ + name(const name&) = delete; \ + name(name&&) = delete; \ + name& operator=(const name&) = delete; \ + name& operator=(name&&) = delete; + +#define DOCTEST_DEFINE_INTERFACE(name) \ + name::~name() = default; + // internal macros for string concatenation and anonymous variable name generation #define DOCTEST_CAT_IMPL(s1, s2) s1##s2 #define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) @@ -329,8 +421,6 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) #endif // __COUNTER__ -#define DOCTEST_TOSTR(x) #x - #ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE #define DOCTEST_REF_WRAP(x) x& #else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE @@ -344,31 +434,39 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_PLATFORM_IPHONE #elif defined(_WIN32) #define DOCTEST_PLATFORM_WINDOWS +#elif defined(__wasi__) +#define DOCTEST_PLATFORM_WASI #else // DOCTEST_PLATFORM #define DOCTEST_PLATFORM_LINUX #endif // DOCTEST_PLATFORM -#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ - static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) -#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP #ifndef DOCTEST_BREAK_INTO_DEBUGGER // should probably take a look at https://github.com/scottt/debugbreak #ifdef DOCTEST_PLATFORM_LINUX #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) // Break at the location of the failing check if possible -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) #else #include <signal.h> #define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) #endif #elif defined(DOCTEST_PLATFORM_MAC) #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#elif defined(__ppc__) || defined(__ppc64__) +// https://www.cocoawithlove.com/2008/03/break-into-debugger.html +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler) #else -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler) #endif #elif DOCTEST_MSVC #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() @@ -384,54 +482,67 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP // this is kept here for backwards compatibility since the config option was changed #ifdef DOCTEST_CONFIG_USE_IOSFWD +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS #define DOCTEST_CONFIG_USE_STD_HEADERS +#endif #endif // DOCTEST_CONFIG_USE_IOSFWD +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 +#if DOCTEST_CLANG +#include <ciso646> +#endif // clang + +#ifdef _LIBCPP_VERSION +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // _LIBCPP_VERSION + #ifdef DOCTEST_CONFIG_USE_STD_HEADERS #ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#include <iosfwd> +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include <cstddef> #include <ostream> +#include <istream> +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #else // DOCTEST_CONFIG_USE_STD_HEADERS -#if DOCTEST_CLANG -// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) -#include <ciso646> -#endif // clang - -#ifdef _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD -#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD -#else // _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { -#define DOCTEST_STD_NAMESPACE_END } -#endif // _LIBCPP_VERSION - // Forward declaring 'X' in namespace std is not permitted by the C++ Standard. DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) -DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) -typedef decltype(nullptr) nullptr_t; +namespace std { // NOLINT(cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using) +typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using) template <class charT> struct char_traits; template <> struct char_traits<char>; template <class charT, class traits> -class basic_ostream; -typedef basic_ostream<char, char_traits<char>> ostream; +class basic_ostream; // NOLINT(fuchsia-virtual-inheritance) +typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using) +template<class traits> +// NOLINTNEXTLINE +basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>&, const char*); +template <class charT, class traits> +class basic_istream; +typedef basic_istream<char, char_traits<char>> istream; // NOLINT(modernize-use-using) template <class... Types> class tuple; #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -template <class _Ty> +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template <class Ty> class allocator; -template <class _Elem, class _Traits, class _Alloc> +template <class Elem, class Traits, class Alloc> class basic_string; using string = basic_string<char, char_traits<char>, allocator<char>>; #endif // VS 2019 -DOCTEST_STD_NAMESPACE_END +} // namespace std DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -443,8 +554,14 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP namespace doctest { +using std::size_t; + DOCTEST_INTERFACE extern bool is_running_in_test; +#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE +#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned +#endif + // A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length // of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: // - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) @@ -457,7 +574,6 @@ DOCTEST_INTERFACE extern bool is_running_in_test; // TODO: // - optimizations - like not deleting memory unnecessarily in operator= and etc. // - resize/reserve/clear -// - substr // - replace // - back/front // - iterator stuff @@ -467,63 +583,84 @@ DOCTEST_INTERFACE extern bool is_running_in_test; // - relational operators as free functions - taking const char* as one of the params class DOCTEST_INTERFACE String { - static const unsigned len = 24; //!OCLINT avoid private static members - static const unsigned last = len - 1; //!OCLINT avoid private static members +public: + using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE; + +private: + static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members + static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members struct view // len should be more than sizeof(view) - because of the final byte for flags { char* ptr; - unsigned size; - unsigned capacity; + size_type size; + size_type capacity; }; union { - char buf[len]; + char buf[len]; // NOLINT(*-avoid-c-arrays) view data; }; - bool isOnStack() const { return (buf[last] & 128) == 0; } - void setOnHeap(); - void setLast(unsigned in = last); + char* allocate(size_type sz); + + bool isOnStack() const noexcept { return (buf[last] & 128) == 0; } + void setOnHeap() noexcept; + void setLast(size_type in = last) noexcept; + void setSize(size_type sz) noexcept; void copy(const String& other); public: - String(); + static DOCTEST_CONSTEXPR size_type npos = static_cast<size_type>(-1); + + String() noexcept; ~String(); // cppcheck-suppress noExplicitConstructor String(const char* in); - String(const char* in, unsigned in_size); + String(const char* in, size_type in_size); + + String(std::istream& in, size_type in_size); String(const String& other); String& operator=(const String& other); String& operator+=(const String& other); - String operator+(const String& other) const; - String(String&& other); - String& operator=(String&& other); + String(String&& other) noexcept; + String& operator=(String&& other) noexcept; - char operator[](unsigned i) const; - char& operator[](unsigned i); + char operator[](size_type i) const; + char& operator[](size_type i); // the only functions I'm willing to leave in the interface - available for inlining const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT char* c_str() { - if(isOnStack()) + if (isOnStack()) { return reinterpret_cast<char*>(buf); + } return data.ptr; } - unsigned size() const; - unsigned capacity() const; + size_type size() const; + size_type capacity() const; + + String substr(size_type pos, size_type cnt = npos) &&; + String substr(size_type pos, size_type cnt = npos) const &; + + size_type find(char ch, size_type pos = 0) const; + size_type rfind(char ch, size_type pos = npos) const; int compare(const char* other, bool no_case = false) const; int compare(const String& other, bool no_case = false) const; + +friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); }; +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); @@ -531,7 +668,21 @@ DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); +class DOCTEST_INTERFACE Contains { +public: + explicit Contains(const String& string); + + bool checkWith(const String& other) const; + + String string; +}; + +DOCTEST_INTERFACE String toString(const Contains& in); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs); namespace Color { enum Enum @@ -604,7 +755,7 @@ namespace assertType { DT_WARN_THROWS_WITH = is_throws_with | is_warn, DT_CHECK_THROWS_WITH = is_throws_with | is_check, DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, - + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, @@ -685,9 +836,27 @@ struct DOCTEST_INTERFACE AssertData String m_decomp; // for specific exception-related asserts - bool m_threw_as; - const char* m_exception_type; - const char* m_exception_string; + bool m_threw_as; + const char* m_exception_type; + + class DOCTEST_INTERFACE StringContains { + private: + Contains content; + bool isContains; + + public: + StringContains(const String& str) : content(str), isContains(false) { } + StringContains(Contains cntn) : content(static_cast<Contains&&>(cntn)), isContains(true) { } + + bool check(const String& str) { return isContains ? (content == str) : (content.string == str); } + + operator const String&() const { return content.string; } + + const char* c_str() const { return content.string.c_str(); } + } m_exception_string; + + AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string); }; struct DOCTEST_INTERFACE MessageData @@ -704,13 +873,13 @@ struct DOCTEST_INTERFACE SubcaseSignature const char* m_file; int m_line; + bool operator==(const SubcaseSignature& other) const; bool operator<(const SubcaseSignature& other) const; }; struct DOCTEST_INTERFACE IContextScope { - IContextScope(); - virtual ~IContextScope(); + DOCTEST_DECLARE_INTERFACE(IContextScope) virtual void stringify(std::ostream*) const = 0; }; @@ -720,9 +889,8 @@ namespace detail { struct ContextOptions //!OCLINT too many fields { - std::ostream* cout; // stdout stream - std::cout by default - std::ostream* cerr; // stderr stream - std::cerr by default - String binary_name; // the test binary name + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name const detail::TestCase* currentTest = nullptr; @@ -741,9 +909,12 @@ struct ContextOptions //!OCLINT too many fields bool case_sensitive; // if filtering should be case sensitive bool exit; // if the program should be exited after the tests are ran/whatever bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output bool no_throw; // to skip exceptions-related assertion macros bool no_exitcode; // if the framework should return 0 as the exitcode bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework bool no_version; // to not print the version of the framework bool no_colors; // if output to the console should be colorized bool force_colors; // forces the use of colors even when a tty cannot be detected @@ -765,150 +936,189 @@ struct ContextOptions //!OCLINT too many fields }; namespace detail { - template <bool CONDITION, typename TYPE = void> - struct enable_if - {}; + namespace types { +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + using namespace std; +#else + template <bool COND, typename T = void> + struct enable_if { }; + + template <typename T> + struct enable_if<true, T> { using type = T; }; - template <typename TYPE> - struct enable_if<true, TYPE> - { typedef TYPE type; }; + struct true_type { static DOCTEST_CONSTEXPR bool value = true; }; + struct false_type { static DOCTEST_CONSTEXPR bool value = false; }; - // clang-format off - template<class T> struct remove_reference { typedef T type; }; - template<class T> struct remove_reference<T&> { typedef T type; }; - template<class T> struct remove_reference<T&&> { typedef T type; }; + template <typename T> struct remove_reference { using type = T; }; + template <typename T> struct remove_reference<T&> { using type = T; }; + template <typename T> struct remove_reference<T&&> { using type = T; }; - template<typename T, typename U = T&&> U declval(int); + template <typename T> struct is_rvalue_reference : false_type { }; + template <typename T> struct is_rvalue_reference<T&&> : true_type { }; - template<typename T> T declval(long); + template<typename T> struct remove_const { using type = T; }; + template <typename T> struct remove_const<const T> { using type = T; }; - template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ; + // Compiler intrinsics + template <typename T> struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); }; + template <typename T> struct underlying_type { using type = __underlying_type(T); }; - template<class T> struct is_lvalue_reference { const static bool value=false; }; - template<class T> struct is_lvalue_reference<T&> { const static bool value=true; }; + template <typename T> struct is_pointer : false_type { }; + template <typename T> struct is_pointer<T*> : true_type { }; + + template <typename T> struct is_array : false_type { }; + // NOLINTNEXTLINE(*-avoid-c-arrays) + template <typename T, size_t SIZE> struct is_array<T[SIZE]> : true_type { }; +#endif + } + + // <utility> + template <typename T> + T&& declval(); template <class T> - inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT - { + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type& t) DOCTEST_NOEXCEPT { return static_cast<T&&>(t); } template <class T> - inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT - { - static_assert(!is_lvalue_reference<T>::value, - "Can not forward an rvalue as an lvalue."); + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type&& t) DOCTEST_NOEXCEPT { return static_cast<T&&>(t); } - template<class T> struct remove_const { typedef T type; }; - template<class T> struct remove_const<const T> { typedef T type; }; -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template<class T> struct is_enum : public std::is_enum<T> {}; - template<class T> struct underlying_type : public std::underlying_type<T> {}; -#else - // Use compiler intrinsics - template<class T> struct is_enum { constexpr static bool value = __is_enum(T); }; - template<class T> struct underlying_type { typedef __underlying_type(T) type; }; -#endif - // clang-format on + template <typename T> + struct deferred_false : types::false_type { }; + +// MSVS 2015 :( +#if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900 + template <typename T, typename = void> + struct has_global_insertion_operator : types::false_type { }; template <typename T> - struct deferred_false - // cppcheck-suppress unusedStructMember - { static const bool value = false; }; - - namespace has_insertion_operator_impl { - std::ostream &os(); - template<class T> - DOCTEST_REF_WRAP(T) val(); - - template<class, class = void> - struct check { - static constexpr bool value = false; - }; + struct has_global_insertion_operator<T, decltype(::operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { }; - template<class T> - struct check<T, decltype(os() << val<T>(), void())> { - static constexpr bool value = true; - }; - } // namespace has_insertion_operator_impl + template <typename T, typename = void> + struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator<T>::value; }; + + template <typename T, bool global> + struct insert_hack; + + template <typename T> + struct insert_hack<T, true> { + static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); } + }; + + template <typename T> + struct insert_hack<T, false> { + static void insert(std::ostream& os, const T& t) { operator<<(os, t); } + }; - template<class T> - using has_insertion_operator = has_insertion_operator_impl::check<const T>; + template <typename T> + using insert_hack_t = insert_hack<T, has_global_insertion_operator<T>::value>; +#else + template <typename T, typename = void> + struct has_insertion_operator : types::false_type { }; +#endif - DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + template <typename T> + struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { }; + + template <typename T> + struct should_stringify_as_underlying_type { + static DOCTEST_CONSTEXPR bool value = detail::types::is_enum<T>::value && !doctest::detail::has_insertion_operator<T>::value; + }; - DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream - DOCTEST_INTERFACE String getTlsOssResult(); + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); template <bool C> - struct StringMakerBase - { + struct StringMakerBase { template <typename T> static String convert(const DOCTEST_REF_WRAP(T)) { +#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES + static_assert(deferred_false<T>::value, "No stringification detected for type T. See string conversion manual"); +#endif return "{?}"; } }; - template <> - struct StringMakerBase<true> - { - template <typename T> - static String convert(const DOCTEST_REF_WRAP(T) in) { - *getTlsOss() << in; - return getTlsOssResult(); - } - }; - - DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + template <typename T> + struct filldata; template <typename T> - String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { - return rawMemoryToString(&object, sizeof(object)); + void filloss(std::ostream* stream, const T& in) { + filldata<T>::fill(stream, in); + } + + template <typename T, size_t N> + void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays) + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filloss<typename types::remove_reference<decltype(in)>::type>(stream, in); } template <typename T> - const char* type_to_string() { - return "<>"; + String toStream(const T& in) { + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); } + + template <> + struct StringMakerBase<true> { + template <typename T> + static String convert(const DOCTEST_REF_WRAP(T) in) { + return toStream(in); + } + }; } // namespace detail template <typename T> -struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value> +struct StringMaker : public detail::StringMakerBase< + detail::has_insertion_operator<T>::value || detail::types::is_pointer<T>::value || detail::types::is_array<T>::value> {}; -template <typename T> -struct StringMaker<T*> -{ - template <typename U> - static String convert(U* p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; +#ifndef DOCTEST_STRINGIFY +#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY +#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__)) +#else +#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__) +#endif +#endif -template <typename R, typename C> -struct StringMaker<R C::*> -{ - static String convert(R C::*p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; +template <typename T> +String toString() { +#if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0 + String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void) + String::size_type beginPos = ret.find('<'); + return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)"))); +#else + String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE] + String::size_type begin = ret.find('=') + 2; + return ret.substr(begin, ret.size() - begin - 1); +#endif +} -template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true> +template <typename T, typename detail::types::enable_if<!detail::should_stringify_as_underlying_type<T>::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { return StringMaker<T>::convert(value); } #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(char* in); DOCTEST_INTERFACE String toString(const char* in); #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +DOCTEST_INTERFACE String toString(String in); + +DOCTEST_INTERFACE String toString(std::nullptr_t); + DOCTEST_INTERFACE String toString(bool in); + DOCTEST_INTERFACE String toString(float in); DOCTEST_INTERFACE String toString(double in); DOCTEST_INTERFACE String toString(double long in); @@ -916,40 +1126,95 @@ DOCTEST_INTERFACE String toString(double long in); DOCTEST_INTERFACE String toString(char in); DOCTEST_INTERFACE String toString(char signed in); DOCTEST_INTERFACE String toString(char unsigned in); -DOCTEST_INTERFACE String toString(int short in); -DOCTEST_INTERFACE String toString(int short unsigned in); -DOCTEST_INTERFACE String toString(int in); -DOCTEST_INTERFACE String toString(int unsigned in); -DOCTEST_INTERFACE String toString(int long in); -DOCTEST_INTERFACE String toString(int long unsigned in); -DOCTEST_INTERFACE String toString(int long long in); -DOCTEST_INTERFACE String toString(int long long unsigned in); -DOCTEST_INTERFACE String toString(std::nullptr_t in); - -template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true> +DOCTEST_INTERFACE String toString(short in); +DOCTEST_INTERFACE String toString(short unsigned in); +DOCTEST_INTERFACE String toString(signed in); +DOCTEST_INTERFACE String toString(unsigned in); +DOCTEST_INTERFACE String toString(long in); +DOCTEST_INTERFACE String toString(long unsigned in); +DOCTEST_INTERFACE String toString(long long in); +DOCTEST_INTERFACE String toString(long long unsigned in); + +template <typename T, typename detail::types::enable_if<detail::should_stringify_as_underlying_type<T>::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { - typedef typename detail::underlying_type<T>::type UT; - return toString(static_cast<UT>(value)); + using UT = typename detail::types::underlying_type<T>::type; + return (DOCTEST_STRINGIFY(static_cast<UT>(value))); } -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -DOCTEST_INTERFACE String toString(const std::string& in); -#endif // VS 2019 +namespace detail { + template <typename T> + struct filldata + { + static void fill(std::ostream* stream, const T& in) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + insert_hack_t<T>::insert(*stream, in); +#else + operator<<(*stream, in); +#endif + } + }; + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) +// NOLINTBEGIN(*-avoid-c-arrays) + template <typename T, size_t N> + struct filldata<T[N]> { + static void fill(std::ostream* stream, const T(&in)[N]) { + *stream << "["; + for (size_t i = 0; i < N; i++) { + if (i != 0) { *stream << ", "; } + *stream << (DOCTEST_STRINGIFY(in[i])); + } + *stream << "]"; + } + }; +// NOLINTEND(*-avoid-c-arrays) +DOCTEST_MSVC_SUPPRESS_WARNING_POP -class DOCTEST_INTERFACE Approx + // Specialized since we don't want the terminating null byte! +// NOLINTBEGIN(*-avoid-c-arrays) + template <size_t N> + struct filldata<const char[N]> { + static void fill(std::ostream* stream, const char (&in)[N]) { + *stream << String(in, in[N - 1] ? N : N - 1); + } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + }; +// NOLINTEND(*-avoid-c-arrays) + + template <> + struct filldata<const void*> { + static void fill(std::ostream* stream, const void* in); + }; + + template <typename T> + struct filldata<T*> { +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180) + static void fill(std::ostream* stream, const T* in) { +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast") + filldata<const void*>::fill(stream, +#if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0) + reinterpret_cast<const void*>(in) +#else + *reinterpret_cast<const void* const*>(&in) +#endif + ); +DOCTEST_CLANG_SUPPRESS_WARNING_POP + } + }; +} + +struct DOCTEST_INTERFACE Approx { -public: - explicit Approx(double value); + Approx(double value); Approx operator()(double value) const; #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> explicit Approx(const T& value, - typename detail::enable_if<std::is_constructible<double, T>::value>::type* = + typename detail::types::enable_if<std::is_constructible<double, T>::value>::type* = static_cast<T*>(nullptr)) { - *this = Approx(static_cast<double>(value)); + *this = static_cast<double>(value); } #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS @@ -957,7 +1222,7 @@ public: #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> - typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( + typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( const T& newEpsilon) { m_epsilon = static_cast<double>(newEpsilon); return *this; @@ -968,7 +1233,7 @@ public: #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template <typename T> - typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( + typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( const T& newScale) { m_scale = static_cast<double>(newScale); return *this; @@ -989,30 +1254,27 @@ public: DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend String toString(const Approx& in); - #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_APPROX_PREFIX \ - template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type + template <typename T> friend typename std::enable_if<std::is_constructible<double, T>::value, bool>::type - DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast<double>(lhs), rhs); } DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) && lhs != rhs; } #undef DOCTEST_APPROX_PREFIX #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format on -private: double m_epsilon; double m_scale; double m_value; @@ -1022,18 +1284,35 @@ DOCTEST_INTERFACE String toString(const Approx& in); DOCTEST_INTERFACE const ContextOptions* getContextOptions(); -#if !defined(DOCTEST_CONFIG_DISABLE) +template <typename F> +struct DOCTEST_INTERFACE_DECL IsNaN +{ + F value; bool flipped; + IsNaN(F f, bool flip = false) : value(f), flipped(flip) { } + IsNaN<F> operator!() const { return { value, !flipped }; } + operator bool() const; +}; +#ifndef __MINGW32__ +extern template struct DOCTEST_INTERFACE_DECL IsNaN<float>; +extern template struct DOCTEST_INTERFACE_DECL IsNaN<double>; +extern template struct DOCTEST_INTERFACE_DECL IsNaN<long double>; +#endif +DOCTEST_INTERFACE String toString(IsNaN<float> in); +DOCTEST_INTERFACE String toString(IsNaN<double> in); +DOCTEST_INTERFACE String toString(IsNaN<double long> in); + +#ifndef DOCTEST_CONFIG_DISABLE namespace detail { // clang-format off #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - template<class T> struct decay_array { typedef T type; }; - template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; }; - template<class T> struct decay_array<T[]> { typedef T* type; }; + template<class T> struct decay_array { using type = T; }; + template<class T, unsigned N> struct decay_array<T[N]> { using type = T*; }; + template<class T> struct decay_array<T[]> { using type = T*; }; - template<class T> struct not_char_pointer { enum { value = 1 }; }; - template<> struct not_char_pointer<char*> { enum { value = 0 }; }; - template<> struct not_char_pointer<const char*> { enum { value = 0 }; }; + template<class T> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 1; }; + template<> struct not_char_pointer<char*> { static DOCTEST_CONSTEXPR int value = 0; }; + template<> struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR int value = 0; }; template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {}; #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING @@ -1056,16 +1335,22 @@ namespace detail { bool m_entered = false; Subcase(const String& name, const char* file, int line); + Subcase(const Subcase&) = delete; + Subcase(Subcase&&) = delete; + Subcase& operator=(const Subcase&) = delete; + Subcase& operator=(Subcase&&) = delete; ~Subcase(); operator bool() const; + + private: + bool checkFilters(); }; template <typename L, typename R> String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, const DOCTEST_REF_WRAP(R) rhs) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - return toString(lhs) + op + toString(rhs); + return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs)); } #if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) @@ -1076,12 +1361,16 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") // If not it doesn't find the operator or if the operator at global scope is defined after // this template, the template won't be instantiated due to SFINAE. Once the template is not // instantiated it can look for global operator using normal conversions. -#define SFINAE_OP(ret,op) decltype(doctest::detail::declval<L>() op doctest::detail::declval<R>(),static_cast<ret>(0)) +#ifdef __NVCC__ +#define SFINAE_OP(ret,op) ret +#else +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{}) +#endif #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ template <typename R> \ - DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ - bool res = op_macro(doctest::detail::forward<L>(lhs), doctest::detail::forward<R>(rhs)); \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<R>(rhs)); \ if(m_at & assertType::is_false) \ res = !res; \ if(!res || doctest::getContextOptions()->success) \ @@ -1100,11 +1389,12 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") return *this; \ } - struct DOCTEST_INTERFACE Result + struct DOCTEST_INTERFACE Result // NOLINT(*-member-init) { bool m_passed; String m_decomp; + Result() = default; // TODO: Why do we need this? (To remove NOLINT) Result(bool passed, const String& decomposition = String()); // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence @@ -1161,8 +1451,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") #ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE bool #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } @@ -1210,26 +1499,26 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") assertType::Enum m_at; explicit Expression_lhs(L&& in, assertType::Enum at) - : lhs(doctest::detail::forward<L>(in)) + : lhs(static_cast<L&&>(in)) , m_at(at) {} DOCTEST_NOINLINE operator Result() { -// this is needed only foc MSVC 2015: -// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202 +// this is needed only for MSVC 2015 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool bool res = static_cast<bool>(lhs); DOCTEST_MSVC_SUPPRESS_WARNING_POP - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional res = !res; + } - if(!res || getContextOptions()->success) - return Result(res, toString(lhs)); - return Result(res); + if(!res || getContextOptions()->success) { + return { res, (DOCTEST_STRINGIFY(lhs)) }; + } + return { res }; } - /* This is required for user-defined conversions from Expression_lhs to L */ - //operator L() const { return lhs; } - operator L() const { return lhs; } + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } // clang-format off DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional @@ -1286,22 +1575,27 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 template <typename L> - Expression_lhs<L> operator<<(L &&operand) { - return Expression_lhs<L>(doctest::detail::forward<L>(operand), m_at); + Expression_lhs<L> operator<<(L&& operand) { + return Expression_lhs<L>(static_cast<L&&>(operand), m_at); + } + + template <typename L,typename types::enable_if<!doctest::detail::types::is_rvalue_reference<L>::value,void >::type* = nullptr> + Expression_lhs<const L&> operator<<(const L &operand) { + return Expression_lhs<const L&>(operand, m_at); } }; struct DOCTEST_INTERFACE TestSuite { - const char* m_test_suite; - const char* m_description; - bool m_skip; - bool m_no_breaks; - bool m_no_output; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; TestSuite& operator*(const char* in); @@ -1312,25 +1606,28 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP } }; - typedef void (*funcType)(); + using funcType = void (*)(); struct DOCTEST_INTERFACE TestCase : public TestCaseData { funcType m_test; // a function pointer to the test case - const char* m_type; // for templated test cases - gets appended to the real name + String m_type; // for templated test cases - gets appended to the real name int m_template_id; // an ID used to distinguish between the different versions of a templated test case String m_full_name; // contains the name (only for templated test cases!) + the template type TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type = "", int template_id = -1); + const String& type = String(), int template_id = -1); TestCase(const TestCase& other); + TestCase(TestCase&&) = delete; DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function TestCase& operator=(const TestCase& other); DOCTEST_MSVC_SUPPRESS_WARNING_POP + TestCase& operator=(TestCase&&) = delete; + TestCase& operator*(const char* in); template <typename T> @@ -1340,6 +1637,8 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP } bool operator<(const TestCase& other) const; + + ~TestCase() = default; }; // forward declarations of functions used by the macros @@ -1379,27 +1678,36 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP struct DOCTEST_INTERFACE ResultBuilder : public AssertData { ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type = "", const char* exception_string = ""); + const char* exception_type = "", const String& exception_string = ""); + + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string); void setResult(const Result& res); template <int comparison, typename L, typename R> - DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); - if(m_failed || getContextOptions()->success) + if (m_failed || getContextOptions()->success) { m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + return !m_failed; } template <typename L> - DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { m_failed = !val; - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional m_failed = !m_failed; + } - if(m_failed || getContextOptions()->success) - m_decomp = toString(val); + if (m_failed || getContextOptions()->success) { + m_decomp = (DOCTEST_STRINGIFY(val)); + } + + return !m_failed; } void translateException(); @@ -1419,8 +1727,8 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); - DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, - const char* expr, Result result); + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, const Result& result); #define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ do { \ @@ -1435,7 +1743,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP if(checkIfShouldThrow(at)) \ throwException(); \ } \ - return; \ + return !failed; \ } \ } while(false) @@ -1450,7 +1758,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP throwException() template <int comparison, typename L, typename R> - DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); @@ -1461,10 +1769,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; } template <typename L> - DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) val) { bool failed = !val; @@ -1475,14 +1784,14 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); - DOCTEST_ASSERT_IN_TESTS(toString(val)); + DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val))); + DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val))); + return !failed; } struct DOCTEST_INTERFACE IExceptionTranslator { - IExceptionTranslator(); - virtual ~IExceptionTranslator(); + DOCTEST_DECLARE_INTERFACE(IExceptionTranslator) virtual bool translate(String&) const = 0; }; @@ -1498,7 +1807,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP try { throw; // lgtm [cpp/rethrow-no-exception] // cppcheck-suppress catchExceptionByValue - } catch(T ex) { // NOLINT + } catch(const T& ex) { res = m_translateFunction(ex); //!OCLINT parameter reassignment return true; } catch(...) {} //!OCLINT - empty catch statement @@ -1513,95 +1822,70 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); - template <bool C> - struct StringStreamBase - { - template <typename T> - static void convert(std::ostream* s, const T& in) { - *s << toString(in); - } - - // always treat char* as a string in this context - no matter - // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined - static void convert(std::ostream* s, const char* in) { *s << String(in); } - }; - - template <> - struct StringStreamBase<true> - { - template <typename T> - static void convert(std::ostream* s, const T& in) { - *s << in; - } - }; + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + ContextScopeBase(const ContextScopeBase&) = delete; - template <typename T> - struct StringStream : public StringStreamBase<has_insertion_operator<T>::value> - {}; + ContextScopeBase& operator=(const ContextScopeBase&) = delete; + ContextScopeBase& operator=(ContextScopeBase&&) = delete; - template <typename T> - void toStream(std::ostream* s, const T& value) { - StringStream<T>::convert(s, value); - } + ~ContextScopeBase() override = default; -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); - DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); - DOCTEST_INTERFACE void toStream(std::ostream* s, float in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); - - DOCTEST_INTERFACE void toStream(std::ostream* s, char in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); - - // ContextScope base class used to allow implementing methods of ContextScope - // that don't depend on the template parameter in doctest.cpp. - class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { protected: ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other) noexcept; void destroy(); + bool need_to_destroy{true}; }; template <typename L> class ContextScope : public ContextScopeBase { - const L lambda_; + L lambda_; public: explicit ContextScope(const L &lambda) : lambda_(lambda) {} + explicit ContextScope(L&& lambda) : lambda_(static_cast<L&&>(lambda)) { } - ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + ContextScope(const ContextScope&) = delete; + ContextScope(ContextScope&&) noexcept = default; + + ContextScope& operator=(const ContextScope&) = delete; + ContextScope& operator=(ContextScope&&) = delete; void stringify(std::ostream* s) const override { lambda_(s); } - ~ContextScope() override { destroy(); } + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } }; struct DOCTEST_INTERFACE MessageBuilder : public MessageData { std::ostream* m_stream; + bool logged = false; MessageBuilder(const char* file, int line, assertType::Enum severity); - MessageBuilder() = delete; + + MessageBuilder(const MessageBuilder&) = delete; + MessageBuilder(MessageBuilder&&) = delete; + + MessageBuilder& operator=(const MessageBuilder&) = delete; + MessageBuilder& operator=(MessageBuilder&&) = delete; + ~MessageBuilder(); // the preferred way of chaining parameters for stringification +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) template <typename T> MessageBuilder& operator,(const T& in) { - toStream(m_stream, in); + *m_stream << (DOCTEST_STRINGIFY(in)); return *this; } +DOCTEST_MSVC_SUPPRESS_WARNING_POP // kept here just for backwards-compatibility - the comma operator should be preferred now template <typename T> @@ -1617,7 +1901,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP bool log(); void react(); }; - + template <typename L> ContextScope<L> MakeContextScope(const L &lambda) { return ContextScope<L>(lambda); @@ -1670,7 +1954,7 @@ int registerExceptionTranslator(String (*)(T)) { #endif // DOCTEST_CONFIG_DISABLE namespace detail { - typedef void (*assert_handler)(const AssertData&); + using assert_handler = void (*)(const AssertData&); struct ContextState; } // namespace detail @@ -1683,12 +1967,19 @@ class DOCTEST_INTERFACE Context public: explicit Context(int argc = 0, const char* const* argv = nullptr); - ~Context(); + Context(const Context&) = delete; + Context(Context&&) = delete; + + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + ~Context(); // NOLINT(performance-trivially-destructible) void applyCommandLine(int argc, const char* const* argv); void addFilter(const char* filter, const char* value); void clearFilters(); + void setOption(const char* option, bool value); void setOption(const char* option, int value); void setOption(const char* option, const char* value); @@ -1698,6 +1989,8 @@ public: void setAssertHandler(detail::assert_handler ah); + void setCout(std::ostream* out); + int run(); }; @@ -1724,6 +2017,7 @@ struct DOCTEST_INTERFACE CurrentTestCaseStats int numAssertsFailedCurrentTest; double seconds; int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; }; struct DOCTEST_INTERFACE TestCaseException @@ -1787,8 +2081,7 @@ struct DOCTEST_INTERFACE IReporter // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) virtual void test_case_skipped(const TestCaseData&) = 0; - // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have - virtual ~IReporter(); + DOCTEST_DECLARE_INTERFACE(IReporter) // can obtain all currently active contexts and stringify them if one wishes to do so static int get_num_active_contexts(); @@ -1800,7 +2093,7 @@ struct DOCTEST_INTERFACE IReporter }; namespace detail { - typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + using reporterCreatorFunc = IReporter* (*)(const ContextOptions&); DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); @@ -1817,14 +2110,30 @@ int registerReporter(const char* name, int priority, bool isReporter) { } } // namespace doctest +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_EMPTY [] { return false; }() +#else +#define DOCTEST_FUNC_EMPTY (void)0 +#endif + // if registering is not disabled -#if !defined(DOCTEST_CONFIG_DISABLE) +#ifndef DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_SCOPE_BEGIN [&] +#define DOCTEST_FUNC_SCOPE_END () +#define DOCTEST_FUNC_SCOPE_RET(v) return v +#else +#define DOCTEST_FUNC_SCOPE_BEGIN do +#define DOCTEST_FUNC_SCOPE_END while(false) +#define DOCTEST_FUNC_SCOPE_RET(v) (void)0 +#endif // common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ - if(b.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - b.react() +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ + if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react(); \ + DOCTEST_FUNC_SCOPE_RET(!b.m_failed) #ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #define DOCTEST_WRAP_IN_TRY(x) x; @@ -1832,7 +2141,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_WRAP_IN_TRY(x) \ try { \ x; \ - } catch(...) { _DOCTEST_RB.translateException(); } + } catch(...) { DOCTEST_RB.translateException(); } #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS @@ -1846,27 +2155,26 @@ int registerReporter(const char* name, int priority, bool isReporter) { // registers the test by initializing a dummy var with a function #define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ - global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \ doctest::detail::regTest( \ doctest::detail::TestCase( \ f, __FILE__, __LINE__, \ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() + decorators)) #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ - namespace { \ + namespace { /* NOLINT */ \ struct der : public base \ { \ void f(); \ }; \ - static void func() { \ + static DOCTEST_INLINE_NOINLINE void func() { \ der v; \ v.f(); \ } \ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ } \ - inline DOCTEST_NOINLINE void der::f() + DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers) #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ static void f(); \ @@ -1875,18 +2183,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ static doctest::detail::funcType proxy() { return f; } \ - DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ static void f() // for registering tests #define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for registering tests in classes - requires C++17 for inline variables! -#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#if DOCTEST_CPLUSPLUS >= 201703L #define DOCTEST_TEST_CASE_CLASS(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ decorators) #else // DOCTEST_TEST_CASE_CLASS #define DOCTEST_TEST_CASE_CLASS(...) \ @@ -1895,26 +2203,25 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for converting types to strings without the <typeinfo> header and demangling -#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ - template <> \ - inline const char* type_to_string<__VA_ARGS__>() { \ - return "<" #__VA_ARGS__ ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(...) \ - namespace doctest { namespace detail { \ - DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \ + namespace doctest { \ + template <> \ + inline String toString<__VA_ARGS__>() { \ + return str; \ } \ } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + static_assert(true, "") + +#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__) #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ template <typename T> \ static void func(); \ - namespace { \ + namespace { /* NOLINT */ \ template <typename Tuple> \ struct iter; \ template <typename Type, typename... Rest> \ @@ -1923,7 +2230,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { iter(const char* file, unsigned line, int index) { \ doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \ doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string<Type>(), \ + doctest::toString<Type>(), \ int(line) * 1000 + index) \ * dec); \ iter<std::tuple<Rest...>>(file, line, index + 1); \ @@ -1940,20 +2247,20 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) #define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ - doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ - DOCTEST_GLOBAL_NO_WARNINGS_END() + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) #define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ @@ -1962,17 +2269,17 @@ int registerReporter(const char* name, int priority, bool isReporter) { static void anon() #define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) // for subcases #define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ doctest::detail::Subcase(name, __FILE__, __LINE__)) // for grouping tests in test suites by using code blocks #define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ namespace ns_name { namespace doctest_detail_test_suite_ns { \ - static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ @@ -1992,53 +2299,53 @@ int registerReporter(const char* name, int priority, bool isReporter) { namespace ns_name #define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) // for starting a testsuite block #define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") // for ending a testsuite block #define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int // for registering exception translators #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ inline doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ - doctest::registerExceptionTranslator(translatorName); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerExceptionTranslator(translatorName)) \ doctest::String translatorName(signature) #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ signature) // for registering reporters #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter<reporter>(name, priority, true); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter<reporter>(name, priority, true)) \ + static_assert(true, "") // for registering listeners #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter<reporter>(name, priority, false); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter<reporter>(name, priority, false)) \ + static_assert(true, "") -// for logging +// clang-format off +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 #define DOCTEST_INFO(...) \ - DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ __VA_ARGS__) +// clang-format on #define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ - auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ [&](std::ostream* s_name) { \ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ mb_name.m_stream = s_name; \ @@ -2048,16 +2355,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) #define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ mb * __VA_ARGS__; \ - DOCTEST_ASSERT_LOG_AND_REACT(mb); \ - } while(false) + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + } DOCTEST_FUNC_SCOPE_END // clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) -#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) // clang-format on #define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) @@ -2070,18 +2379,37 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - } while(false) + } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2095,6 +2423,14 @@ int registerReporter(const char* name, int priority, bool isReporter) { doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + #endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS #define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) @@ -2105,51 +2441,83 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) // clang-format off -#define DOCTEST_WARN_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) -#define DOCTEST_CHECK_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END // clang-format on +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #expr, #__VA_ARGS__, message); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(const typename doctest::detail::remove_const< \ - typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ - _DOCTEST_RB.translateException(); \ - _DOCTEST_RB.m_threw_as = true; \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(const typename doctest::detail::types::remove_const< \ + typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ } \ - } while(false) + } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ - do { \ + DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, expr_str, "", __VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ } \ - } while(false) + } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END // clang-format off #define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") @@ -2172,166 +2540,23 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) #define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } while(false) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } while(false) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } while(false) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } while(false) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } while(false) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END // clang-format on -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \ - __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ - doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ - #__VA_ARGS__, __VA_ARGS__) - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) -#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) -#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) -#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) -#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) -#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) -#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) -#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) -#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) -#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) -#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) -#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) -#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) -#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) -#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) -#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) -#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) -#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) - -#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) -#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS - -#undef DOCTEST_WARN_THROWS -#undef DOCTEST_CHECK_THROWS -#undef DOCTEST_REQUIRE_THROWS -#undef DOCTEST_WARN_THROWS_AS -#undef DOCTEST_CHECK_THROWS_AS -#undef DOCTEST_REQUIRE_THROWS_AS -#undef DOCTEST_WARN_THROWS_WITH -#undef DOCTEST_CHECK_THROWS_WITH -#undef DOCTEST_REQUIRE_THROWS_WITH -#undef DOCTEST_WARN_THROWS_WITH_AS -#undef DOCTEST_CHECK_THROWS_WITH_AS -#undef DOCTEST_REQUIRE_THROWS_WITH_AS -#undef DOCTEST_WARN_NOTHROW -#undef DOCTEST_CHECK_NOTHROW -#undef DOCTEST_REQUIRE_NOTHROW - -#undef DOCTEST_WARN_THROWS_MESSAGE -#undef DOCTEST_CHECK_THROWS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_MESSAGE -#undef DOCTEST_WARN_THROWS_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_WARN_NOTHROW_MESSAGE -#undef DOCTEST_CHECK_NOTHROW_MESSAGE -#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) - -#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#undef DOCTEST_REQUIRE -#undef DOCTEST_REQUIRE_FALSE -#undef DOCTEST_REQUIRE_MESSAGE -#undef DOCTEST_REQUIRE_FALSE_MESSAGE -#undef DOCTEST_REQUIRE_EQ -#undef DOCTEST_REQUIRE_NE -#undef DOCTEST_REQUIRE_GT -#undef DOCTEST_REQUIRE_LT -#undef DOCTEST_REQUIRE_GE -#undef DOCTEST_REQUIRE_LE -#undef DOCTEST_REQUIRE_UNARY -#undef DOCTEST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // ================================================================================================= @@ -2341,7 +2566,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #else // DOCTEST_CONFIG_DISABLE #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ - namespace { \ + namespace /* NOLINT */ { \ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ struct der : public base \ { void f(); }; \ @@ -2355,51 +2580,48 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests #define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests in classes #define DOCTEST_TEST_CASE_CLASS(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for converting types to strings without the <typeinfo> header and demangling -#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#define DOCTEST_TYPE_TO_STRING_IMPL(...) +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "") +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") // for typed tests #define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ template <typename type> \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ template <typename type> \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") // for subcases #define DOCTEST_SUBCASE(name) // for a testsuite block -#define DOCTEST_TEST_SUITE(name) namespace +#define DOCTEST_TEST_SUITE(name) namespace // NOLINT // for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") // for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ - static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) @@ -2413,80 +2635,253 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0)) #define DOCTEST_FAIL(...) (static_cast<void>(0)) -#define DOCTEST_WARN(...) (static_cast<void>(0)) -#define DOCTEST_CHECK(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_FALSE(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) - -#define DOCTEST_WARN_EQ(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_EQ(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_EQ(...) (static_cast<void>(0)) -#define DOCTEST_WARN_NE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_NE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_NE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_GT(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_GT(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_GT(...) (static_cast<void>(0)) -#define DOCTEST_WARN_LT(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_LT(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_LT(...) (static_cast<void>(0)) -#define DOCTEST_WARN_GE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_GE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_GE(...) (static_cast<void>(0)) -#define DOCTEST_WARN_LE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_LE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_LE(...) (static_cast<void>(0)) - -#define DOCTEST_WARN_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_UNARY(...) (static_cast<void>(0)) -#define DOCTEST_WARN_UNARY_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_CHECK_UNARY_FALSE(...) (static_cast<void>(0)) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) (static_cast<void>(0)) +#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \ + && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES) + +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() + +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template <typename L, typename R> \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest + +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }() +#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED #endif // DOCTEST_CONFIG_DISABLE +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \ + "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }() + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + // clang-format off // KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS #define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ @@ -2533,11 +2928,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { // clang-format on // == SHORT VERSIONS OF THE MACROS -#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) +#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES #define TEST_CASE(name) DOCTEST_TEST_CASE(name) #define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) #define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__) #define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) #define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) #define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) @@ -2670,37 +3066,17 @@ int registerReporter(const char* name, int priority, bool isReporter) { #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES -#if !defined(DOCTEST_CONFIG_DISABLE) +#ifndef DOCTEST_CONFIG_DISABLE // this is here to clear the 'current test suite' for the current translation unit - at the top DOCTEST_TEST_SUITE_END(); -// add stringification for primitive/fundamental types -namespace doctest { namespace detail { - DOCTEST_TYPE_TO_STRING_IMPL(bool) - DOCTEST_TYPE_TO_STRING_IMPL(float) - DOCTEST_TYPE_TO_STRING_IMPL(double) - DOCTEST_TYPE_TO_STRING_IMPL(long double) - DOCTEST_TYPE_TO_STRING_IMPL(char) - DOCTEST_TYPE_TO_STRING_IMPL(signed char) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) -#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) - DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) -#endif // not MSVC or wchar_t support enabled - DOCTEST_TYPE_TO_STRING_IMPL(short int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) - DOCTEST_TYPE_TO_STRING_IMPL(int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) - DOCTEST_TYPE_TO_STRING_IMPL(long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) - DOCTEST_TYPE_TO_STRING_IMPL(long long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) -}} // namespace doctest::detail - #endif // DOCTEST_CONFIG_DISABLE DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_INCLUDED diff --git a/contrib/doctest/scripts/cmake/Config.cmake.in b/contrib/doctest/scripts/cmake/Config.cmake.in new file mode 100644 index 000000000..44ddb07d8 --- /dev/null +++ b/contrib/doctest/scripts/cmake/Config.cmake.in @@ -0,0 +1,6 @@ +if(NOT TARGET doctest::doctest) + # Provide path for scripts + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + + include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") +endif() diff --git a/contrib/doctest/scripts/cmake/assemble_single_header.cmake b/contrib/doctest/scripts/cmake/assemble_single_header.cmake new file mode 100644 index 000000000..980975491 --- /dev/null +++ b/contrib/doctest/scripts/cmake/assemble_single_header.cmake @@ -0,0 +1,13 @@ +set(doctest_include_folder "${CMAKE_CURRENT_LIST_DIR}/../../doctest/") + +file(READ ${doctest_include_folder}/parts/doctest_fwd.h fwd) +file(READ ${doctest_include_folder}/parts/doctest.cpp impl) + +file(WRITE ${doctest_include_folder}/doctest.h "// ====================================================================== lgtm [cpp/missing-header-guard]\n") +file(APPEND ${doctest_include_folder}/doctest.h "// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==\n") +file(APPEND ${doctest_include_folder}/doctest.h "// ======================================================================\n") +file(APPEND ${doctest_include_folder}/doctest.h "${fwd}\n") +file(APPEND ${doctest_include_folder}/doctest.h "#ifndef DOCTEST_SINGLE_HEADER\n") +file(APPEND ${doctest_include_folder}/doctest.h "#define DOCTEST_SINGLE_HEADER\n") +file(APPEND ${doctest_include_folder}/doctest.h "#endif // DOCTEST_SINGLE_HEADER\n") +file(APPEND ${doctest_include_folder}/doctest.h "\n${impl}") diff --git a/contrib/doctest/scripts/cmake/common.cmake b/contrib/doctest/scripts/cmake/common.cmake new file mode 100644 index 000000000..b90f5dd75 --- /dev/null +++ b/contrib/doctest/scripts/cmake/common.cmake @@ -0,0 +1,212 @@ +include(CMakeParseArguments) + +# cache this for use inside of the function +set(CURRENT_LIST_DIR_CACHED ${CMAKE_CURRENT_LIST_DIR}) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +enable_testing() + +find_package(Threads) + +set(DOCTEST_TEST_MODE "COMPARE" CACHE STRING "Test mode - normal/run through valgrind/collect output/compare with output") +set_property(CACHE DOCTEST_TEST_MODE PROPERTY STRINGS "NORMAL;VALGRIND;COLLECT;COMPARE") + +function(doctest_add_test_impl) + cmake_parse_arguments(ARG "NO_VALGRIND;NO_OUTPUT;XML_OUTPUT;JUNIT_OUTPUT" "NAME" "COMMAND" ${ARGN}) + if(NOT "${ARG_UNPARSED_ARGUMENTS}" STREQUAL "" OR "${ARG_NAME}" STREQUAL "" OR "${ARG_COMMAND}" STREQUAL "") + message(FATAL_ERROR "doctest_add_test() called with wrong options!") + endif() + + set(the_test_mode NORMAL) + + # construct the command that will be called by the exec_test.cmake script + set(the_command "") + if(${DOCTEST_TEST_MODE} STREQUAL "VALGRIND" AND NOT ARG_NO_VALGRIND) + set(the_test_mode VALGRIND) + set(the_command "valgrind -v --leak-check=full --track-origins=yes --error-exitcode=1") + endif() + foreach(cur ${ARG_COMMAND}) + set(the_command "${the_command} ${cur}") + endforeach() + if(ARG_XML_OUTPUT) + set(the_command "${the_command} --reporters=xml") + set(ARG_NAME ${ARG_NAME}_xml) + endif() + if(ARG_JUNIT_OUTPUT) + set(the_command "${the_command} --reporters=junit") + set(ARG_NAME ${ARG_NAME}_junit) + endif() + + # append the argument for removing paths from filenames in the output so tests give the same output everywhere + set(the_command "${the_command} --dt-no-path-filenames=1") + # append the argument for substituting source line numbers with 0 in the output so tests give the same output when lines change a bit + set(the_command "${the_command} --dt-no-line-numbers=1") + # append the argument for ignoring the exit code of the test programs because some are intended to have failing tests + set(the_command "${the_command} --dt-no-exitcode=1") + # append the argument for using the same line format in the output - so gcc/non-gcc builds have the same output + set(the_command "${the_command} --dt-gnu-file-line=0") + # append the argument for skipping any time-related output so that the reference output from reporters is stable on CI + set(the_command "${the_command} --dt-no-time-in-output=1") + + string(STRIP ${the_command} the_command) + + if(${DOCTEST_TEST_MODE} STREQUAL "COLLECT" OR ${DOCTEST_TEST_MODE} STREQUAL "COMPARE") + if(NOT ARG_NO_OUTPUT) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_output/) + set(the_test_mode ${DOCTEST_TEST_MODE}) + list(APPEND ADDITIONAL_FLAGS -DTEST_OUTPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/test_output/${ARG_NAME}.txt) + list(APPEND ADDITIONAL_FLAGS -DTEST_TEMP_FILE=${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/temp_test_output_${ARG_NAME}.txt) + endif() + endif() + + list(APPEND ADDITIONAL_FLAGS -DTEST_MODE=${the_test_mode}) + + add_test(NAME ${ARG_NAME} COMMAND ${CMAKE_COMMAND} -DCOMMAND=${the_command} ${ADDITIONAL_FLAGS} -P ${CURRENT_LIST_DIR_CACHED}/exec_test.cmake) +endfunction() + +# a custom version of add_test() to suite my needs +function(doctest_add_test) + doctest_add_test_impl(${ARGN}) + doctest_add_test_impl(${ARGN} XML_OUTPUT) + doctest_add_test_impl(${ARGN} JUNIT_OUTPUT) +endfunction() + +macro(add_compiler_flags) + foreach(flag ${ARGV}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") + endforeach() +endmacro() + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compiler_flags(-Werror) + add_compiler_flags(-fstrict-aliasing) + + # The following options are not valid when clang-cl is used. + if(NOT MSVC) + add_compiler_flags(-pedantic) + add_compiler_flags(-pedantic-errors) + add_compiler_flags(-fvisibility=hidden) + endif() +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + #add_compiler_flags(-Wno-unknown-pragmas) + add_compiler_flags(-Wall) + add_compiler_flags(-Wextra) + add_compiler_flags(-fdiagnostics-show-option) + add_compiler_flags(-Wconversion) + add_compiler_flags(-Wold-style-cast) + add_compiler_flags(-Wfloat-equal) + add_compiler_flags(-Wlogical-op) + add_compiler_flags(-Wundef) + add_compiler_flags(-Wredundant-decls) + add_compiler_flags(-Wshadow) + add_compiler_flags(-Wstrict-overflow=5) + add_compiler_flags(-Wwrite-strings) + add_compiler_flags(-Wpointer-arith) + add_compiler_flags(-Wcast-qual) + add_compiler_flags(-Wformat=2) + add_compiler_flags(-Wswitch-default) + add_compiler_flags(-Wmissing-include-dirs) + add_compiler_flags(-Wcast-align) + add_compiler_flags(-Wswitch-enum) + add_compiler_flags(-Wnon-virtual-dtor) + add_compiler_flags(-Wctor-dtor-privacy) + add_compiler_flags(-Wsign-conversion) + add_compiler_flags(-Wdisabled-optimization) + add_compiler_flags(-Weffc++) + add_compiler_flags(-Winvalid-pch) + add_compiler_flags(-Wmissing-declarations) + add_compiler_flags(-Woverloaded-virtual) + add_compiler_flags(-Wunused-but-set-variable) + add_compiler_flags(-Wunused-result) + + # add_compiler_flags(-Wsuggest-override) + # add_compiler_flags(-Wmultiple-inheritance) + # add_compiler_flags(-Wcatch-value) + # add_compiler_flags(-Wsuggest-attribute=cold) + # add_compiler_flags(-Wsuggest-attribute=const) + # add_compiler_flags(-Wsuggest-attribute=format) + # add_compiler_flags(-Wsuggest-attribute=malloc) + # add_compiler_flags(-Wsuggest-attribute=noreturn) + # add_compiler_flags(-Wsuggest-attribute=pure) + # add_compiler_flags(-Wsuggest-final-methods) + # add_compiler_flags(-Wsuggest-final-types) + + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) + add_compiler_flags(-Wnoexcept) + endif() + + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + add_compiler_flags(-Wno-missing-field-initializers) + endif() + + # no way to silence it in the expression decomposition macros: _Pragma() in macros doesn't work for the c++ front-end of g++ + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69543 + # Also the warning is completely worthless nowadays - https://stackoverflow.com/questions/14016993 + #add_compiler_flags(-Waggregate-return) + + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + add_compiler_flags(-Wdouble-promotion) + add_compiler_flags(-Wtrampolines) + add_compiler_flags(-Wzero-as-null-pointer-constant) + add_compiler_flags(-Wuseless-cast) + add_compiler_flags(-Wvector-operation-performance) + endif() + + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + add_compiler_flags(-Wshift-overflow=2) + add_compiler_flags(-Wnull-dereference) + add_compiler_flags(-Wduplicated-cond) + endif() + + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) + add_compiler_flags(-Walloc-zero) + add_compiler_flags(-Walloca) + add_compiler_flags(-Wduplicated-branches) + endif() + + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0) + add_compiler_flags(-Wcast-align=strict) + endif() +endif() + +# necessary for some older compilers which don't default to C++11 +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compiler_flags(-Weverything) + add_compiler_flags(-Wno-c++98-compat) + add_compiler_flags(-Wno-c++98-compat-pedantic) + add_compiler_flags(-Wno-c++98-compat-bind-to-temporary-copy) + add_compiler_flags(-Wno-c++98-compat-local-type-template-args) + add_compiler_flags(-Qunused-arguments -fcolor-diagnostics) # needed for ccache integration +endif() + +if(MSVC) + add_compiler_flags(/std:c++latest) # for post c++14 updates in MSVC + add_compiler_flags(/permissive-) # force standard conformance - this is the better flag than /Za + add_compiler_flags(/WX) + add_compiler_flags(/Wall) # turns on warnings from levels 1 through 4 which are off by default - https://msdn.microsoft.com/en-us/library/23k5d385.aspx + + add_compiler_flags( + /wd4514 # unreferenced inline function has been removed + /wd4571 # SEH related + /wd5264 # const variable is not used + /wd4710 # function not inlined + /wd4711 # function 'x' selected for automatic inline expansion + + /wd4616 # invalid compiler warnings - https://msdn.microsoft.com/en-us/library/t7ab6xtd.aspx + /wd4619 # invalid compiler warnings - https://msdn.microsoft.com/en-us/library/tacee08d.aspx + + #/wd4820 # padding in structs + #/wd4625 # copy constructor was implicitly defined as deleted + #/wd4626 # assignment operator was implicitly defined as deleted + #/wd5027 # move assignment operator was implicitly defined as deleted + #/wd5026 # move constructor was implicitly defined as deleted + #/wd4623 # default constructor was implicitly defined as deleted + ) +endif() diff --git a/contrib/doctest/scripts/cmake/doctest.cmake b/contrib/doctest/scripts/cmake/doctest.cmake new file mode 100644 index 000000000..3c4929f20 --- /dev/null +++ b/contrib/doctest/scripts/cmake/doctest.cmake @@ -0,0 +1,189 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +doctest +----- + +This module defines a function to help use the doctest test framework. + +The :command:`doctest_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each doctest test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: doctest_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + doctest_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [ADD_LABELS value] + [TEST_LIST var] + [JUNIT_OUTPUT_DIR dir] + ) + + ``doctest_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-cases`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``<target>_TESTS`` variable. + + The options are: + + ``target`` + Specifies the doctest executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the doctest executable with the ``--list-test-cases`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``doctest_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``doctest_discover_tests``. + + ``ADD_LABELS value`` + Specifies if the test labels should be set automatically. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``<target>_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``doctest_discover_tests()``. + Note that this variable is only available in CTest. + + ``JUNIT_OUTPUT_DIR dir`` + If specified, the parameter is passed along with ``--reporters=junit`` + and ``--out=`` to the test executable. The actual file name is the same + as the test target, including prefix and suffix. This should be used + instead of EXTRA_ARGS to avoid race conditions writing the XML result + output when using parallel test execution. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(doctest_discover_tests TARGET) + cmake_parse_arguments( + "" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS" + ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + ## Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_ADD_LABELS=${_ADD_LABELS}" + -D "TEST_PREFIX=${_TEST_PREFIX}" + -D "TEST_SUFFIX=${_TEST_SUFFIX}" + -D "TEST_LIST=${_TEST_LIST}" + -D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}" + -D "CTEST_FILE=${ctest_tests_file}" + -P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + if(NOT CMAKE_VERSION VERSION_LESS 3.10) + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if(NOT ${test_include_file_set}) + set_property(DIRECTORY + PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + ) + else() + message(FATAL_ERROR + "Cannot set more than one TEST_INCLUDE_FILE" + ) + endif() + endif() + +endfunction() + +############################################################################### + +set(_DOCTEST_DISCOVER_TESTS_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake +) diff --git a/contrib/doctest/scripts/cmake/doctestAddTests.cmake b/contrib/doctest/scripts/cmake/doctestAddTests.cmake new file mode 100644 index 000000000..3b254850f --- /dev/null +++ b/contrib/doctest/scripts/cmake/doctestAddTests.cmake @@ -0,0 +1,120 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(add_labels ${TEST_ADD_LABELS}) +set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}") +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${TEST_EXECUTABLE}' does not exist" + ) +endif() + +if("${spec}" MATCHES .) + set(spec "--test-case=${spec}") +endif() + +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases + OUTPUT_VARIABLE output + RESULT_VARIABLE result + WORKING_DIRECTORY "${TEST_WORKING_DIR}" +) +if(NOT ${result} EQUAL 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") + +# Parse output +foreach(line ${output}) + if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==]) + continue() + endif() + set(test ${line}) + set(labels "") + if(${add_labels}) + # get test suite that test belongs to + execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites + OUTPUT_VARIABLE labeloutput + RESULT_VARIABLE labelresult + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ) + if(NOT ${labelresult} EQUAL 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${labelresult}\n" + " Output: ${labeloutput}\n" + ) + endif() + + string(REPLACE "\n" ";" labeloutput "${labeloutput}") + foreach(labelline ${labeloutput}) + if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==]) + continue() + endif() + list(APPEND labels ${labelline}) + endforeach() + endif() + + if(NOT "${junit_output_dir}" STREQUAL "") + # turn testname into a valid filename by replacing all special characters with "-" + string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}") + set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml") + else() + unset(TEST_JUNIT_OUTPUT_PARAM) + endif() + # use escape commas to handle properly test cases with commas inside the name + string(REPLACE "," "\\," test_name ${test}) + # ...and add to script + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "--test-case=${test_name}" + "${TEST_JUNIT_OUTPUT_PARAM}" + ${extra_args} + ) + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ${properties} + LABELS ${labels} + ) + unset(labels) + list(APPEND tests "${prefix}${test}${suffix}") +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set +# properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/contrib/doctest/scripts/cmake/exec_test.cmake b/contrib/doctest/scripts/cmake/exec_test.cmake new file mode 100644 index 000000000..ab51a0157 --- /dev/null +++ b/contrib/doctest/scripts/cmake/exec_test.cmake @@ -0,0 +1,70 @@ +# Arguments: +# - COMMAND: the command to run with all it's arguments +# - TEST_MODE: NORMAL/VALGRIND/COLLECT/COMPARE +# - TEST_OUTPUT_FILE: the file to/from which to write/read the output of the test +# - TEST_TEMP_FILE: the temp file for the current test output used in COMPARE mode +# To run something through this script use cmake like this: +# cmake -DCOMMAND=path/to/my.exe -arg1 -arg2 -DTEST_MODE=VALGRIND -P path/to/exec_test.cmake + +#message("COMMAND: ${COMMAND}") +#message("TEST_MODE: ${TEST_MODE}") +#message("TEST_OUTPUT_FILE: ${TEST_OUTPUT_FILE}") +#message("TEST_TEMP_FILE: ${TEST_TEMP_FILE}") + +string(REPLACE " " ";" COMMAND_LIST ${COMMAND}) +set(cmd COMMAND ${COMMAND_LIST} RESULT_VARIABLE CMD_RESULT) +if("${TEST_MODE}" STREQUAL "COLLECT") + list(APPEND cmd OUTPUT_FILE ${TEST_OUTPUT_FILE} ERROR_FILE ${TEST_OUTPUT_FILE}) +elseif("${TEST_MODE}" STREQUAL "COMPARE") + list(APPEND cmd OUTPUT_FILE ${TEST_TEMP_FILE} ERROR_FILE ${TEST_TEMP_FILE}) +endif() + +execute_process(${cmd}) + +# fix line endings +if("${TEST_MODE}" STREQUAL "COLLECT" AND NOT CMAKE_HOST_UNIX) + execute_process(COMMAND dos2unix ${TEST_OUTPUT_FILE}) +endif() + +if("${TEST_MODE}" STREQUAL "COMPARE") + # fix line endings + if(NOT CMAKE_HOST_UNIX) + execute_process(COMMAND dos2unix ${TEST_TEMP_FILE}) + endif() + + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.14.0") + set(IGNORE_EOL --ignore-eol) + endif() + + execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files ${IGNORE_EOL} ${TEST_OUTPUT_FILE} ${TEST_TEMP_FILE} RESULT_VARIABLE cmp_result) + + if(cmp_result) + find_package(Git) + if(GIT_FOUND) + set(cmd ${GIT_EXECUTABLE} diff --no-index ${TEST_OUTPUT_FILE} ${TEST_TEMP_FILE}) + execute_process(COMMAND ${GIT_EXECUTABLE} diff --no-index ${TEST_OUTPUT_FILE} ${TEST_TEMP_FILE} OUTPUT_VARIABLE DIFF) + message("${DIFF}") + endif() + + # file(READ ${TEST_OUTPUT_FILE} orig) + # file(READ ${TEST_TEMP_FILE} temp) + + # message("==========================================================================") + # message("== CONTENTS OF ${TEST_OUTPUT_FILE}") + # message("==========================================================================") + # message("${orig}") + # message("==========================================================================") + # message("== CONTENTS OF ${TEST_TEMP_FILE}") + # message("==========================================================================") + # message("${temp}") + # message("==========================================================================") + # message("== CONTENTS END") + # message("==========================================================================") + + set(CMD_RESULT "Output is different from reference file!") + endif() +endif() + +if(CMD_RESULT) + message(FATAL_ERROR "Running '${COMMAND}' ended with code '${CMD_RESULT}'") +endif() diff --git a/contrib/doctest/scripts/version.txt b/contrib/doctest/scripts/version.txt new file mode 100644 index 000000000..e393c3c55 --- /dev/null +++ b/contrib/doctest/scripts/version.txt @@ -0,0 +1 @@ +2.4.11
\ No newline at end of file diff --git a/contrib/kann/kann.c b/contrib/kann/kann.c index 70d1f02d6..658f98a44 100644 --- a/contrib/kann/kann.c +++ b/contrib/kann/kann.c @@ -1,3 +1,19 @@ +/* + * Copyright 2024 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "config.h" #include <math.h> @@ -19,9 +35,9 @@ static void kad_ext_collate(int n, kad_node_t **a, float **_x, float **_g, float int i, j, k, l, n_var; float *x, *g, *c; n_var = kad_size_var(n, a); - x = *_x = (float*)realloc(*_x, n_var * sizeof(float)); - g = *_g = (float*)realloc(*_g, n_var * sizeof(float)); - c = *_c = (float*)realloc(*_c, kad_size_const(n, a) * sizeof(float)); + x = *_x = (float *) realloc(*_x, n_var * sizeof(float)); + g = *_g = (float *) realloc(*_g, n_var * sizeof(float)); + c = *_c = (float *) realloc(*_c, kad_size_const(n, a) * sizeof(float)); memset(g, 0, n_var * sizeof(float)); for (i = j = k = 0; i < n; ++i) { kad_node_t *v = a[i]; @@ -32,7 +48,8 @@ static void kad_ext_collate(int n, kad_node_t **a, float **_x, float **_g, float v->x = &x[j]; v->g = &g[j]; j += l; - } else if (kad_is_const(v)) { + } + else if (kad_is_const(v)) { l = kad_len(v); memcpy(&c[k], v->x, l * sizeof(float)); free(v->x); @@ -51,7 +68,8 @@ static void kad_ext_sync(int n, kad_node_t **a, float *x, float *g, float *c) v->x = &x[j]; v->g = &g[j]; j += kad_len(v); - } else if (kad_is_const(v)) { + } + else if (kad_is_const(v)) { v->x = &c[k]; k += kad_len(v); } @@ -68,14 +86,14 @@ kann_t *kann_new(kad_node_t *cost, int n_rest, ...) if (cost->n_d != 0) return 0; va_start(ap, n_rest); - roots = (kad_node_t**)malloc((n_roots + 1) * sizeof(kad_node_t*)); + roots = (kad_node_t **) malloc((n_roots + 1) * sizeof(kad_node_t *)); for (i = 0; i < n_rest; ++i) - roots[i] = va_arg(ap, kad_node_t*); + roots[i] = va_arg(ap, kad_node_t *); roots[i++] = cost; va_end(ap); cost->ext_flag |= KANN_F_COST; - a = (kann_t*)calloc(1, sizeof(kann_t)); + a = (kann_t *) calloc(1, sizeof(kann_t)); a->v = kad_compile_array(&a->n, n_roots, roots); for (i = 0; i < a->n; ++i) { @@ -84,7 +102,7 @@ kann_t *kann_new(kad_node_t *cost, int n_rest, ...) } if (has_recur && !has_pivot) { /* an RNN that doesn't have a pivot; then add a pivot on top of cost and recompile */ cost->ext_flag &= ~KANN_F_COST; - roots[n_roots-1] = cost = kad_avg(1, &cost), cost->ext_flag |= KANN_F_COST; + roots[n_roots - 1] = cost = kad_avg(1, &cost), cost->ext_flag |= KANN_F_COST; free(a->v); a->v = kad_compile_array(&a->n, n_roots, roots); } @@ -96,7 +114,7 @@ kann_t *kann_new(kad_node_t *cost, int n_rest, ...) kann_t *kann_clone(kann_t *a, int batch_size) { kann_t *b; - b = (kann_t*)calloc(1, sizeof(kann_t)); + b = (kann_t *) calloc(1, sizeof(kann_t)); b->n = a->n; b->v = kad_clone(a->n, a->v, batch_size); kad_ext_collate(b->n, b->v, &b->x, &b->g, &b->c); @@ -106,7 +124,7 @@ kann_t *kann_clone(kann_t *a, int batch_size) kann_t *kann_unroll_array(kann_t *a, int *len) { kann_t *b; - b = (kann_t*)calloc(1, sizeof(kann_t)); + b = (kann_t *) calloc(1, sizeof(kann_t)); b->x = a->x, b->g = a->g, b->c = a->c; /* these arrays are shared */ b->v = kad_unroll(a->n, a->v, &b->n, len); return b; @@ -118,7 +136,7 @@ kann_t *kann_unroll(kann_t *a, ...) va_list ap; int i, n_pivots, *len; n_pivots = kad_n_pivots(a->n, a->v); - len = (int*)calloc(n_pivots, sizeof(int)); + len = (int *) calloc(n_pivots, sizeof(int)); va_start(ap, a); for (i = 0; i < n_pivots; ++i) len[i] = va_arg(ap, int); va_end(ap); @@ -137,7 +155,9 @@ void kann_delete_unrolled(kann_t *a) void kann_delete(kann_t *a) { if (a == 0) return; - free(a->x); free(a->g); free(a->c); + free(a->x); + free(a->g); + free(a->c); kann_delete_unrolled(a); } @@ -146,7 +166,7 @@ static void kann_switch_core(kann_t *a, int is_train) int i; for (i = 0; i < a->n; ++i) if (a->v[i]->op == 12 && a->v[i]->n_child == 2) - *(int32_t*)a->v[i]->ptr = !!is_train; + *(int32_t *) a->v[i]->ptr = !!is_train; } #define chk_flg(flag, mask) ((mask) == 0 || ((flag) & (mask))) @@ -158,7 +178,8 @@ int kann_find(const kann_t *a, uint32_t ext_flag, int32_t ext_label) for (i = k = 0; i < a->n; ++i) if (chk_flg(a->v[i]->ext_flag, ext_flag) && chk_lbl(a->v[i]->ext_label, ext_label)) ++k, r = i; - return k == 1? r : k == 0? -1 : -2; + return k == 1 ? r : k == 0 ? -1 + : -2; } int kann_feed_bind(kann_t *a, uint32_t ext_flag, int32_t ext_label, float **x) @@ -176,8 +197,10 @@ int kann_feed_dim(const kann_t *a, uint32_t ext_flag, int32_t ext_label) int i, k, n = 0; for (i = k = 0; i < a->n; ++i) if (kad_is_feed(a->v[i]) && chk_flg(a->v[i]->ext_flag, ext_flag) && chk_lbl(a->v[i]->ext_label, ext_label)) - ++k, n = a->v[i]->n_d > 1? kad_len(a->v[i]) / a->v[i]->d[0] : a->v[i]->n_d == 1? a->v[i]->d[0] : 1; - return k == 1? n : k == 0? -1 : -2; + ++k, n = a->v[i]->n_d > 1 ? kad_len(a->v[i]) / a->v[i]->d[0] : a->v[i]->n_d == 1 ? a->v[i]->d[0] + : 1; + return k == 1 ? n : k == 0 ? -1 + : -2; } static float kann_cost_core(kann_t *a, int cost_label, int cal_grad) @@ -210,7 +233,8 @@ void kann_rnn_start(kann_t *a) if (p->pre) { /* NB: BE CAREFUL of the interaction between kann_rnn_start() and kann_set_batch_size() */ kad_node_t *q = p->pre; if (q->x) memcpy(p->x, q->x, kad_len(p) * sizeof(float)); - else memset(p->x, 0, kad_len(p) * sizeof(float)); + else + memset(p->x, 0, kad_len(p) * sizeof(float)); if (q->n_child > 0) free(q->x); q->x = p->x; } @@ -223,7 +247,7 @@ void kann_rnn_end(kann_t *a) kad_ext_sync(a->n, a->v, a->x, a->g, a->c); for (i = 0; i < a->n; ++i) if (a->v[i]->pre && a->v[i]->pre->n_child > 0) - a->v[i]->pre->x = (float*)calloc(kad_len(a->v[i]->pre), sizeof(float)); + a->v[i]->pre->x = (float *) calloc(kad_len(a->v[i]->pre), sizeof(float)); } static int kann_class_error_core(const kann_t *ann, int *base) @@ -238,10 +262,10 @@ static int kann_class_error_core(const kann_t *ann, int *base) float t_sum = 0.0f, t_min = 1.0f, t_max = 0.0f, x_max = 0.0f, x_min = 1.0f; int x_max_k = -1, t_max_k = -1; for (k = 0; k < n; ++k) { - float xk = x->x[off+k], tk = t->x[off+k]; + float xk = x->x[off + k], tk = t->x[off + k]; t_sum += tk; - t_min = t_min < tk? t_min : tk; - x_min = x_min < xk? x_min : xk; + t_min = t_min < tk ? t_min : tk; + x_min = x_min < xk ? x_min : xk; if (t_max < tk) t_max = tk, t_max_k = k; if (x_max < xk) x_max = xk, x_max_k = k; } @@ -283,7 +307,7 @@ typedef struct mtaux_t { /* cross-worker data */ static void *mt_worker(void *data) /* pthread worker */ { - mtaux1_t *mt1 = (mtaux1_t*)data; + mtaux1_t *mt1 = (mtaux1_t *) data; mtaux_t *mt = mt1->g; for (;;) { int action; @@ -297,7 +321,8 @@ static void *mt_worker(void *data) /* pthread worker */ if (action == -1) break; if (mt->eval_out) kann_eval(mt1->a, KANN_F_OUT, 0); - else mt1->cost = kann_cost_core(mt1->a, mt->cost_label, mt->cal_grad); + else + mt1->cost = kann_cost_core(mt1->a, mt->cost_label, mt->cal_grad); } pthread_exit(0); } @@ -324,18 +349,18 @@ void kann_mt(kann_t *ann, int n_threads, int max_batch_size) int i, k; if (n_threads <= 1) { - if (ann->mt) mt_destroy((mtaux_t*)ann->mt); + if (ann->mt) mt_destroy((mtaux_t *) ann->mt); ann->mt = 0; return; } if (n_threads > max_batch_size) n_threads = max_batch_size; if (n_threads <= 1) return; - mt = (mtaux_t*)calloc(1, sizeof(mtaux_t)); + mt = (mtaux_t *) calloc(1, sizeof(mtaux_t)); mt->n_threads = n_threads, mt->max_batch_size = max_batch_size; pthread_mutex_init(&mt->mtx, 0); pthread_cond_init(&mt->cv, 0); - mt->mt = (mtaux1_t*)calloc(n_threads, sizeof(mtaux1_t)); + mt->mt = (mtaux1_t *) calloc(n_threads, sizeof(mtaux1_t)); for (i = k = 0; i < n_threads; ++i) { int size = (max_batch_size - k) / (n_threads - i); mt->mt[i].a = kann_clone(ann, size); @@ -350,11 +375,11 @@ void kann_mt(kann_t *ann, int n_threads, int max_batch_size) static void mt_kickoff(kann_t *a, int cost_label, int cal_grad, int eval_out) { - mtaux_t *mt = (mtaux_t*)a->mt; + mtaux_t *mt = (mtaux_t *) a->mt; int i, j, k, B, n_var; B = kad_sync_dim(a->n, a->v, -1); /* get the current batch size */ - assert(B <= mt->max_batch_size); /* TODO: can be relaxed */ + assert(B <= mt->max_batch_size); /* TODO: can be relaxed */ n_var = kann_size_var(a); pthread_mutex_lock(&mt->mtx); @@ -376,7 +401,7 @@ static void mt_kickoff(kann_t *a, int cost_label, int cal_grad, int eval_out) float kann_cost(kann_t *a, int cost_label, int cal_grad) { - mtaux_t *mt = (mtaux_t*)a->mt; + mtaux_t *mt = (mtaux_t *) a->mt; int i, j, B, k, n_var; float cost; @@ -392,7 +417,7 @@ float kann_cost(kann_t *a, int cost_label, int cal_grad) for (i = k = 0, cost = 0.0f; i < mt->n_threads; ++i) { int size = (B - k) / (mt->n_threads - i); cost += mt->mt[i].cost * size / B; - kad_saxpy(n_var, (float)size / B, mt->mt[i].a->g, a->g); + kad_saxpy(n_var, (float) size / B, mt->mt[i].a->g, a->g); k += size; } for (j = 0; j < a->n; ++j) { /* copy values back at recurrent nodes (needed by textgen; TODO: temporary solution) */ @@ -410,14 +435,14 @@ float kann_cost(kann_t *a, int cost_label, int cal_grad) int kann_eval_out(kann_t *a) { - mtaux_t *mt = (mtaux_t*)a->mt; + mtaux_t *mt = (mtaux_t *) a->mt; int j, B, n_eval; if (mt == 0) return kann_eval(a, KANN_F_OUT, 0); B = kad_sync_dim(a->n, a->v, -1); /* get the current batch size */ mt_kickoff(a, 0, 0, 1); n_eval = kann_eval(mt->mt[0].a, KANN_F_OUT, 0); while (mt->n_idle < mt->n_threads - 1); /* busy waiting until all threads in sync */ - for (j = 0; j < a->n; ++j) { /* copy output values back */ + for (j = 0; j < a->n; ++j) { /* copy output values back */ kad_node_t *p = a->v[j]; if (p->ext_flag & KANN_F_OUT) { int i, t, k, d0 = p->d[0] / B, d1 = 1; /* for RNN, p->d[0] may equal unroll_len * batch_size */ @@ -438,7 +463,7 @@ int kann_eval_out(kann_t *a) int kann_class_error(const kann_t *ann, int *base) { - mtaux_t *mt = (mtaux_t*)ann->mt; + mtaux_t *mt = (mtaux_t *) ann->mt; int i, n_err = 0, b = 0; if (mt == 0) return kann_class_error_core(ann, base); for (i = 0; i < mt->n_threads; ++i) { @@ -450,7 +475,7 @@ int kann_class_error(const kann_t *ann, int *base) void kann_switch(kann_t *ann, int is_train) { - mtaux_t *mt = (mtaux_t*)ann->mt; + mtaux_t *mt = (mtaux_t *) ann->mt; int i; if (mt == 0) { kann_switch_core(ann, is_train); @@ -460,11 +485,25 @@ void kann_switch(kann_t *ann, int is_train) kann_switch_core(mt->mt[i].a, is_train); } #else -void kann_mt(kann_t *ann, int n_threads, int max_batch_size) {} -float kann_cost(kann_t *a, int cost_label, int cal_grad) { return kann_cost_core(a, cost_label, cal_grad); } -int kann_eval_out(kann_t *a) { return kann_eval(a, KANN_F_OUT, 0); } -int kann_class_error(const kann_t *a, int *base) { return kann_class_error_core(a, base); } -void kann_switch(kann_t *ann, int is_train) { return kann_switch_core(ann, is_train); } +void kann_mt(kann_t *ann, int n_threads, int max_batch_size) +{ +} +float kann_cost(kann_t *a, int cost_label, int cal_grad) +{ + return kann_cost_core(a, cost_label, cal_grad); +} +int kann_eval_out(kann_t *a) +{ + return kann_eval(a, KANN_F_OUT, 0); +} +int kann_class_error(const kann_t *a, int *base) +{ + return kann_class_error_core(a, base); +} +void kann_switch(kann_t *ann, int is_train) +{ + return kann_switch_core(ann, is_train); +} #endif /*********************** @@ -485,7 +524,7 @@ void kann_save_fp(FILE *fp, kann_t *ann) void kann_save(const char *fn, kann_t *ann) { FILE *fp; - fp = fn && strcmp(fn, "-")? fopen(fn, "wb") : stdout; + fp = fn && strcmp(fn, "-") ? fopen(fn, "wb") : stdout; kann_save_fp(fp, ann); fclose(fp); } @@ -500,13 +539,13 @@ kann_t *kann_load_fp(FILE *fp) if (strncmp(magic, KANN_MAGIC, 4) != 0) { return 0; } - ann = (kann_t*)calloc(1, sizeof(kann_t)); + ann = (kann_t *) calloc(1, sizeof(kann_t)); ann->v = kad_load(fp, &ann->n); n_var = kad_size_var(ann->n, ann->v); n_const = kad_size_const(ann->n, ann->v); - ann->x = (float*)malloc(n_var * sizeof(float)); - ann->g = (float*)calloc(n_var, sizeof(float)); - ann->c = (float*)malloc(n_const * sizeof(float)); + ann->x = (float *) malloc(n_var * sizeof(float)); + ann->g = (float *) calloc(n_var, sizeof(float)); + ann->c = (float *) malloc(n_const * sizeof(float)); (void) !fread(ann->x, sizeof(float), n_var, fp); (void) !fread(ann->c, sizeof(float), n_const, fp); kad_ext_sync(ann->n, ann->v, ann->x, ann->g, ann->c); @@ -517,7 +556,7 @@ kann_t *kann_load(const char *fn) { FILE *fp; kann_t *ann; - fp = fn && strcmp(fn, "-")? fopen(fn, "rb") : stdin; + fp = fn && strcmp(fn, "-") ? fopen(fn, "rb") : stdin; ann = kann_load_fp(fp); fclose(fp); return ann; @@ -531,23 +570,24 @@ kann_t *kann_load(const char *fn) kad_node_t *kann_new_leaf_array(int *offset, kad_node_p *par, uint8_t flag, float x0_01, int n_d, int32_t d[KAD_MAX_DIM]) { - int i, len, off = offset && par? *offset : -1; + int i, len, off = offset && par ? *offset : -1; kad_node_t *p; if (off >= 0 && par[off]) return par[(*offset)++]; - p = (kad_node_t*)calloc(1, sizeof(kad_node_t)); + p = (kad_node_t *) calloc(1, sizeof(kad_node_t)); p->n_d = n_d, p->flag = flag; memcpy(p->d, d, n_d * sizeof(int32_t)); len = kad_len(p); - p->x = (float*)calloc(len, sizeof(float)); + p->x = (float *) calloc(len, sizeof(float)); if (p->n_d <= 1) { for (i = 0; i < len; ++i) p->x[i] = x0_01; - } else { + } + else { double sdev_inv; - sdev_inv = 1.0 / sqrt((double)len / p->d[0]); + sdev_inv = 1.0 / sqrt((double) len / p->d[0]); for (i = 0; i < len; ++i) - p->x[i] = (float)(kad_drand_normal(0) * sdev_inv); + p->x[i] = (float) (kad_drand_normal(0) * sdev_inv); } if (off >= 0) par[off] = p, ++(*offset); return p; @@ -557,7 +597,9 @@ kad_node_t *kann_new_leaf2(int *offset, kad_node_p *par, uint8_t flag, float x0_ { int32_t i, d[KAD_MAX_DIM]; va_list ap; - va_start(ap, n_d); for (i = 0; i < n_d; ++i) d[i] = va_arg(ap, int); va_end(ap); + va_start(ap, n_d); + for (i = 0; i < n_d; ++i) d[i] = va_arg(ap, int); + va_end(ap); return kann_new_leaf_array(offset, par, flag, x0_01, n_d, d); } @@ -565,7 +607,7 @@ kad_node_t *kann_layer_dense2(int *offset, kad_node_p *par, kad_node_t *in, int { int n0; kad_node_t *w, *b; - n0 = in->n_d >= 2? kad_len(in) / in->d[0] : kad_len(in); + n0 = in->n_d >= 2 ? kad_len(in) / in->d[0] : kad_len(in); w = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 2, n1, n0); b = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 1, n1); return kad_add(kad_cmul(in, w), b); @@ -583,27 +625,27 @@ kad_node_t *kann_layer_layernorm2(int *offset, kad_node_t **par, kad_node_t *in) { int n0; kad_node_t *alpha, *beta; - n0 = in->n_d >= 2? kad_len(in) / in->d[0] : kad_len(in); + n0 = in->n_d >= 2 ? kad_len(in) / in->d[0] : kad_len(in); alpha = kann_new_leaf2(offset, par, KAD_VAR, 1.0f, 1, n0); - beta = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 1, n0); + beta = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 1, n0); return kad_add(kad_mul(kad_stdnorm(in), alpha), beta); } static inline kad_node_t *cmul_norm2(int *offset, kad_node_t **par, kad_node_t *x, kad_node_t *w, int use_norm) { - return use_norm? kann_layer_layernorm2(offset, par, kad_cmul(x, w)) : kad_cmul(x, w); + return use_norm ? kann_layer_layernorm2(offset, par, kad_cmul(x, w)) : kad_cmul(x, w); } kad_node_t *kann_layer_rnn2(int *offset, kad_node_t **par, kad_node_t *in, kad_node_t *h0, int rnn_flag) { - int n0, n1 = h0->d[h0->n_d-1], use_norm = !!(rnn_flag & KANN_RNN_NORM); + int n0, n1 = h0->d[h0->n_d - 1], use_norm = !!(rnn_flag & KANN_RNN_NORM); kad_node_t *t, *w, *u, *b, *out; u = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 2, n1, n1); b = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 1, n1); t = cmul_norm2(offset, par, h0, u, use_norm); if (in) { - n0 = in->n_d >= 2? kad_len(in) / in->d[0] : kad_len(in); + n0 = in->n_d >= 2 ? kad_len(in) / in->d[0] : kad_len(in); w = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 2, n1, n0); t = kad_add(cmul_norm2(offset, par, in, w, use_norm), t); } @@ -614,10 +656,10 @@ kad_node_t *kann_layer_rnn2(int *offset, kad_node_t **par, kad_node_t *in, kad_n kad_node_t *kann_layer_gru2(int *offset, kad_node_t **par, kad_node_t *in, kad_node_t *h0, int rnn_flag) { - int n0 = 0, n1 = h0->d[h0->n_d-1], use_norm = !!(rnn_flag & KANN_RNN_NORM); + int n0 = 0, n1 = h0->d[h0->n_d - 1], use_norm = !!(rnn_flag & KANN_RNN_NORM); kad_node_t *t, *r, *z, *w, *u, *b, *s, *out; - if (in) n0 = in->n_d >= 2? kad_len(in) / in->d[0] : kad_len(in); + if (in) n0 = in->n_d >= 2 ? kad_len(in) / in->d[0] : kad_len(in); /* z = sigm(x_t * W_z + h_{t-1} * U_z + b_z) */ u = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 2, n1, n1); b = kann_new_leaf2(offset, par, KAD_VAR, 0.0f, 1, n1); @@ -657,16 +699,36 @@ kad_node_t *kann_new_leaf(uint8_t flag, float x0_01, int n_d, ...) { int32_t i, d[KAD_MAX_DIM]; va_list ap; - va_start(ap, n_d); for (i = 0; i < n_d; ++i) d[i] = va_arg(ap, int); va_end(ap); + va_start(ap, n_d); + for (i = 0; i < n_d; ++i) d[i] = va_arg(ap, int); + va_end(ap); return kann_new_leaf_array(0, 0, flag, x0_01, n_d, d); } -kad_node_t *kann_new_scalar(uint8_t flag, float x) { return kann_new_leaf(flag, x, 0); } -kad_node_t *kann_new_weight(int n_row, int n_col) { return kann_new_leaf(KAD_VAR, 0.0f, 2, n_row, n_col); } -kad_node_t *kann_new_vec(int n, float x) { return kann_new_leaf(KAD_VAR, x, 1, n); } -kad_node_t *kann_new_bias(int n) { return kann_new_vec(n, 0.0f); } -kad_node_t *kann_new_weight_conv2d(int n_out, int n_in, int k_row, int k_col) { return kann_new_leaf(KAD_VAR, 0.0f, 4, n_out, n_in, k_row, k_col); } -kad_node_t *kann_new_weight_conv1d(int n_out, int n_in, int kernel_len) { return kann_new_leaf(KAD_VAR, 0.0f, 3, n_out, n_in, kernel_len); } +kad_node_t *kann_new_scalar(uint8_t flag, float x) +{ + return kann_new_leaf(flag, x, 0); +} +kad_node_t *kann_new_weight(int n_row, int n_col) +{ + return kann_new_leaf(KAD_VAR, 0.0f, 2, n_row, n_col); +} +kad_node_t *kann_new_vec(int n, float x) +{ + return kann_new_leaf(KAD_VAR, x, 1, n); +} +kad_node_t *kann_new_bias(int n) +{ + return kann_new_vec(n, 0.0f); +} +kad_node_t *kann_new_weight_conv2d(int n_out, int n_in, int k_row, int k_col) +{ + return kann_new_leaf(KAD_VAR, 0.0f, 4, n_out, n_in, k_row, k_col); +} +kad_node_t *kann_new_weight_conv1d(int n_out, int n_in, int kernel_len) +{ + return kann_new_leaf(KAD_VAR, 0.0f, 3, n_out, n_in, kernel_len); +} kad_node_t *kann_layer_input(int n1) { @@ -676,23 +738,32 @@ kad_node_t *kann_layer_input(int n1) return t; } -kad_node_t *kann_layer_dense(kad_node_t *in, int n1) { return kann_layer_dense2(0, 0, in, n1); } -kad_node_t *kann_layer_dropout(kad_node_t *t, float r) { return kann_layer_dropout2(0, 0, t, r); } -kad_node_t *kann_layer_layernorm(kad_node_t *in) { return kann_layer_layernorm2(0, 0, in); } +kad_node_t *kann_layer_dense(kad_node_t *in, int n1) +{ + return kann_layer_dense2(0, 0, in, n1); +} +kad_node_t *kann_layer_dropout(kad_node_t *t, float r) +{ + return kann_layer_dropout2(0, 0, t, r); +} +kad_node_t *kann_layer_layernorm(kad_node_t *in) +{ + return kann_layer_layernorm2(0, 0, in); +} kad_node_t *kann_layer_rnn(kad_node_t *in, int n1, int rnn_flag) { kad_node_t *h0; - h0 = (rnn_flag & KANN_RNN_VAR_H0)? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); - h0->x = (float*)calloc(n1, sizeof(float)); + h0 = (rnn_flag & KANN_RNN_VAR_H0) ? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); + h0->x = (float *) calloc(n1, sizeof(float)); return kann_layer_rnn2(0, 0, in, h0, rnn_flag); } kad_node_t *kann_layer_gru(kad_node_t *in, int n1, int rnn_flag) { kad_node_t *h0; - h0 = (rnn_flag & KANN_RNN_VAR_H0)? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); - h0->x = (float*)calloc(n1, sizeof(float)); + h0 = (rnn_flag & KANN_RNN_VAR_H0) ? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); + h0->x = (float *) calloc(n1, sizeof(float)); return kann_layer_gru2(0, 0, in, h0, rnn_flag); } @@ -705,13 +776,13 @@ kad_node_t *kann_layer_lstm(kad_node_t *in, int n1, int rnn_flag) { int n0; kad_node_t *i, *f, *o, *g, *w, *u, *b, *h0, *c0, *c, *out; - kad_node_t *(*cmul)(kad_node_t*, kad_node_t*) = (rnn_flag & KANN_RNN_NORM)? kann_cmul_norm : kad_cmul; + kad_node_t *(*cmul)(kad_node_t *, kad_node_t *) = (rnn_flag & KANN_RNN_NORM) ? kann_cmul_norm : kad_cmul; - n0 = in->n_d >= 2? kad_len(in) / in->d[0] : kad_len(in); - h0 = (rnn_flag & KANN_RNN_VAR_H0)? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); - h0->x = (float*)calloc(n1, sizeof(float)); - c0 = (rnn_flag & KANN_RNN_VAR_H0)? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); - c0->x = (float*)calloc(n1, sizeof(float)); + n0 = in->n_d >= 2 ? kad_len(in) / in->d[0] : kad_len(in); + h0 = (rnn_flag & KANN_RNN_VAR_H0) ? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); + h0->x = (float *) calloc(n1, sizeof(float)); + c0 = (rnn_flag & KANN_RNN_VAR_H0) ? kad_var(0, 0, 2, 1, n1) : kad_const(0, 2, 1, n1); + c0->x = (float *) calloc(n1, sizeof(float)); /* i = sigm(x_t * W_i + h_{t-1} * U_i + b_i) */ w = kann_new_weight(n1, n0); @@ -766,18 +837,21 @@ kad_node_t *kann_layer_cost(kad_node_t *t, int n_out, int cost_type) if (cost_type == KANN_C_MSE) { cost = kad_mse(t, truth); - } else if (cost_type == KANN_C_CEB) { + } + else if (cost_type == KANN_C_CEB) { t = kad_sigm(t); cost = kad_ce_bin(t, truth); - } else if (cost_type == KANN_C_CEB_NEG) { + } + else if (cost_type == KANN_C_CEB_NEG) { t = kad_tanh(t); cost = kad_ce_bin_neg(t, truth); - } else if (cost_type == KANN_C_CEM) { + } + else if (cost_type == KANN_C_CEM) { t = kad_softmax(t); cost = kad_ce_multi(t, truth); } else { - assert (0); + assert(0); } t->ext_flag |= KANN_F_OUT; @@ -791,8 +865,8 @@ void kann_shuffle(int n, int *s) int i, j, t; for (i = 0; i < n; ++i) s[i] = i; for (i = n; i > 0; --i) { - j = (int)(i * kad_drand(0)); - t = s[j], s[j] = s[i-1], s[i-1] = t; + j = (int) (i * kad_drand(0)); + t = s[j], s[j] = s[i - 1], s[i - 1] = t; } } @@ -805,7 +879,7 @@ void kann_shuffle(int n, int *s) void kann_RMSprop(int n, float h0, const float *h, float decay, const float *g, float *t, float *r) { - int i, n4 = n>>2<<2; + int i, n4 = n >> 2 << 2; __m128 vh, vg, vr, vt, vd, vd1, tmp, vtiny; vh = _mm_set1_ps(h0); vd = _mm_set1_ps(decay); @@ -823,7 +897,7 @@ void kann_RMSprop(int n, float h0, const float *h, float decay, const float *g, } for (; i < n; ++i) { r[i] = (1. - decay) * g[i] * g[i] + decay * r[i]; - t[i] -= (h? h[i] : h0) / sqrtf(1e-6f + r[i]) * g[i]; + t[i] -= (h ? h[i] : h0) / sqrtf(1e-6f + r[i]) * g[i]; } } #else @@ -831,7 +905,7 @@ void kann_RMSprop(int n, float h0, const float *h, float decay, const float *g, { int i; for (i = 0; i < n; ++i) { - float lr = h? h[i] : h0; + float lr = h ? h[i] : h0; r[i] = (1.0f - decay) * g[i] * g[i] + decay * r[i]; t[i] -= lr / sqrtf(1e-6f + r[i]) * g[i]; } @@ -847,8 +921,8 @@ float kann_grad_clip(float thres, int n, float *g) s2 = sqrt(s2); if (s2 > thres) for (i = 0, s2 = 1.0 / s2; i < n; ++i) - g[i] *= (float)s2; - return (float)s2 / thres; + g[i] *= (float) s2; + return (float) s2 / thres; } /**************************************************************** @@ -868,21 +942,21 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, if (n_in < 0 || n_out < 0) return -1; n_var = kann_size_var(ann); n_const = kann_size_const(ann); - r = (float*)calloc(n_var, sizeof(float)); - shuf = (int*)malloc(n * sizeof(int)); - x = (float**)malloc(n * sizeof(float*)); - y = (float**)malloc(n * sizeof(float*)); + r = (float *) calloc(n_var, sizeof(float)); + shuf = (int *) malloc(n * sizeof(int)); + x = (float **) malloc(n * sizeof(float *)); + y = (float **) malloc(n * sizeof(float *)); kann_shuffle(n, shuf); for (j = 0; j < n; ++j) x[j] = _x[shuf[j]], y[j] = _y[shuf[j]]; - n_val = (int)(n * frac_val); + n_val = (int) (n * frac_val); n_train = n - n_val; - min_x = (float*)malloc(n_var * sizeof(float)); - min_c = (float*)malloc(n_const * sizeof(float)); + min_x = (float *) malloc(n_var * sizeof(float)); + min_c = (float *) malloc(n_const * sizeof(float)); - x1 = (float*)malloc(n_in * mini_size * sizeof(float)); - y1 = (float*)malloc(n_out * mini_size * sizeof(float)); - kann_feed_bind(ann, KANN_F_IN, 0, &x1); + x1 = (float *) malloc(n_in * mini_size * sizeof(float)); + y1 = (float *) malloc(n_out * mini_size * sizeof(float)); + kann_feed_bind(ann, KANN_F_IN, 0, &x1); kann_feed_bind(ann, KANN_F_TRUTH, 0, &y1); for (i = 0; i < max_epoch; ++i) { @@ -891,10 +965,10 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, kann_shuffle(n_train, shuf); kann_switch(ann, 1); while (n_proc < n_train) { - int b, c, ms = n_train - n_proc < mini_size? n_train - n_proc : mini_size; + int b, c, ms = n_train - n_proc < mini_size ? n_train - n_proc : mini_size; for (b = 0; b < ms; ++b) { - memcpy(&x1[b*n_in], x[shuf[n_proc+b]], n_in * sizeof(float)); - memcpy(&y1[b*n_out], y[shuf[n_proc+b]], n_out * sizeof(float)); + memcpy(&x1[b * n_in], x[shuf[n_proc + b]], n_in * sizeof(float)); + memcpy(&y1[b * n_out], y[shuf[n_proc + b]], n_out * sizeof(float)); } kann_set_batch_size(ann, ms); train_cost += kann_cost(ann, 0, 1) * ms; @@ -907,10 +981,10 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, kann_switch(ann, 0); n_proc = 0; while (n_proc < n_val) { - int b, c, ms = n_val - n_proc < mini_size? n_val - n_proc : mini_size; + int b, c, ms = n_val - n_proc < mini_size ? n_val - n_proc : mini_size; for (b = 0; b < ms; ++b) { - memcpy(&x1[b*n_in], x[n_train+n_proc+b], n_in * sizeof(float)); - memcpy(&y1[b*n_out], y[n_train+n_proc+b], n_out * sizeof(float)); + memcpy(&x1[b * n_in], x[n_train + n_proc + b], n_in * sizeof(float)); + memcpy(&y1[b * n_out], y[n_train + n_proc + b], n_out * sizeof(float)); } kann_set_batch_size(ann, ms); val_cost += kann_cost(ann, 0, 0) * ms; @@ -919,6 +993,8 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, n_proc += ms; } if (n_val > 0) val_cost /= n_val; + (void) (n_train_err); + (void) (n_val_err); if (cb) { cb(i + 1, train_cost, val_cost, ud); #if 0 @@ -937,8 +1013,9 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, memcpy(min_x, ann->x, n_var * sizeof(float)); memcpy(min_c, ann->c, n_const * sizeof(float)); drop_streak = 0; - min_val_cost = (float)val_cost; - } else if (++drop_streak >= max_drop_streak) + min_val_cost = (float) val_cost; + } + else if (++drop_streak >= max_drop_streak) break; } } @@ -947,13 +1024,20 @@ int kann_train_fnn1(kann_t *ann, float lr, int mini_size, int max_epoch, memcpy(ann->c, min_c, n_const * sizeof(float)); } - free(min_c); free(min_x); free(y1); free(x1); free(y); free(x); free(shuf); free(r); + free(min_c); + free(min_x); + free(y1); + free(x1); + free(y); + free(x); + free(shuf); + free(r); return i; } float kann_cost_fnn1(kann_t *ann, int n, float **x, float **y) { - int n_in, n_out, n_proc = 0, mini_size = 64 < n? 64 : n; + int n_in, n_out, n_proc = 0, mini_size = 64 < n ? 64 : n; float *x1, *y1; double cost = 0.0; @@ -961,23 +1045,24 @@ float kann_cost_fnn1(kann_t *ann, int n, float **x, float **y) n_out = kann_dim_out(ann); if (n <= 0 || n_in < 0 || n_out < 0) return 0.0; - x1 = (float*)malloc(n_in * mini_size * sizeof(float)); - y1 = (float*)malloc(n_out * mini_size * sizeof(float)); - kann_feed_bind(ann, KANN_F_IN, 0, &x1); + x1 = (float *) malloc(n_in * mini_size * sizeof(float)); + y1 = (float *) malloc(n_out * mini_size * sizeof(float)); + kann_feed_bind(ann, KANN_F_IN, 0, &x1); kann_feed_bind(ann, KANN_F_TRUTH, 0, &y1); kann_switch(ann, 0); while (n_proc < n) { - int b, ms = n - n_proc < mini_size? n - n_proc : mini_size; + int b, ms = n - n_proc < mini_size ? n - n_proc : mini_size; for (b = 0; b < ms; ++b) { - memcpy(&x1[b*n_in], x[n_proc+b], n_in * sizeof(float)); - memcpy(&y1[b*n_out], y[n_proc+b], n_out * sizeof(float)); + memcpy(&x1[b * n_in], x[n_proc + b], n_in * sizeof(float)); + memcpy(&y1[b * n_out], y[n_proc + b], n_out * sizeof(float)); } kann_set_batch_size(ann, ms); cost += kann_cost(ann, 0, 0) * ms; n_proc += ms; } - free(y1); free(x1); - return (float)(cost / n); + free(y1); + free(x1); + return (float) (cost / n); } const float *kann_apply1(kann_t *a, float *x) diff --git a/contrib/librdns/curve.c b/contrib/librdns/curve.c index 19ec2508c..9fc345fb5 100644 --- a/contrib/librdns/curve.c +++ b/contrib/librdns/curve.c @@ -1,4 +1,20 @@ /* + * Copyright 2024 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* * Copyright (c) 2014, Vsevolod Stakhov * * All rights reserved. @@ -39,12 +55,11 @@ #include <tweetnacl.h> -void -randombytes(uint8_t *data, uint64_t len) +void randombytes(uint8_t *data, uint64_t len) { - ottery_rand_bytes (data, len); + ottery_rand_bytes(data, len); } -void sodium_memzero (uint8_t *data, uint64_t len) +void sodium_memzero(uint8_t *data, uint64_t len) { volatile uint8_t *p = data; @@ -54,14 +69,13 @@ void sodium_memzero (uint8_t *data, uint64_t len) } void sodium_init(void) { - } -ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data); -ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, - void *plugin_data, struct rdns_request **req_out); -void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data); -void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data); +ssize_t rdns_curve_send(struct rdns_request *req, void *plugin_data); +ssize_t rdns_curve_recv(struct rdns_io_channel *ioc, void *buf, size_t len, + void *plugin_data, struct rdns_request **req_out); +void rdns_curve_finish_request(struct rdns_request *req, void *plugin_data); +void rdns_curve_dtor(struct rdns_resolver *resolver, void *plugin_data); struct rdns_curve_entry { char *name; @@ -103,34 +117,36 @@ struct rdns_curve_ctx { }; static struct rdns_curve_client_key * -rdns_curve_client_key_new (struct rdns_curve_ctx *ctx) +rdns_curve_client_key_new(struct rdns_curve_ctx *ctx) { struct rdns_curve_client_key *new; struct rdns_curve_nm_entry *nm; struct rdns_curve_entry *entry, *tmp; - new = calloc (1, sizeof (struct rdns_curve_client_key)); - crypto_box_keypair (new->pk, new->sk); + new = calloc(1, sizeof(struct rdns_curve_client_key)); + crypto_box_keypair(new->pk, new->sk); - HASH_ITER (hh, ctx->entries, entry, tmp) { - nm = calloc (1, sizeof (struct rdns_curve_nm_entry)); + HASH_ITER(hh, ctx->entries, entry, tmp) + { + nm = calloc(1, sizeof(struct rdns_curve_nm_entry)); nm->entry = entry; - crypto_box_beforenm (nm->k, entry->pk, new->sk); + crypto_box_beforenm(nm->k, entry->pk, new->sk); - DL_APPEND (new->nms, nm); + DL_APPEND(new->nms, nm); } - new->counter = ottery_rand_uint64 (); + new->counter = ottery_rand_uint64(); return new; } static struct rdns_curve_nm_entry * -rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) +rdns_curve_find_nm(struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) { struct rdns_curve_nm_entry *nm; - DL_FOREACH (key->nms, nm) { + DL_FOREACH(key->nms, nm) + { if (nm->entry == entry) { return nm; } @@ -140,67 +156,68 @@ rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry * } static void -rdns_curve_client_key_free (struct rdns_curve_client_key *key) +rdns_curve_client_key_free(struct rdns_curve_client_key *key) { struct rdns_curve_nm_entry *nm, *tmp; - DL_FOREACH_SAFE (key->nms, nm, tmp) { - sodium_memzero (nm->k, sizeof (nm->k)); - free (nm); + DL_FOREACH_SAFE(key->nms, nm, tmp) + { + sodium_memzero(nm->k, sizeof(nm->k)); + free(nm); } - sodium_memzero (key->sk, sizeof (key->sk)); - free (key); + sodium_memzero(key->sk, sizeof(key->sk)); + free(key); } -struct rdns_curve_ctx* -rdns_curve_ctx_new (double key_refresh_interval) +struct rdns_curve_ctx * +rdns_curve_ctx_new(double key_refresh_interval) { struct rdns_curve_ctx *new; - new = calloc (1, sizeof (struct rdns_curve_ctx)); + new = calloc(1, sizeof(struct rdns_curve_ctx)); new->key_refresh_interval = key_refresh_interval; return new; } -void -rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, - const char *name, const unsigned char *pubkey) +void rdns_curve_ctx_add_key(struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) { struct rdns_curve_entry *entry; bool success = true; - entry = malloc (sizeof (struct rdns_curve_entry)); + entry = malloc(sizeof(struct rdns_curve_entry)); if (entry != NULL) { - entry->name = strdup (name); + entry->name = strdup(name); if (entry->name == NULL) { success = false; } - memcpy (entry->pk, pubkey, sizeof (entry->pk)); + memcpy(entry->pk, pubkey, sizeof(entry->pk)); if (success) { - HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry); + HASH_ADD_KEYPTR(hh, ctx->entries, entry->name, strlen(entry->name), entry); } } } -#define rdns_curve_write_hex(in, out, offset, base) do { \ - *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ -} while (0) +#define rdns_curve_write_hex(in, out, offset, base) \ + do { \ + *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ + } while (0) static bool -rdns_curve_hex_to_byte (const char *in, unsigned char *out) +rdns_curve_hex_to_byte(const char *in, unsigned char *out) { int i; - for (i = 0; i <= 1; i ++) { + for (i = 0; i <= 1; i++) { if (in[i] >= '0' && in[i] <= '9') { - rdns_curve_write_hex (in, out, i, '0'); + rdns_curve_write_hex(in, out, i, '0'); } else if (in[i] >= 'a' && in[i] <= 'f') { - rdns_curve_write_hex (in, out, i, 'a' - 10); + rdns_curve_write_hex(in, out, i, 'a' - 10); } else if (in[i] >= 'A' && in[i] <= 'F') { - rdns_curve_write_hex (in, out, i, 'A' - 10); + rdns_curve_write_hex(in, out, i, 'A' - 10); } else { return false; @@ -212,16 +229,16 @@ rdns_curve_hex_to_byte (const char *in, unsigned char *out) #undef rdns_curve_write_hex unsigned char * -rdns_curve_key_from_hex (const char *hex) +rdns_curve_key_from_hex(const char *hex) { - unsigned int len = strlen (hex), i; + unsigned int len = strlen(hex), i; unsigned char *res = NULL; if (len == crypto_box_PUBLICKEYBYTES * 2) { - res = calloc (1, crypto_box_PUBLICKEYBYTES); - for (i = 0; i < crypto_box_PUBLICKEYBYTES; i ++) { - if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) { - free (res); + res = calloc(1, crypto_box_PUBLICKEYBYTES); + for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) { + if (!rdns_curve_hex_to_byte(&hex[i * 2], &res[i])) { + free(res); return NULL; } } @@ -230,35 +247,34 @@ rdns_curve_key_from_hex (const char *hex) return res; } -void -rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +void rdns_curve_ctx_destroy(struct rdns_curve_ctx *ctx) { struct rdns_curve_entry *entry, *tmp; - HASH_ITER (hh, ctx->entries, entry, tmp) { - free (entry->name); - free (entry); + HASH_ITER(hh, ctx->entries, entry, tmp) + { + free(entry->name); + free(entry); } - free (ctx); + free(ctx); } static void -rdns_curve_refresh_key_callback (void *user_data) +rdns_curve_refresh_key_callback(void *user_data) { struct rdns_curve_ctx *ctx = user_data; struct rdns_resolver *resolver; resolver = ctx->resolver; - rdns_info ("refresh dnscurve keys"); - REF_RELEASE (ctx->cur_key); - ctx->cur_key = rdns_curve_client_key_new (ctx); - REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + rdns_info("refresh dnscurve keys"); + REF_RELEASE(ctx->cur_key); + ctx->cur_key = rdns_curve_client_key_new(ctx); + REF_INIT_RETAIN(ctx->cur_key, rdns_curve_client_key_free); } -void -rdns_curve_register_plugin (struct rdns_resolver *resolver, - struct rdns_curve_ctx *ctx) +void rdns_curve_register_plugin(struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) { struct rdns_plugin *plugin; @@ -266,7 +282,7 @@ rdns_curve_register_plugin (struct rdns_resolver *resolver, return; } - plugin = calloc (1, sizeof (struct rdns_plugin)); + plugin = calloc(1, sizeof(struct rdns_plugin)); if (plugin != NULL) { plugin->data = ctx; plugin->type = RDNS_PLUGIN_CURVE; @@ -274,24 +290,24 @@ rdns_curve_register_plugin (struct rdns_resolver *resolver, plugin->cb.curve_plugin.recv_cb = rdns_curve_recv; plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request; plugin->dtor = rdns_curve_dtor; - sodium_init (); - ctx->cur_key = rdns_curve_client_key_new (ctx); - REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + sodium_init(); + ctx->cur_key = rdns_curve_client_key_new(ctx); + REF_INIT_RETAIN(ctx->cur_key, rdns_curve_client_key_free); if (ctx->key_refresh_interval > 0) { - ctx->key_refresh_event = resolver->async->add_periodic ( - resolver->async->data, ctx->key_refresh_interval, - rdns_curve_refresh_key_callback, ctx); + ctx->key_refresh_event = resolver->async->add_periodic( + resolver->async->data, ctx->key_refresh_interval, + rdns_curve_refresh_key_callback, ctx); } ctx->resolver = resolver; - rdns_resolver_register_plugin (resolver, plugin); + rdns_resolver_register_plugin(resolver, plugin); } } ssize_t -rdns_curve_send (struct rdns_request *req, void *plugin_data) +rdns_curve_send(struct rdns_request *req, void *plugin_data) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; struct rdns_curve_entry *entry; struct iovec iov[4]; unsigned char *m; @@ -301,62 +317,62 @@ rdns_curve_send (struct rdns_request *req, void *plugin_data) ssize_t ret, boxed_len; /* Check for key */ - HASH_FIND_STR (ctx->entries, req->io->srv->name, entry); + HASH_FIND_STR(ctx->entries, req->io->srv->name, entry); if (entry != NULL) { - nm = rdns_curve_find_nm (ctx->cur_key, entry); - creq = malloc (sizeof (struct rdns_curve_request)); + nm = rdns_curve_find_nm(ctx->cur_key, entry); + creq = malloc(sizeof(struct rdns_curve_request)); if (creq == NULL) { return -1; } boxed_len = req->pos + crypto_box_ZEROBYTES; - m = malloc (boxed_len); + m = malloc(boxed_len); if (m == NULL) { return -1; } /* Ottery is faster than sodium native PRG that uses /dev/random only */ - memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t)); - ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t)); - sodium_memzero (creq->nonce + 12, crypto_box_NONCEBYTES - 12); + memcpy(creq->nonce, &ctx->cur_key->counter, sizeof(uint64_t)); + ottery_rand_bytes(creq->nonce + sizeof(uint64_t), 12 - sizeof(uint64_t)); + sodium_memzero(creq->nonce + 12, crypto_box_NONCEBYTES - 12); - sodium_memzero (m, crypto_box_ZEROBYTES); - memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos); + sodium_memzero(m, crypto_box_ZEROBYTES); + memcpy(m + crypto_box_ZEROBYTES, req->packet, req->pos); - if (crypto_box_afternm (m, m, boxed_len, - creq->nonce, nm->k) == -1) { - sodium_memzero (m, boxed_len); - free (m); + if (crypto_box_afternm(m, m, boxed_len, + creq->nonce, nm->k) == -1) { + sodium_memzero(m, boxed_len); + free(m); return -1; } creq->key = ctx->cur_key; - REF_RETAIN (ctx->cur_key); + REF_RETAIN(ctx->cur_key); creq->entry = entry; creq->req = req; creq->nm = nm; - HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq); + HASH_ADD_KEYPTR(hh, ctx->requests, creq->nonce, 12, creq); req->curve_plugin_data = creq; - ctx->cur_key->counter ++; - ctx->cur_key->uses ++; + ctx->cur_key->counter++; + ctx->cur_key->uses++; /* Now form a dnscurve packet */ - iov[0].iov_base = (void *)qmagic; - iov[0].iov_len = sizeof (qmagic) - 1; + iov[0].iov_base = (void *) qmagic; + iov[0].iov_len = sizeof(qmagic) - 1; iov[1].iov_base = ctx->cur_key->pk; - iov[1].iov_len = sizeof (ctx->cur_key->pk); + iov[1].iov_len = sizeof(ctx->cur_key->pk); iov[2].iov_base = creq->nonce; iov[2].iov_len = 12; iov[3].iov_base = m + crypto_box_BOXZEROBYTES; iov[3].iov_len = boxed_len - crypto_box_BOXZEROBYTES; - ret = writev (req->io->sock, iov, sizeof (iov) / sizeof (iov[0])); - sodium_memzero (m, boxed_len); - free (m); + ret = writev(req->io->sock, iov, sizeof(iov) / sizeof(iov[0])); + sodium_memzero(m, boxed_len); + free(m); } else { - ret = write (req->io->sock, req->packet, req->pos); + ret = write(req->io->sock, req->packet, req->pos); req->curve_plugin_data = NULL; } @@ -364,10 +380,10 @@ rdns_curve_send (struct rdns_request *req, void *plugin_data) } ssize_t -rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, - struct rdns_request **req_out) +rdns_curve_recv(struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, + struct rdns_request **req_out) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; ssize_t ret, boxlen; static const char rmagic[] = "R6fnvWJ8"; unsigned char *p, *box; @@ -376,71 +392,69 @@ rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugi struct rdns_resolver *resolver; resolver = ctx->resolver; - ret = read (ioc->sock, buf, len); + ret = read(ioc->sock, buf, len); if (ret <= 0 || ret < 64) { /* Definitely not a DNSCurve packet */ return ret; } - if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) { + if (memcmp(buf, rmagic, sizeof(rmagic) - 1) == 0) { /* Likely DNSCurve packet */ - p = ((unsigned char *)buf) + 8; - HASH_FIND (hh, ctx->requests, p, 12, creq); + p = ((unsigned char *) buf) + 8; + HASH_FIND(hh, ctx->requests, p, 12, creq); if (creq == NULL) { - rdns_info ("unable to find nonce in the internal hash"); + rdns_info("unable to find nonce in the internal hash"); return ret; } - memcpy (enonce, p, crypto_box_NONCEBYTES); + memcpy(enonce, p, crypto_box_NONCEBYTES); p += crypto_box_NONCEBYTES; boxlen = ret - crypto_box_NONCEBYTES + - crypto_box_BOXZEROBYTES - - sizeof (rmagic) + 1; + crypto_box_BOXZEROBYTES - + sizeof(rmagic) + 1; if (boxlen < 0) { return ret; } - box = malloc (boxlen); - sodium_memzero (box, crypto_box_BOXZEROBYTES); - memcpy (box + crypto_box_BOXZEROBYTES, p, - boxlen - crypto_box_BOXZEROBYTES); - - if (crypto_box_open_afternm (box, box, boxlen, enonce, creq->nm->k) != -1) { - memcpy (buf, box + crypto_box_ZEROBYTES, - boxlen - crypto_box_ZEROBYTES); + box = malloc(boxlen); + sodium_memzero(box, crypto_box_BOXZEROBYTES); + memcpy(box + crypto_box_BOXZEROBYTES, p, + boxlen - crypto_box_BOXZEROBYTES); + + if (crypto_box_open_afternm(box, box, boxlen, enonce, creq->nm->k) != -1) { + memcpy(buf, box + crypto_box_ZEROBYTES, + boxlen - crypto_box_ZEROBYTES); ret = boxlen - crypto_box_ZEROBYTES; *req_out = creq->req; } else { - rdns_info ("unable open cryptobox of size %d", (int)boxlen); + rdns_info("unable open cryptobox of size %d", (int) boxlen); } - free (box); + free(box); } return ret; } -void -rdns_curve_finish_request (struct rdns_request *req, void *plugin_data) +void rdns_curve_finish_request(struct rdns_request *req, void *plugin_data) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; struct rdns_curve_request *creq = req->curve_plugin_data; if (creq != NULL) { - REF_RELEASE (creq->key); - HASH_DELETE (hh, ctx->requests, creq); + REF_RELEASE(creq->key); + HASH_DELETE(hh, ctx->requests, creq); } } -void -rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data) +void rdns_curve_dtor(struct rdns_resolver *resolver, void *plugin_data) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; if (ctx->key_refresh_event != NULL) { - resolver->async->del_periodic (resolver->async->data, - ctx->key_refresh_event); + resolver->async->del_periodic(resolver->async->data, + ctx->key_refresh_event); } - REF_RELEASE (ctx->cur_key); + REF_RELEASE(ctx->cur_key); } #elif defined(USE_RSPAMD_CRYPTOBOX) @@ -454,13 +468,13 @@ rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data) #define crypto_box_BOXZEROBYTES 16 #endif -ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data, - struct sockaddr *saddr, socklen_t slen); -ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, - void *plugin_data, struct rdns_request **req_out, - struct sockaddr *saddr, socklen_t slen); -void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data); -void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data); +ssize_t rdns_curve_send(struct rdns_request *req, void *plugin_data, + struct sockaddr *saddr, socklen_t slen); +ssize_t rdns_curve_recv(struct rdns_io_channel *ioc, void *buf, size_t len, + void *plugin_data, struct rdns_request **req_out, + struct sockaddr *saddr, socklen_t slen); +void rdns_curve_finish_request(struct rdns_request *req, void *plugin_data); +void rdns_curve_dtor(struct rdns_resolver *resolver, void *plugin_data); struct rdns_curve_entry { char *name; @@ -502,35 +516,36 @@ struct rdns_curve_ctx { }; static struct rdns_curve_client_key * -rdns_curve_client_key_new (struct rdns_curve_ctx *ctx) +rdns_curve_client_key_new(struct rdns_curve_ctx *ctx) { struct rdns_curve_client_key *new; struct rdns_curve_nm_entry *nm; struct rdns_curve_entry *entry, *tmp; - new = calloc (1, sizeof (struct rdns_curve_client_key)); - rspamd_cryptobox_keypair (new->pk, new->sk, RSPAMD_CRYPTOBOX_MODE_25519); + new = calloc(1, sizeof(struct rdns_curve_client_key)); + rspamd_cryptobox_keypair(new->pk, new->sk); - HASH_ITER (hh, ctx->entries, entry, tmp) { - nm = calloc (1, sizeof (struct rdns_curve_nm_entry)); + HASH_ITER(hh, ctx->entries, entry, tmp) + { + nm = calloc(1, sizeof(struct rdns_curve_nm_entry)); nm->entry = entry; - rspamd_cryptobox_nm (nm->k, entry->pk, new->sk, - RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_nm(nm->k, entry->pk, new->sk); - DL_APPEND (new->nms, nm); + DL_APPEND(new->nms, nm); } - new->counter = ottery_rand_uint64 (); + new->counter = ottery_rand_uint64(); return new; } static struct rdns_curve_nm_entry * -rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) +rdns_curve_find_nm(struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) { struct rdns_curve_nm_entry *nm; - DL_FOREACH (key->nms, nm) { + DL_FOREACH(key->nms, nm) + { if (nm->entry == entry) { return nm; } @@ -540,68 +555,69 @@ rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry * } static void -rdns_curve_client_key_free (struct rdns_curve_client_key *key) +rdns_curve_client_key_free(struct rdns_curve_client_key *key) { struct rdns_curve_nm_entry *nm, *tmp; - DL_FOREACH_SAFE (key->nms, nm, tmp) { - rspamd_explicit_memzero (nm->k, sizeof (nm->k)); - free (nm); + DL_FOREACH_SAFE(key->nms, nm, tmp) + { + rspamd_explicit_memzero(nm->k, sizeof(nm->k)); + free(nm); } - rspamd_explicit_memzero (key->sk, sizeof (key->sk)); - free (key); + rspamd_explicit_memzero(key->sk, sizeof(key->sk)); + free(key); } -struct rdns_curve_ctx* -rdns_curve_ctx_new (double key_refresh_interval) +struct rdns_curve_ctx * +rdns_curve_ctx_new(double key_refresh_interval) { struct rdns_curve_ctx *new; - new = calloc (1, sizeof (struct rdns_curve_ctx)); + new = calloc(1, sizeof(struct rdns_curve_ctx)); new->key_refresh_interval = key_refresh_interval; return new; } -void -rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, - const char *name, const unsigned char *pubkey) +void rdns_curve_ctx_add_key(struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) { struct rdns_curve_entry *entry; bool success = true; - entry = malloc (sizeof (struct rdns_curve_entry)); + entry = malloc(sizeof(struct rdns_curve_entry)); if (entry != NULL) { - entry->name = strdup (name); + entry->name = strdup(name); if (entry->name == NULL) { success = false; } - memcpy (entry->pk, pubkey, sizeof (entry->pk)); + memcpy(entry->pk, pubkey, sizeof(entry->pk)); if (success) { - HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry); + HASH_ADD_KEYPTR(hh, ctx->entries, entry->name, strlen(entry->name), entry); } } } -#define rdns_curve_write_hex(in, out, offset, base) do { \ - *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ -} while (0) +#define rdns_curve_write_hex(in, out, offset, base) \ + do { \ + *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ + } while (0) static bool -rdns_curve_hex_to_byte (const char *in, unsigned char *out) +rdns_curve_hex_to_byte(const char *in, unsigned char *out) { int i; - for (i = 0; i <= 1; i ++) { + for (i = 0; i <= 1; i++) { if (in[i] >= '0' && in[i] <= '9') { - rdns_curve_write_hex (in, out, i, '0'); + rdns_curve_write_hex(in, out, i, '0'); } else if (in[i] >= 'a' && in[i] <= 'f') { - rdns_curve_write_hex (in, out, i, 'a' - 10); + rdns_curve_write_hex(in, out, i, 'a' - 10); } else if (in[i] >= 'A' && in[i] <= 'F') { - rdns_curve_write_hex (in, out, i, 'A' - 10); + rdns_curve_write_hex(in, out, i, 'A' - 10); } else { return false; @@ -613,18 +629,18 @@ rdns_curve_hex_to_byte (const char *in, unsigned char *out) #undef rdns_curve_write_hex unsigned char * -rdns_curve_key_from_hex (const char *hex) +rdns_curve_key_from_hex(const char *hex) { - unsigned int len = strlen (hex), i; + unsigned int len = strlen(hex), i; unsigned char *res = NULL; - if (len == rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519) * 2) { - res = calloc (1, rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); + if (len == crypto_box_publickeybytes() * 2) { + res = calloc(1, crypto_box_publickeybytes()); for (i = 0; - i < rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519); - i ++) { - if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) { - free (res); + i < crypto_box_publickeybytes(); + i++) { + if (!rdns_curve_hex_to_byte(&hex[i * 2], &res[i])) { + free(res); return NULL; } } @@ -633,35 +649,34 @@ rdns_curve_key_from_hex (const char *hex) return res; } -void -rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +void rdns_curve_ctx_destroy(struct rdns_curve_ctx *ctx) { struct rdns_curve_entry *entry, *tmp; - HASH_ITER (hh, ctx->entries, entry, tmp) { - free (entry->name); - free (entry); + HASH_ITER(hh, ctx->entries, entry, tmp) + { + free(entry->name); + free(entry); } - free (ctx); + free(ctx); } static void -rdns_curve_refresh_key_callback (void *user_data) +rdns_curve_refresh_key_callback(void *user_data) { struct rdns_curve_ctx *ctx = user_data; struct rdns_resolver *resolver; resolver = ctx->resolver; - rdns_info ("refresh dnscurve keys"); - REF_RELEASE (ctx->cur_key); - ctx->cur_key = rdns_curve_client_key_new (ctx); - REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + rdns_info("refresh dnscurve keys"); + REF_RELEASE(ctx->cur_key); + ctx->cur_key = rdns_curve_client_key_new(ctx); + REF_INIT_RETAIN(ctx->cur_key, rdns_curve_client_key_free); } -void -rdns_curve_register_plugin (struct rdns_resolver *resolver, - struct rdns_curve_ctx *ctx) +void rdns_curve_register_plugin(struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) { struct rdns_plugin *plugin; @@ -669,7 +684,7 @@ rdns_curve_register_plugin (struct rdns_resolver *resolver, return; } - plugin = calloc (1, sizeof (struct rdns_plugin)); + plugin = calloc(1, sizeof(struct rdns_plugin)); if (plugin != NULL) { plugin->data = ctx; plugin->type = RDNS_PLUGIN_CURVE; @@ -677,24 +692,24 @@ rdns_curve_register_plugin (struct rdns_resolver *resolver, plugin->cb.curve_plugin.recv_cb = rdns_curve_recv; plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request; plugin->dtor = rdns_curve_dtor; - ctx->cur_key = rdns_curve_client_key_new (ctx); - REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + ctx->cur_key = rdns_curve_client_key_new(ctx); + REF_INIT_RETAIN(ctx->cur_key, rdns_curve_client_key_free); if (ctx->key_refresh_interval > 0) { - ctx->key_refresh_event = resolver->async->add_periodic ( - resolver->async->data, ctx->key_refresh_interval, - rdns_curve_refresh_key_callback, ctx); + ctx->key_refresh_event = resolver->async->add_periodic( + resolver->async->data, ctx->key_refresh_interval, + rdns_curve_refresh_key_callback, ctx); } ctx->resolver = resolver; - rdns_resolver_register_plugin (resolver, plugin); + rdns_resolver_register_plugin(resolver, plugin); } } ssize_t -rdns_curve_send (struct rdns_request *req, void *plugin_data, - struct sockaddr *saddr, socklen_t slen) +rdns_curve_send(struct rdns_request *req, void *plugin_data, + struct sockaddr *saddr, socklen_t slen) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; struct rdns_curve_entry *entry; struct iovec iov[4]; unsigned char *m; @@ -704,53 +719,52 @@ rdns_curve_send (struct rdns_request *req, void *plugin_data, ssize_t ret, boxed_len; /* Check for key */ - HASH_FIND_STR (ctx->entries, req->io->srv->name, entry); + HASH_FIND_STR(ctx->entries, req->io->srv->name, entry); if (entry != NULL) { - nm = rdns_curve_find_nm (ctx->cur_key, entry); - creq = malloc (sizeof (struct rdns_curve_request)); + nm = rdns_curve_find_nm(ctx->cur_key, entry); + creq = malloc(sizeof(struct rdns_curve_request)); if (creq == NULL) { return -1; } boxed_len = req->pos + crypto_box_ZEROBYTES; - m = malloc (boxed_len); + m = malloc(boxed_len); if (m == NULL) { free(creq); return -1; } /* Ottery is faster than sodium native PRG that uses /dev/random only */ - memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t)); - ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t)); - rspamd_explicit_memzero (creq->nonce + 12, - rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) - 12); + memcpy(creq->nonce, &ctx->cur_key->counter, sizeof(uint64_t)); + ottery_rand_bytes(creq->nonce + sizeof(uint64_t), 12 - sizeof(uint64_t)); + rspamd_explicit_memzero(creq->nonce + 12, + crypto_box_noncebytes() - 12); - rspamd_explicit_memzero (m, crypto_box_ZEROBYTES); - memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos); + rspamd_explicit_memzero(m, crypto_box_ZEROBYTES); + memcpy(m + crypto_box_ZEROBYTES, req->packet, req->pos); - rspamd_cryptobox_encrypt_nm_inplace (m + crypto_box_ZEROBYTES, - boxed_len, - creq->nonce, - nm->k, - m, - RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_encrypt_nm_inplace(m + crypto_box_ZEROBYTES, + boxed_len, + creq->nonce, + nm->k, + m); creq->key = ctx->cur_key; - REF_RETAIN (ctx->cur_key); + REF_RETAIN(ctx->cur_key); creq->entry = entry; creq->req = req; creq->nm = nm; - HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq); + HASH_ADD_KEYPTR(hh, ctx->requests, creq->nonce, 12, creq); req->curve_plugin_data = creq; - ctx->cur_key->counter ++; - ctx->cur_key->uses ++; + ctx->cur_key->counter++; + ctx->cur_key->uses++; /* Now form a dnscurve packet */ - iov[0].iov_base = (void *)qmagic; - iov[0].iov_len = sizeof (qmagic) - 1; + iov[0].iov_base = (void *) qmagic; + iov[0].iov_len = sizeof(qmagic) - 1; iov[1].iov_base = ctx->cur_key->pk; - iov[1].iov_len = sizeof (ctx->cur_key->pk); + iov[1].iov_len = sizeof(ctx->cur_key->pk); iov[2].iov_base = creq->nonce; iov[2].iov_len = 12; iov[3].iov_base = m + crypto_box_BOXZEROBYTES; @@ -758,17 +772,17 @@ rdns_curve_send (struct rdns_request *req, void *plugin_data, struct msghdr msg; - memset (&msg, 0, sizeof (msg)); + memset(&msg, 0, sizeof(msg)); msg.msg_namelen = slen; msg.msg_name = saddr; msg.msg_iov = iov; - msg.msg_iovlen = sizeof (iov) / sizeof (iov[0]); - ret = sendmsg (req->io->sock, &msg, 0); - rspamd_explicit_memzero (m, boxed_len); - free (m); + msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]); + ret = sendmsg(req->io->sock, &msg, 0); + rspamd_explicit_memzero(m, boxed_len); + free(m); } else { - ret = sendto (req->io->sock, req->packet, req->pos, 0, saddr, slen); + ret = sendto(req->io->sock, req->packet, req->pos, 0, saddr, slen); req->curve_plugin_data = NULL; } @@ -776,10 +790,10 @@ rdns_curve_send (struct rdns_request *req, void *plugin_data, } ssize_t -rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, - struct rdns_request **req_out, struct sockaddr *saddr, socklen_t slen) +rdns_curve_recv(struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, + struct rdns_request **req_out, struct sockaddr *saddr, socklen_t slen) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; ssize_t ret, boxlen; static const char rmagic[] = "R6fnvWJ8"; unsigned char *p, *box; @@ -788,102 +802,97 @@ rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugi struct rdns_resolver *resolver; resolver = ctx->resolver; - ret = recv (ioc->sock, buf, len, 0); + ret = recv(ioc->sock, buf, len, 0); if (ret <= 0 || ret < 64) { /* Definitely not a DNSCurve packet */ return ret; } - if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) { + if (memcmp(buf, rmagic, sizeof(rmagic) - 1) == 0) { /* Likely DNSCurve packet */ - p = ((unsigned char *)buf) + 8; - HASH_FIND (hh, ctx->requests, p, 12, creq); + p = ((unsigned char *) buf) + 8; + HASH_FIND(hh, ctx->requests, p, 12, creq); if (creq == NULL) { - rdns_info ("unable to find nonce in the internal hash"); + rdns_info("unable to find nonce in the internal hash"); return ret; } - memcpy (enonce, p, rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); - p += rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519); - boxlen = ret - rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) + - crypto_box_BOXZEROBYTES - - sizeof (rmagic) + 1; + memcpy(enonce, p, crypto_box_noncebytes()); + p += crypto_box_noncebytes(); + boxlen = ret - crypto_box_noncebytes() + + crypto_box_BOXZEROBYTES - + sizeof(rmagic) + 1; if (boxlen < 0) { return ret; } - box = malloc (boxlen); - rspamd_explicit_memzero (box, crypto_box_BOXZEROBYTES); - memcpy (box + crypto_box_BOXZEROBYTES, p, - boxlen - crypto_box_BOXZEROBYTES); - - if (!rspamd_cryptobox_decrypt_nm_inplace ( - box + rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519), - boxlen - rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519), - enonce, creq->nm->k, box, RSPAMD_CRYPTOBOX_MODE_25519)) { - memcpy (buf, box + crypto_box_ZEROBYTES, - boxlen - crypto_box_ZEROBYTES); + box = malloc(boxlen); + rspamd_explicit_memzero(box, crypto_box_BOXZEROBYTES); + memcpy(box + crypto_box_BOXZEROBYTES, p, + boxlen - crypto_box_BOXZEROBYTES); + + if (!rspamd_cryptobox_decrypt_nm_inplace( + box + crypto_box_macbytes(), + boxlen - crypto_box_macbytes(), + enonce, creq->nm->k, box)) { + memcpy(buf, box + crypto_box_ZEROBYTES, + boxlen - crypto_box_ZEROBYTES); ret = boxlen - crypto_box_ZEROBYTES; *req_out = creq->req; } else { - rdns_info ("unable open cryptobox of size %d", (int)boxlen); + rdns_info("unable open cryptobox of size %d", (int) boxlen); } - free (box); + free(box); } return ret; } -void -rdns_curve_finish_request (struct rdns_request *req, void *plugin_data) +void rdns_curve_finish_request(struct rdns_request *req, void *plugin_data) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; struct rdns_curve_request *creq = req->curve_plugin_data; if (creq != NULL) { - REF_RELEASE (creq->key); - HASH_DELETE (hh, ctx->requests, creq); + REF_RELEASE(creq->key); + HASH_DELETE(hh, ctx->requests, creq); } } -void -rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data) +void rdns_curve_dtor(struct rdns_resolver *resolver, void *plugin_data) { - struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *) plugin_data; if (ctx->key_refresh_event != NULL) { - resolver->async->del_periodic (resolver->async->data, - ctx->key_refresh_event); + resolver->async->del_periodic(resolver->async->data, + ctx->key_refresh_event); } - REF_RELEASE (ctx->cur_key); + REF_RELEASE(ctx->cur_key); } #else /* Fake functions */ -struct rdns_curve_ctx* rdns_curve_ctx_new (double rekey_interval) +struct rdns_curve_ctx *rdns_curve_ctx_new(double rekey_interval) { return NULL; } -void rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, - const char *name, const unsigned char *pubkey) +void rdns_curve_ctx_add_key(struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) { - } -void rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +void rdns_curve_ctx_destroy(struct rdns_curve_ctx *ctx) { - } -void rdns_curve_register_plugin (struct rdns_resolver *resolver, - struct rdns_curve_ctx *ctx) +void rdns_curve_register_plugin(struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) { - } unsigned char * -rdns_curve_key_from_hex (const char *hex) +rdns_curve_key_from_hex(const char *hex) { return NULL; } diff --git a/contrib/libucl/lua_ucl.c b/contrib/libucl/lua_ucl.c index a9edb3e4d..19ac9cb12 100644 --- a/contrib/libucl/lua_ucl.c +++ b/contrib/libucl/lua_ucl.c @@ -1,3 +1,19 @@ +/* + * Copyright 2024 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /* Copyright (c) 2014, Vsevolod Stakhov * All rights reserved. * @@ -28,6 +44,7 @@ #include "ucl.h" #include "ucl_internal.h" #include "lua_ucl.h" +#include "kvec.h" #include <strings.h> /*** @@ -69,6 +86,7 @@ func = "huh"; #define PARSER_META "ucl.parser.meta" #define EMITTER_META "ucl.emitter.meta" #define NULL_META "ucl.null.meta" +#define ITER_META "ucl.object.iter" #define OBJECT_META "ucl.object.meta" #define UCL_OBJECT_TYPE_META "ucl.type.object" #define UCL_ARRAY_TYPE_META "ucl.type.array" @@ -79,10 +97,20 @@ static int ucl_object_lua_push_scalar(lua_State *L, const ucl_object_t *obj, int static int ucl_object_push_lua_common(lua_State *L, const ucl_object_t *obj, int flags); static ucl_object_t *ucl_object_lua_fromtable(lua_State *L, int idx, ucl_string_flags_t flags); static ucl_object_t *ucl_object_lua_fromelt(lua_State *L, int idx, ucl_string_flags_t flags); +static ucl_object_t *lua_ucl_object_get(lua_State *L, int index); +static int lua_ucl_type(lua_State *L); +static int lua_ucl_pairs(lua_State *L); +static int lua_ucl_ipairs(lua_State *L); +static int lua_ucl_len(lua_State *L); +static int lua_ucl_index(lua_State *L); +static int lua_ucl_newindex(lua_State *L); +static int lua_ucl_object_tostring(lua_State *L); +static int lua_ucl_object_unwrap(lua_State *L); +static int lua_ucl_object_validate(lua_State *L); static void *ucl_null; -struct _rspamd_lua_text { +struct rspamd_compat_lua_text { const char *start; unsigned int len; unsigned int flags; @@ -519,7 +547,7 @@ ucl_object_lua_fromelt(lua_State *L, int idx, ucl_string_flags_t flags) } else { /* Assume it is a text like object */ - struct _rspamd_lua_text *t = lua_touserdata(L, idx); + struct rspamd_compat_lua_text *t = lua_touserdata(L, idx); if (t) { if (t->len > 0) { @@ -551,7 +579,22 @@ ucl_object_lua_fromelt(lua_State *L, int idx, ucl_string_flags_t flags) } else { if (type == LUA_TTABLE) { - obj = ucl_object_lua_fromtable(L, idx, flags); + lua_rawgeti(L, idx, 0); + + if (lua_type(L, -1) == LUA_TUSERDATA) { + /* It is a cloaked ucl object */ + obj = lua_ucl_object_get(L, idx); + + if (obj) { + obj = ucl_object_ref(obj); + } + } + + lua_pop(L, 1); + + if (obj == NULL) { + obj = ucl_object_lua_fromtable(L, idx, flags); + } } else if (type == LUA_TFUNCTION) { fd = malloc(sizeof(*fd)); @@ -679,16 +722,54 @@ lua_ucl_parser_get(lua_State *L, int index) static ucl_object_t * lua_ucl_object_get(lua_State *L, int index) { - return *((ucl_object_t **) luaL_checkudata(L, index, OBJECT_META)); + if (lua_istable(L, index)) { + ucl_object_t *res = NULL; + lua_rawgeti(L, index, 0); + + if (lua_isuserdata(L, -1)) { + res = *((ucl_object_t **) lua_touserdata(L, -1)); + } + + lua_pop(L, 1); + + return res; + } + + return NULL; } -static void -lua_ucl_push_opaque(lua_State *L, ucl_object_t *obj) +void ucl_object_push_lua_unwrapped(lua_State *L, const ucl_object_t *obj) { ucl_object_t **pobj; + /* We create a plain lua table with the following elements: + * [0] - ucl object as userdata + * ["method"] - methods + */ + lua_createtable(L, 1, 9); pobj = lua_newuserdata(L, sizeof(*pobj)); - *pobj = obj; + *pobj = ucl_object_ref(obj); + lua_rawseti(L, -2, 0); + + lua_pushcfunction(L, lua_ucl_index); + lua_setfield(L, -2, "at"); + lua_pushcfunction(L, lua_ucl_type); + lua_setfield(L, -2, "type"); + lua_pushcfunction(L, lua_ucl_pairs); + lua_setfield(L, -2, "pairs"); + lua_pushcfunction(L, lua_ucl_ipairs); + lua_setfield(L, -2, "ipairs"); + lua_pushcfunction(L, lua_ucl_len); + lua_setfield(L, -2, "len"); + lua_pushcfunction(L, lua_ucl_object_tostring); + lua_setfield(L, -2, "tostring"); + lua_pushcfunction(L, lua_ucl_object_unwrap); + lua_setfield(L, -2, "unwrap"); + lua_pushcfunction(L, lua_ucl_object_unwrap); + lua_setfield(L, -2, "tolua"); + lua_pushcfunction(L, lua_ucl_object_validate); + lua_setfield(L, -2, "validate"); + luaL_getmetatable(L, OBJECT_META); lua_setmetatable(L, -2); } @@ -878,7 +959,7 @@ static int lua_ucl_parser_parse_text(lua_State *L) { struct ucl_parser *parser; - struct _rspamd_lua_text *t; + struct rspamd_compat_lua_text *t; enum ucl_parse_type type = UCL_PARSE_UCL; int ret = 2; @@ -890,7 +971,7 @@ lua_ucl_parser_parse_text(lua_State *L) else if (lua_type(L, 2) == LUA_TSTRING) { const char *s; gsize len; - static struct _rspamd_lua_text st_t; + static struct rspamd_compat_lua_text st_t; s = lua_tolstring(L, 2, &len); st_t.start = s; @@ -969,7 +1050,8 @@ lua_ucl_parser_get_object_wrapped(lua_State *L) obj = ucl_parser_get_object(parser); if (obj != NULL) { - lua_ucl_push_opaque(L, obj); + ucl_object_push_lua_unwrapped(L, obj); + ucl_object_unref(obj); } else { lua_pushnil(L); @@ -1128,8 +1210,9 @@ lua_ucl_object_tostring(lua_State *L) enum ucl_emitter format = UCL_EMIT_JSON_COMPACT; obj = lua_ucl_object_get(L, 1); + int type = ucl_object_type(obj); - if (obj) { + if (type == UCL_ARRAY || type == UCL_OBJECT) { if (lua_gettop(L) > 1) { if (lua_type(L, 2) == LUA_TSTRING) { const char *strtype = lua_tostring(L, 2); @@ -1140,9 +1223,12 @@ lua_ucl_object_tostring(lua_State *L) return lua_ucl_to_string(L, obj, format); } - else { + else if (type == UCL_NULL) { lua_pushnil(L); } + else { + ucl_object_lua_push_scalar(L, obj, 0); + } return 1; } @@ -1211,7 +1297,8 @@ lua_ucl_object_validate(lua_State *L) lua_pushnil(L); if (ext_refs) { - lua_ucl_push_opaque(L, ext_refs); + ucl_object_push_lua_unwrapped(L, ext_refs); + ucl_object_unref(ext_refs); } } else { @@ -1219,7 +1306,8 @@ lua_ucl_object_validate(lua_State *L) lua_pushfstring(L, "validation error: %s", err.msg); if (ext_refs) { - lua_ucl_push_opaque(L, ext_refs); + ucl_object_push_lua_unwrapped(L, ext_refs); + ucl_object_unref(ext_refs); } } } @@ -1229,7 +1317,8 @@ lua_ucl_object_validate(lua_State *L) lua_pushfstring(L, "cannot find the requested path: %s", path); if (ext_refs) { - lua_ucl_push_opaque(L, ext_refs); + ucl_object_push_lua_unwrapped(L, ext_refs); + ucl_object_unref(ext_refs); } } } @@ -1257,6 +1346,338 @@ lua_ucl_object_gc(lua_State *L) return 0; } +static int +lua_ucl_iter_gc(lua_State *L) +{ + ucl_object_iter_t it; + + it = *((ucl_object_iter_t *) lua_touserdata(L, 1)); + + if (it) { + ucl_object_iterate_free(it); + } + + return 0; +} + +static int +lua_ucl_index(lua_State *L) +{ + ucl_object_t *obj; + + obj = lua_ucl_object_get(L, 1); + + if (lua_type(L, 2) == LUA_TSTRING) { + /* Index by string */ + + if (ucl_object_type(obj) == UCL_OBJECT) { + size_t len; + const char *key = lua_tolstring(L, 2, &len); + const ucl_object_t *elt; + + elt = ucl_object_lookup_len(obj, key, strlen(key)); + + if (elt) { + ucl_object_push_lua_unwrapped(L, elt); + } + else { + lua_pushnil(L); + } + + return 1; + } + else { + return luaL_error(L, "cannot index non-object: %s", ucl_object_type_to_string(ucl_object_type(obj))); + } + } + else if (lua_type(L, 2) == LUA_TNUMBER) { + /* Index by number */ + if (ucl_object_type(obj) == UCL_ARRAY) { + /* +1 as Lua indexes elements from 1 and ucl indexes them from 0 */ + lua_Integer idx = lua_tointeger(L, 2) + 1; + const ucl_object_t *elt; + + elt = ucl_array_find_index(obj, idx); + + if (elt) { + ucl_object_push_lua_unwrapped(L, elt); + } + else { + lua_pushnil(L); + } + + return 1; + } + else { + return luaL_error(L, "cannot index non-array: %s", ucl_object_type_to_string(ucl_object_type(obj))); + } + } + else { + return luaL_error(L, "invalid index type: %s", lua_typename(L, lua_type(L, 2))); + } +} + +static int +lua_ucl_newindex(lua_State *L) +{ + ucl_object_t *obj; + obj = lua_ucl_object_get(L, 1); + int key_type = lua_type(L, 2); + + if (ucl_object_type(obj) == UCL_OBJECT) { + if (key_type == LUA_TSTRING) { + lua_Integer keylen; + const char *key = lua_tolstring(L, 2, &keylen); + + ucl_object_t *value_obj = lua_ucl_object_get(L, 3); + if (value_obj) { + value_obj = ucl_object_ref(value_obj); + } + else { + value_obj = ucl_object_lua_import(L, 3); + } + + if (ucl_object_lookup_len(obj, key, keylen) != NULL) { + if (value_obj != NULL) { + ucl_object_replace_key(obj, value_obj, key, keylen, true); + } + else { + /* Delete key */ + ucl_object_delete_keyl(obj, key, keylen); + } + } + else { + if (value_obj != NULL) { + ucl_object_insert_key(obj, value_obj, key, keylen, true); + } + /* Do nothing if value_obj is null, like Lua does */ + } + } + else if (key_type == LUA_TNUMBER) { + /* + * We have some object but wanted it to be an array + * In Lua, this is allowed but UCL has distinction between array and object + * Here, we allow one thing: if an object is empty, and we want a numeric index, we convert it to an array + */ + lua_Integer idx = lua_tointeger(L, 2); + if (idx == 1 && obj->len == 0 && obj->value.ov == NULL) { + /* UCL preallocate arrays, but it is not a requirement once av is NULL */ + obj->type = UCL_ARRAY; + ucl_object_t *value_obj = lua_ucl_object_get(L, 3); + if (value_obj) { + value_obj = ucl_object_ref(value_obj); + } + else { + value_obj = ucl_object_lua_import(L, 3); + } + + if (value_obj == NULL) { + return luaL_error(L, "invalid value type: %s", lua_typename(L, lua_type(L, 3))); + } + ucl_array_append(obj, value_obj); + } + else { + return luaL_error(L, "invalid index type for an object: %s", lua_typename(L, key_type)); + } + } + else { + return luaL_error(L, "invalid index type for an object: %s", lua_typename(L, key_type)); + } + + return 0; + } + else if (ucl_object_type(obj) == UCL_ARRAY) { + if (key_type == LUA_TNUMBER) { + lua_Integer idx = lua_tointeger(L, 2); + + ucl_object_t *value_obj = lua_ucl_object_get(L, 3); + if (value_obj) { + value_obj = ucl_object_ref(value_obj); + } + else { + value_obj = ucl_object_lua_import(L, 3); + } + + if (value_obj == NULL) { + return luaL_error(L, "invalid value type: %s", lua_typename(L, lua_type(L, 3))); + } + + /* Lua allows sparse arrays and ucl does not + * So we have 2 options: + * 1) Idx is some existing index, so we need to replace it + * 3) Idx is #len, so we append it + * Everything else is an error + */ + if (idx == ucl_array_size(obj) + 1) { + ucl_array_append(obj, value_obj); + } + else if (idx >= 1 && idx <= ucl_array_size(obj)) { + ucl_array_replace_index(obj, value_obj, idx - 1); + } + else { + ucl_object_unref(value_obj); + + return luaL_error(L, "invalid index for array: %d", (int) idx); + } + } + else if (key_type == LUA_TSTRING) { + /* + * We have some array but wanted it to be an object + * In Lua, this is allowed but UCL has distinction between array and object + * Here, we allow one thing: if an array is empty, and we want a string index, we convert it to an object + * The biggest issue is that ucl array is preallocated in general, so we need to free it somehow + */ + /* + * Dirty hacks are here + */ + kvec_t(ucl_object_t *) *real_ar = obj->value.av; + + if (real_ar) { + kv_destroy(*real_ar); + } + UCL_FREE(sizeof(*real_ar), real_ar); + obj->value.av = NULL; + obj->type = UCL_OBJECT; + + lua_Integer keylen; + const char *key = lua_tolstring(L, 2, &keylen); + + ucl_object_t *value_obj = lua_ucl_object_get(L, 3); + if (value_obj) { + value_obj = ucl_object_ref(value_obj); + } + else { + value_obj = ucl_object_lua_import(L, 3); + } + + if (value_obj) { + ucl_object_insert_key(obj, value_obj, key, keylen, true); + } + else { + return luaL_error(L, "invalid value type: %s", lua_typename(L, lua_type(L, 3))); + } + } + else { + return luaL_error(L, "invalid index type for an array: %s", lua_typename(L, key_type)); + } + + return 0; + } + else { + return luaL_error(L, "invalid index type: %s (obj type: %s)", lua_typename(L, key_type), + ucl_object_type_to_string(ucl_object_type(obj))); + } +} + +static int +lua_ucl_type(lua_State *L) +{ + ucl_object_t *obj; + + obj = lua_ucl_object_get(L, 1); + lua_pushstring(L, ucl_object_type_to_string(ucl_object_type(obj))); + + return 1; +} + +static int +lua_ucl_object_iter(lua_State *L) +{ + ucl_object_iter_t it; + const ucl_object_t *cur; + + it = *((ucl_object_iter_t *) lua_touserdata(L, 1)); + cur = ucl_object_iterate_safe(it, true); + + if (cur) { + if (ucl_object_key(cur)) { + size_t klen; + const char *k = ucl_object_keyl(cur, &klen); + lua_pushlstring(L, k, klen); + } + else { + if (lua_type(L, 2) == LUA_TNUMBER) { + lua_Integer idx = lua_tointeger(L, 2); + if (idx >= 0) { + lua_pushinteger(L, idx + 1); + } + } + else { + lua_pushnumber(L, -1); + } + } + ucl_object_push_lua_unwrapped(L, cur); + + return 2; + } + else { + lua_pushnil(L); + + return 1; + } +} + +static int +lua_ucl_pairs(lua_State *L) +{ + ucl_object_t *obj; + + obj = lua_ucl_object_get(L, 1); + int t = ucl_object_type(obj); + + if ((obj) && (t == UCL_ARRAY || t == UCL_OBJECT || obj->next != NULL)) { + /* iter_func, ucl_object_t, iter */ + lua_pushcfunction(L, lua_ucl_object_iter); + ucl_object_iter_t *pit = lua_newuserdata(L, sizeof(ucl_object_iter_t *)); + luaL_getmetatable(L, ITER_META); + lua_setmetatable(L, -2); + ucl_object_iter_t it = ucl_object_iterate_new(obj); + *pit = it; + lua_pushnumber(L, -1); + + return 3; + } + else { + return luaL_error(L, "invalid object type for pairs: %s", ucl_object_type_to_string(t)); + } +} + +static int +lua_ucl_len(lua_State *L) +{ + ucl_object_t *obj; + + obj = lua_ucl_object_get(L, 1); + lua_pushinteger(L, obj->len); + + return 1; +} + +static int +lua_ucl_ipairs(lua_State *L) +{ + ucl_object_t *obj; + + obj = lua_ucl_object_get(L, 1); + int t = ucl_object_type(obj); + + if ((obj) && (t == UCL_ARRAY || obj->next != NULL)) { + /* iter_func, ucl_object_t, iter */ + lua_pushcfunction(L, lua_ucl_object_iter); + ucl_object_iter_t *pit = lua_newuserdata(L, sizeof(ucl_object_iter_t *)); + luaL_getmetatable(L, ITER_META); + lua_setmetatable(L, -2); + ucl_object_iter_t it = ucl_object_iterate_new(obj); + *pit = it; + lua_pushnumber(L, 0); + + return 3; + } + else { + return luaL_error(L, "invalid object type for ipairs: %s", ucl_object_type_to_string(t)); + } +} + static void lua_ucl_parser_mt(lua_State *L) { @@ -1300,29 +1721,41 @@ lua_ucl_object_mt(lua_State *L) { luaL_newmetatable(L, OBJECT_META); - lua_pushvalue(L, -1); + lua_pushcfunction(L, lua_ucl_index); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, lua_ucl_newindex); + lua_setfield(L, -2, "__newindex"); + /* Usable merely with lua 5.2+ */ + lua_pushcfunction(L, lua_ucl_ipairs); + lua_setfield(L, -2, "__ipairs"); + /* Usable merely with lua 5.2+ */ + lua_pushcfunction(L, lua_ucl_pairs); + lua_setfield(L, -2, "__pairs"); + + /* Access UCL elements using `:at` method */ + lua_pushcfunction(L, lua_ucl_index); + lua_setfield(L, -2, "at"); + + /* Usable merely with lua 5.2+ */ + lua_pushcfunction(L, lua_ucl_len); + lua_setfield(L, -2, "__len"); lua_pushcfunction(L, lua_ucl_object_gc); lua_setfield(L, -2, "__gc"); - lua_pushcfunction(L, lua_ucl_object_tostring); lua_setfield(L, -2, "__tostring"); - lua_pushcfunction(L, lua_ucl_object_tostring); - lua_setfield(L, -2, "tostring"); - - lua_pushcfunction(L, lua_ucl_object_unwrap); - lua_setfield(L, -2, "unwrap"); + lua_pushstring(L, OBJECT_META); + lua_setfield(L, -2, "class"); - lua_pushcfunction(L, lua_ucl_object_unwrap); - lua_setfield(L, -2, "tolua"); + lua_pop(L, 1); - lua_pushcfunction(L, lua_ucl_object_validate); - lua_setfield(L, -2, "validate"); + luaL_newmetatable(L, ITER_META); + lua_pushcfunction(L, lua_ucl_iter_gc); + lua_setfield(L, -2, "__gc"); - lua_pushstring(L, OBJECT_META); - lua_setfield(L, -2, "class"); + lua_pushstring(L, ITER_META); + lua_setfield(L, -2, "__tostring"); lua_pop(L, 1); } diff --git a/contrib/libucl/lua_ucl.h b/contrib/libucl/lua_ucl.h index 4a759e3b4..fe3d86dfd 100644 --- a/contrib/libucl/lua_ucl.h +++ b/contrib/libucl/lua_ucl.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,6 +91,13 @@ UCL_EXTERN ucl_object_t *ucl_object_lua_import_escape(lua_State *L, int idx); */ UCL_EXTERN int ucl_object_push_lua(lua_State *L, const ucl_object_t *obj, bool allow_array); + +/** + * Push an object to lua as userdata object (handling one refcount) + * @param L + * @param obj + */ +UCL_EXTERN void ucl_object_push_lua_unwrapped(lua_State *L, const ucl_object_t *obj); /** * Push an object to lua replacing all ucl.null with `false` * @param L lua state diff --git a/contrib/publicsuffix/README.md b/contrib/publicsuffix/README.md new file mode 100644 index 000000000..eb11436ff --- /dev/null +++ b/contrib/publicsuffix/README.md @@ -0,0 +1,13 @@ +# Public suffixes list + +Update procedure: + +1. Download the list from the [official mirror](https://publicsuffix.org/list/public_suffix_list.dat) +2. Proceed through `idn.pl` script + +1 liner: `curl https://publicsuffix.org/list/public_suffix_list.dat | perl idn.pl > effective_tld_names.dat` + +## Deps installation + +Ensure that you have `cpanm` installed (e.g. by `brew install cpanm`). +Run `cpanm --installdeps .` once. diff --git a/contrib/publicsuffix/cpanfile b/contrib/publicsuffix/cpanfile new file mode 100644 index 000000000..401e27443 --- /dev/null +++ b/contrib/publicsuffix/cpanfile @@ -0,0 +1,3 @@ +requires 'Net::IDN::Encode', '2.500'; +requires 'Unicode::Normalize', '1.0'; + diff --git a/contrib/publicsuffix/effective_tld_names.dat b/contrib/publicsuffix/effective_tld_names.dat index b0a3524cd..bbb7ec699 100644 --- a/contrib/publicsuffix/effective_tld_names.dat +++ b/contrib/publicsuffix/effective_tld_names.dat @@ -32,22 +32,30 @@ ac.ae gov.ae mil.ae -// aero : see https://www.information.aero/index.php?id=66 +// aero : https://information.aero/registration/policies/dmp aero +// 2LDs +airline.aero +airport.aero +// 2LDs (currently not accepting registration, seemingly never have) +// As of 2024-07, these are marked as reserved for potential 3LD +// registrations (clause 11 "allocated subdomains" in the 2006 TLD +// policy), but the relevant industry partners have not opened them up +// for registration. Current status can be determined from the TLD's +// policy document: 2LDs that are open for registration must list +// their policy in the TLD's policy. Any 2LD without such a policy is +// not open for registrations. accident-investigation.aero accident-prevention.aero aerobatic.aero aeroclub.aero aerodrome.aero agents.aero -aircraft.aero -airline.aero -airport.aero air-surveillance.aero -airtraffic.aero air-traffic-control.aero +aircraft.aero +airtraffic.aero ambulance.aero -amusement.aero association.aero author.aero ballooning.aero @@ -78,6 +86,7 @@ exchange.aero express.aero federation.aero flight.aero +freight.aero fuel.aero gliding.aero government.aero @@ -92,6 +101,7 @@ leasing.aero logistics.aero magazine.aero maintenance.aero +marketplace.aero media.aero microlight.aero modelling.aero @@ -114,6 +124,7 @@ show.aero skydiving.aero software.aero student.aero +taxi.aero trader.aero trading.aero trainer.aero @@ -675,7 +686,6 @@ mil.by // second-level domain, but it's being used as one (see www.google.com.by and // www.yahoo.com.by, for example), so we list it here for safety's sake. com.by - // http://hoster.by/ of.by @@ -1019,13 +1029,12 @@ net.et // eu : https://en.wikipedia.org/wiki/.eu eu -// fi : https://en.wikipedia.org/wiki/.fi +// fi : https://www.iana.org/domains/root/db/fi.html fi -// aland.fi : https://en.wikipedia.org/wiki/.ax +// aland.fi : https://www.iana.org/domains/root/db/ax.html // This domain is being phased out in favor of .ax. As there are still many // domains under aland.fi, we still keep it on the list until aland.fi is // completely removed. -// TODO: Check for updates (expected to be phased out around Q1/2009) aland.fi // fj : http://domains.fj/ @@ -2094,18 +2103,18 @@ xn--d5qv7z876c.jp // jp geographic type names // http://jprs.jp/doc/rule/saisoku-1.html *.kawasaki.jp -*.kitakyushu.jp -*.kobe.jp -*.nagoya.jp -*.sapporo.jp -*.sendai.jp -*.yokohama.jp !city.kawasaki.jp +*.kitakyushu.jp !city.kitakyushu.jp +*.kobe.jp !city.kobe.jp +*.nagoya.jp !city.nagoya.jp +*.sapporo.jp !city.sapporo.jp +*.sendai.jp !city.sendai.jp +*.yokohama.jp !city.yokohama.jp // 4th level registration aisai.aichi.jp @@ -5325,22 +5334,27 @@ ngo.ph mil.ph i.ph -// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK +// pk : https://pknic.net.pk +// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK + grandfathered old gon.pk +// Contact Email: staff@pknic.net.pk PKNIC .PK Registry + pk +ac.pk +biz.pk com.pk -net.pk edu.pk -org.pk fam.pk -biz.pk -web.pk -gov.pk +gkp.pk gob.pk +gog.pk gok.pk gon.pk gop.pk gos.pk -info.pk +gov.pk +net.pk +org.pk +web.pk // pl http://www.dns.pl/english/index.html // Submitted by registry @@ -7044,10 +7058,9 @@ gov.zw mil.zw org.zw - // newGTLDs -// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-02-08T15:13:14Z +// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-08-10T15:15:39Z // This list is auto-generated, don't edit it manually. // aaa : American Automobile Association, Inc. // https://www.iana.org/domains/root/db/aaa.html @@ -7233,7 +7246,7 @@ anquan // https://www.iana.org/domains/root/db/anz.html anz -// aol : Oath Inc. +// aol : Yahoo Inc. // https://www.iana.org/domains/root/db/aol.html aol @@ -7325,10 +7338,6 @@ auto // https://www.iana.org/domains/root/db/autos.html autos -// avianca : Avianca Inc. -// https://www.iana.org/domains/root/db/avianca.html -avianca - // aws : AWS Registry LLC // https://www.iana.org/domains/root/db/aws.html aws @@ -8693,10 +8702,6 @@ grocery // https://www.iana.org/domains/root/db/group.html group -// guardian : The Guardian Life Insurance Company of America -// https://www.iana.org/domains/root/db/guardian.html -guardian - // gucci : Guccio Gucci S.p.a. // https://www.iana.org/domains/root/db/gucci.html gucci @@ -9321,7 +9326,7 @@ lotte // https://www.iana.org/domains/root/db/lotto.html lotto -// love : Merchant Law Group LLP +// love : Waterford Limited // https://www.iana.org/domains/root/db/love.html love @@ -9369,7 +9374,7 @@ maison // https://www.iana.org/domains/root/db/makeup.html makeup -// man : MAN SE +// man : MAN Truck & Bus SE // https://www.iana.org/domains/root/db/man.html man @@ -9573,10 +9578,6 @@ nab // https://www.iana.org/domains/root/db/nagoya.html nagoya -// natura : NATURA COSMÉTICOS S.A. -// https://www.iana.org/domains/root/db/natura.html -natura - // navy : Dog Beach, LLC // https://www.iana.org/domains/root/db/navy.html navy @@ -10349,10 +10350,6 @@ shangrila // https://www.iana.org/domains/root/db/sharp.html sharp -// shaw : Shaw Cablesystems G.P. -// https://www.iana.org/domains/root/db/shaw.html -shaw - // shell : Shell Information Technology International Inc // https://www.iana.org/domains/root/db/shell.html shell @@ -10845,7 +10842,7 @@ ups // https://www.iana.org/domains/root/db/vacations.html vacations -// vana : Internet Naming Company LLC +// vana : D3 Registry LLC // https://www.iana.org/domains/root/db/vana.html vana @@ -11555,7 +11552,7 @@ xyz // https://www.iana.org/domains/root/db/yachts.html yachts -// yahoo : Oath Inc. +// yahoo : Yahoo Inc. // https://www.iana.org/domains/root/db/yahoo.html yahoo @@ -11617,9 +11614,27 @@ zuerich // ===END ICANN DOMAINS=== + // ===BEGIN PRIVATE DOMAINS=== + // (Note: these are in alphabetical order by company name) +// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf +co.krd +edu.krd + +// .pl domains (grandfathered) +art.pl +gliwice.pl +krakow.pl +poznan.pl +wroc.pl +zakopane.pl + +// .US +// Submitted by Ed Moore <Ed.Moore@lib.de.us> +lib.de.us + // 12CHARS: https://12chars.com // Submitted by Kenny Niehage <psl@12chars.com> 12chars.dev @@ -11632,7 +11647,7 @@ cc.ua inf.ua ltd.ua -// 611coin : https://611project.org/ +// 611 blockchain domain name system : https://611project.net/ 611.to // A2 Hosting @@ -11640,13 +11655,9 @@ ltd.ua a2hosted.com cpserver.com -// Aaron Marais' Gitlab pages: https://lab.aaronleem.co.za -// Submitted by Aaron Marais <its_me@aaronleem.co.za> -graphox.us - -// accesso Technology Group, plc. : https://accesso.com/ -// Submitted by accesso Team <accessoecommerce@accesso.com> -*.devcdnaccesso.com +// AAA workspace : https://aaa.vodka +// Submitted by Kirill Rezraf <admin@aaa.vodka> +aaa.vodka // Acorn Labs : https://acorn.io // Submitted by Craig Jellick <domains@acorn.io> @@ -11656,6 +11667,10 @@ graphox.us // Submitted by Ofer Kalaora <postmaster@activetrail.com> activetrail.biz +// Adaptable.io : https://adaptable.io +// Submitted by Mark Terrel <support@adaptable.io> +adaptable.app + // Adobe : https://www.adobe.com/ // Submitted by Ian Boston <boston@adobe.com> and Lars Trieloff <trieloff@adobe.com> adobeaemcloud.com @@ -11672,6 +11687,10 @@ hlx3.page adobeio-static.net adobeioruntime.net +// Africa.com Web Solutions Ltd : https://registry.africa.com +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +africa.com + // Agnat sp. z o.o. : https://domena.pl // Submitted by Przemyslaw Plewa <it-admin@domena.pl> beep.pl @@ -11777,23 +11796,30 @@ cloudfront.net // Amazon Cognito // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: 7bee1013-f456-47df-bfe8-03c78d946d61 +// Reference: cb38c251-c93d-4cda-81ec-e72c4f0fdb72 auth.af-south-1.amazoncognito.com +auth.ap-east-1.amazoncognito.com auth.ap-northeast-1.amazoncognito.com auth.ap-northeast-2.amazoncognito.com auth.ap-northeast-3.amazoncognito.com auth.ap-south-1.amazoncognito.com +auth.ap-south-2.amazoncognito.com auth.ap-southeast-1.amazoncognito.com auth.ap-southeast-2.amazoncognito.com auth.ap-southeast-3.amazoncognito.com +auth.ap-southeast-4.amazoncognito.com auth.ca-central-1.amazoncognito.com +auth.ca-west-1.amazoncognito.com auth.eu-central-1.amazoncognito.com +auth.eu-central-2.amazoncognito.com auth.eu-north-1.amazoncognito.com auth.eu-south-1.amazoncognito.com +auth.eu-south-2.amazoncognito.com auth.eu-west-1.amazoncognito.com auth.eu-west-2.amazoncognito.com auth.eu-west-3.amazoncognito.com auth.il-central-1.amazoncognito.com +auth.me-central-1.amazoncognito.com auth.me-south-1.amazoncognito.com auth.sa-east-1.amazoncognito.com auth.us-east-1.amazoncognito.com @@ -11809,14 +11835,14 @@ auth-fips.us-west-2.amazoncognito.com // Amazon EC2 // Submitted by Luke Wells <psl-maintainers@amazon.com> // Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 +*.compute.amazonaws.com.cn *.compute.amazonaws.com *.compute-1.amazonaws.com -*.compute.amazonaws.com.cn us-east-1.amazonaws.com // Amazon EMR // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: 597f3f8e-9283-4e48-8e32-7ee25a1ff6ab +// Reference: 82f43f9f-bbb8-400e-8349-854f5a62f20d emrappui-prod.cn-north-1.amazonaws.com.cn emrnotebooks-prod.cn-north-1.amazonaws.com.cn emrstudio-prod.cn-north-1.amazonaws.com.cn @@ -11841,6 +11867,9 @@ emrstudio-prod.ap-northeast-3.amazonaws.com emrappui-prod.ap-south-1.amazonaws.com emrnotebooks-prod.ap-south-1.amazonaws.com emrstudio-prod.ap-south-1.amazonaws.com +emrappui-prod.ap-south-2.amazonaws.com +emrnotebooks-prod.ap-south-2.amazonaws.com +emrstudio-prod.ap-south-2.amazonaws.com emrappui-prod.ap-southeast-1.amazonaws.com emrnotebooks-prod.ap-southeast-1.amazonaws.com emrstudio-prod.ap-southeast-1.amazonaws.com @@ -11850,18 +11879,30 @@ emrstudio-prod.ap-southeast-2.amazonaws.com emrappui-prod.ap-southeast-3.amazonaws.com emrnotebooks-prod.ap-southeast-3.amazonaws.com emrstudio-prod.ap-southeast-3.amazonaws.com +emrappui-prod.ap-southeast-4.amazonaws.com +emrnotebooks-prod.ap-southeast-4.amazonaws.com +emrstudio-prod.ap-southeast-4.amazonaws.com emrappui-prod.ca-central-1.amazonaws.com emrnotebooks-prod.ca-central-1.amazonaws.com emrstudio-prod.ca-central-1.amazonaws.com +emrappui-prod.ca-west-1.amazonaws.com +emrnotebooks-prod.ca-west-1.amazonaws.com +emrstudio-prod.ca-west-1.amazonaws.com emrappui-prod.eu-central-1.amazonaws.com emrnotebooks-prod.eu-central-1.amazonaws.com emrstudio-prod.eu-central-1.amazonaws.com +emrappui-prod.eu-central-2.amazonaws.com +emrnotebooks-prod.eu-central-2.amazonaws.com +emrstudio-prod.eu-central-2.amazonaws.com emrappui-prod.eu-north-1.amazonaws.com emrnotebooks-prod.eu-north-1.amazonaws.com emrstudio-prod.eu-north-1.amazonaws.com emrappui-prod.eu-south-1.amazonaws.com emrnotebooks-prod.eu-south-1.amazonaws.com emrstudio-prod.eu-south-1.amazonaws.com +emrappui-prod.eu-south-2.amazonaws.com +emrnotebooks-prod.eu-south-2.amazonaws.com +emrstudio-prod.eu-south-2.amazonaws.com emrappui-prod.eu-west-1.amazonaws.com emrnotebooks-prod.eu-west-1.amazonaws.com emrstudio-prod.eu-west-1.amazonaws.com @@ -11871,6 +11912,9 @@ emrstudio-prod.eu-west-2.amazonaws.com emrappui-prod.eu-west-3.amazonaws.com emrnotebooks-prod.eu-west-3.amazonaws.com emrstudio-prod.eu-west-3.amazonaws.com +emrappui-prod.il-central-1.amazonaws.com +emrnotebooks-prod.il-central-1.amazonaws.com +emrstudio-prod.il-central-1.amazonaws.com emrappui-prod.me-central-1.amazonaws.com emrnotebooks-prod.me-central-1.amazonaws.com emrstudio-prod.me-central-1.amazonaws.com @@ -11901,23 +11945,37 @@ emrstudio-prod.us-west-2.amazonaws.com // Amazon Managed Workflows for Apache Airflow // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: 4ab55e6f-90c0-4a8d-b6a0-52ca5dbb1c2e +// Reference: f5ea5d0a-ec6a-4f23-ac1c-553fbff13f5c *.cn-north-1.airflow.amazonaws.com.cn *.cn-northwest-1.airflow.amazonaws.com.cn +*.af-south-1.airflow.amazonaws.com +*.ap-east-1.airflow.amazonaws.com *.ap-northeast-1.airflow.amazonaws.com *.ap-northeast-2.airflow.amazonaws.com +*.ap-northeast-3.airflow.amazonaws.com *.ap-south-1.airflow.amazonaws.com +*.ap-south-2.airflow.amazonaws.com *.ap-southeast-1.airflow.amazonaws.com *.ap-southeast-2.airflow.amazonaws.com +*.ap-southeast-3.airflow.amazonaws.com +*.ap-southeast-4.airflow.amazonaws.com *.ca-central-1.airflow.amazonaws.com +*.ca-west-1.airflow.amazonaws.com *.eu-central-1.airflow.amazonaws.com +*.eu-central-2.airflow.amazonaws.com *.eu-north-1.airflow.amazonaws.com +*.eu-south-1.airflow.amazonaws.com +*.eu-south-2.airflow.amazonaws.com *.eu-west-1.airflow.amazonaws.com *.eu-west-2.airflow.amazonaws.com *.eu-west-3.airflow.amazonaws.com +*.il-central-1.airflow.amazonaws.com +*.me-central-1.airflow.amazonaws.com +*.me-south-1.airflow.amazonaws.com *.sa-east-1.airflow.amazonaws.com *.us-east-1.airflow.amazonaws.com *.us-east-2.airflow.amazonaws.com +*.us-west-1.airflow.amazonaws.com *.us-west-2.airflow.amazonaws.com // Amazon S3 @@ -12211,9 +12269,25 @@ s3-fips.us-west-2.amazonaws.com s3-object-lambda.us-west-2.amazonaws.com s3-website.us-west-2.amazonaws.com +// Amazon SageMaker Ground Truth +// Submitted by AWS Security <psl-maintainers@amazon.com> +// Reference: 98dbfde4-7802-48c3-8751-b60f204e0d9c +labeling.ap-northeast-1.sagemaker.aws +labeling.ap-northeast-2.sagemaker.aws +labeling.ap-south-1.sagemaker.aws +labeling.ap-southeast-1.sagemaker.aws +labeling.ap-southeast-2.sagemaker.aws +labeling.ca-central-1.sagemaker.aws +labeling.eu-central-1.sagemaker.aws +labeling.eu-west-1.sagemaker.aws +labeling.eu-west-2.sagemaker.aws +labeling.us-east-1.sagemaker.aws +labeling.us-east-2.sagemaker.aws +labeling.us-west-2.sagemaker.aws + // Amazon SageMaker Notebook Instances // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: ce8ae0b1-0070-496d-be88-37c31837af9d +// Reference: b5ea56df-669e-43cc-9537-14aa172f5dfc notebook.af-south-1.sagemaker.aws notebook.ap-east-1.sagemaker.aws notebook.ap-northeast-1.sagemaker.aws @@ -12250,6 +12324,7 @@ notebook-fips.us-gov-east-1.sagemaker.aws notebook.us-gov-west-1.sagemaker.aws notebook-fips.us-gov-west-1.sagemaker.aws notebook.us-west-1.sagemaker.aws +notebook-fips.us-west-1.sagemaker.aws notebook.us-west-2.sagemaker.aws notebook-fips.us-west-2.sagemaker.aws notebook.cn-north-1.sagemaker.com.cn @@ -12257,7 +12332,7 @@ notebook.cn-northwest-1.sagemaker.com.cn // Amazon SageMaker Studio // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: 057ee397-6bf8-4f20-b807-d7bc145ac980 +// Reference: 69c723d9-6e1a-4bff-a203-48eecd203183 studio.af-south-1.sagemaker.aws studio.ap-east-1.sagemaker.aws studio.ap-northeast-1.sagemaker.aws @@ -12271,6 +12346,7 @@ studio.ca-central-1.sagemaker.aws studio.eu-central-1.sagemaker.aws studio.eu-north-1.sagemaker.aws studio.eu-south-1.sagemaker.aws +studio.eu-south-2.sagemaker.aws studio.eu-west-1.sagemaker.aws studio.eu-west-2.sagemaker.aws studio.eu-west-3.sagemaker.aws @@ -12289,6 +12365,11 @@ studio.us-west-2.sagemaker.aws studio.cn-north-1.sagemaker.com.cn studio.cn-northwest-1.sagemaker.com.cn +// Amazon SageMaker with MLflow +// Submited by: AWS Security <psl-maintainers@amazon.com> +// Reference: c19f92b3-a82a-452d-8189-831b572eea7e +*.experiments.sagemaker.aws + // Analytics on AWS // Submitted by AWS Security <psl-maintainers@amazon.com> // Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd @@ -12305,8 +12386,8 @@ analytics-gateway.us-west-2.amazonaws.com // AWS Amplify // Submitted by AWS Security <psl-maintainers@amazon.com> -// Reference: 5ecce854-c033-4fc4-a755-1a9916d9a9bb -*.amplifyapp.com +// Reference: c35bed18-6f4f-424f-9298-5756f2f7d72b +amplifyapp.com // AWS App Runner // Submitted by AWS Security <psl-maintainers@amazon.com> @@ -12382,6 +12463,11 @@ webview-assets.aws-cloud9.us-west-2.amazonaws.com vfs.cloud9.us-west-2.amazonaws.com webview-assets.cloud9.us-west-2.amazonaws.com +// AWS Directory Service +// Submitted by AWS Security <psl-maintainers@amazon.com> +// Reference: a13203e8-42dc-4045-a0d2-2ee67bed1068 +awsapps.com + // AWS Elastic Beanstalk // Submitted by AWS Security <psl-maintainers@amazon.com> // Reference: bb5a965c-dec3-4967-aa22-e306ad064797 @@ -12438,11 +12524,6 @@ eero-stage.online // concludes Amazon -// Amune : https://amune.org/ -// Submitted by Team Amune <cert@amune.org> -t3l3p0rt.net -tele.amune.org - // Apigee : https://apigee.com/ // Submitted by Apigee Security Team <security@apigee.com> apigee.io @@ -12468,6 +12549,10 @@ appudo.net // Submitted by Thomas Orozco <thomas@aptible.com> on-aptible.com +// Aquapal : https://aquapal.net/ +// Submitted by Aki Ueno <admin@aquapal.net> +f5.si + // ASEINet : https://www.aseinet.com/ // Submitted by Asei SEKIGUCHI <mail@aseinet.com> user.aseinet.ne.jp @@ -12503,6 +12588,7 @@ autocode.dev // AVM : https://avm.de // Submitted by Andreas Weise <a.weise@avm.de> +myfritz.link myfritz.net // AVStack Pte. Ltd. : https://avstack.io @@ -12522,19 +12608,10 @@ ecommerce-shop.pl // Submitted by Olivier Benz <olivier.benz@b-data.ch> b-data.io -// backplane : https://www.backplane.io -// Submitted by Anthony Voutas <anthony@backplane.io> -backplaneapp.io - // Balena : https://www.balena.io // Submitted by Petros Angelatos <petrosagg@balena.io> balena-devices.com -// University of Banja Luka : https://unibl.org -// Domains for Republic of Srpska administrative entity. -// Submitted by Marko Ivanovic <kormang@hotmail.rs> -rs.ba - // Banzai Cloud // Submitted by Janos Matyas <info@banzaicloud.com> *.banzai.cloud @@ -12574,6 +12651,10 @@ betainabox.com // Submitted by Nathan O'Sullivan <nathan@mammoth.com.au> bnr.la +// Bip : https://bip.sh +// Submitted by Joel Kennedy <joel@bip.sh> +bip.sh + // Bitbucket : http://bitbucket.org // Submitted by Andy Ortlieb <aortlieb@atlassian.com> bitbucket.io @@ -12616,13 +12697,19 @@ square7.net *.s.brave.io // Brendly : https://brendly.rs -// Submitted by Dusan Radovanovic <dusan.radovanovic@brendly.rs> +// Submitted by Dusan Radovanovic <administracija@brendly.rs> +shop.brendly.hr shop.brendly.rs // BrowserSafetyMark // Submitted by Dave Tharp <browsersafetymark.io@quicinc.com> browsersafetymark.io +// BRS Media : https://brsmedia.com/ +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +radio.am +radio.fm + // Bytemark Hosting : https://www.bytemark.co.uk // Submitted by Paul Cammish <paul.cammish@bytemark.co.uk> uk0.bigv.io @@ -12640,7 +12727,9 @@ mycd.eu // Canva Pty Ltd : https://canva.com/ // Submitted by Joel Aquilina <publicsuffixlist@canva.com> canva-apps.cn +*.my.canvasite.cn canva-apps.com +*.my.canva.site // Carrd : https://carrd.co // Submitted by AJ <aj@carrd.co> @@ -12650,71 +12739,42 @@ carrd.co crd.co ju.mp +// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk +// Submitted by Jamie Tanna <jamie.tanna@digital.cabinet-office.gov.uk> +api.gov.uk + +// CDN77.com : http://www.cdn77.com +// Submitted by Jan Krpes <jan.krpes@cdn77.com> +cdn77-storage.com +rsc.contentproxy9.cz +r.cdn77.net +cdn77-ssl.net +c.cdn77.org +rsc.cdn77.org +ssl.origin.cdn77-secure.org + // CentralNic : http://www.centralnic.com/names/domains // Submitted by registry <gavin.brown@centralnic.com> -ae.org +za.bz br.com cn.com -com.de -com.se de.com eu.com -gb.net -hu.net -jp.net jpn.com mex.com ru.com sa.com -se.net uk.com -uk.net us.com -za.bz za.com - -// No longer operated by CentralNic, these entries should be adopted and/or removed by current operators -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -ar.com -hu.com -kr.com -no.com -qc.com -uy.com - -// Africa.com Web Solutions Ltd : https://registry.africa.com -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -africa.com - -// iDOT Services Limited : http://www.domain.gr.com -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -gr.com - -// Radix FZC : http://domains.in.net -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -in.net -web.in - -// US REGISTRY LLC : http://us.org -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -us.org - -// co.com Registry, LLC : https://registry.co.com -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -co.com - -// Roar Domains LLC : https://roar.basketball/ -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -aus.basketball -nz.basketball - -// BRS Media : https://brsmedia.com/ -// Submitted by Gavin Brown <gavin.brown@centralnic.com> -radio.am -radio.fm - -// c.la : http://www.c.la/ -c.la +com.de +gb.net +hu.net +jp.net +se.net +uk.net +ae.org +com.se // certmgr.org : https://certmgr.org // Submitted by B. Blechschmidt <hostmaster@certmgr.org> @@ -12729,10 +12789,6 @@ cx.ua discourse.group discourse.team -// Clever Cloud : https://www.clever-cloud.com/ -// Submitted by Quentin Adam <noc@clever-cloud.com> -cleverapps.io - // Clerk : https://www.clerk.dev // Submitted by Colin Sidoti <systems@clerk.dev> clerk.app @@ -12742,10 +12798,40 @@ clerkstage.app *.stg.dev *.stgstage.dev +// Clever Cloud : https://www.clever-cloud.com/ +// Submitted by Quentin Adam <noc@clever-cloud.com> +cleverapps.cc +*.services.clever-cloud.com +cleverapps.io +cleverapps.tech + // ClickRising : https://clickrising.com/ // Submitted by Umut Gumeli <infrastructure-publicsuffixlist@clickrising.com> clickrising.net +// Cloud DNS Ltd : http://www.cloudns.net +// Submitted by Aleksander Hristov <noc@cloudns.net> & Boyan Peychev <boyan@cloudns.net> +cloudns.asia +cloudns.be +cloudns.biz +cloudns.cc +cloudns.ch +cloudns.cl +cloudns.club +dnsabr.com +cloudns.cx +cloudns.eu +cloudns.in +cloudns.info +dns-cloud.net +dns-dynamic.net +cloudns.nz +cloudns.org +cloudns.ph +cloudns.pro +cloudns.pw +cloudns.us + // Cloud66 : https://www.cloud66.com/ // Submitted by Khash Sajadi <khash@cloud66.com> c66.me @@ -12760,11 +12846,6 @@ cloudaccess.host freesite.host cloudaccess.net -// cloudControl : https://www.cloudcontrol.com/ -// Submitted by Tobias Wilken <tw@cloudcontrol.com> -cloudcontrolled.com -cloudcontrolapp.com - // Cloudera, Inc. : https://www.cloudera.com/ // Submitted by Kedarnath Waikar <security@cloudera.com> *.cloudera.site @@ -12777,52 +12858,46 @@ trycloudflare.com pages.dev r2.dev workers.dev +cloudflare.net +cdn.cloudflare.net +cdn.cloudflareanycast.net +cdn.cloudflarecn.net +cdn.cloudflareglobal.net + +// cloudscale.ch AG : https://www.cloudscale.ch/ +// Submitted by Gaudenz Steinlin <support@cloudscale.ch> +cust.cloudscale.ch +objects.lpg.cloudscale.ch +objects.rma.cloudscale.ch // Clovyr : https://clovyr.io // Submitted by Patrick Nielsen <patrick@clovyr.io> wnext.app -// co.ca : http://registry.co.ca/ -co.ca +// CNPY : https://cnpy.gdn +// Submitted by Angelo Gladding <angelo@lahacker.net> +cnpy.gdn // Co & Co : https://co-co.nl/ // Submitted by Govert Versluis <govert@co-co.nl> *.otap.co -// i-registry s.r.o. : http://www.i-registry.cz/ -// Submitted by Martin Semrad <semrad@i-registry.cz> -co.cz - -// CDN77.com : http://www.cdn77.com -// Submitted by Jan Krpes <jan.krpes@cdn77.com> -c.cdn77.org -cdn77-ssl.net -r.cdn77.net -rsc.cdn77.org -ssl.origin.cdn77-secure.org - -// Cloud DNS Ltd : http://www.cloudns.net -// Submitted by Aleksander Hristov <noc@cloudns.net> -cloudns.asia -cloudns.biz -cloudns.club -cloudns.cc -cloudns.eu -cloudns.in -cloudns.info -cloudns.org -cloudns.pro -cloudns.pw -cloudns.us +// co.ca : http://registry.co.ca/ +co.ca -// CNPY : https://cnpy.gdn -// Submitted by Angelo Gladding <angelo@lahacker.net> -cnpy.gdn +// co.com Registry, LLC : https://registry.co.com +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +co.com // Codeberg e. V. : https://codeberg.org // Submitted by Moritz Marquardt <git@momar.de> codeberg.page +// CodeSandbox B.V. : https://codesandbox.io +// Submitted by Ives van Hoorne <abuse@codesandbox.io> +csb.app +preview.csb.app + // CoDNS B.V. co.nl co.no @@ -12832,6 +12907,10 @@ co.no webhosting.be hosting-cluster.nl +// Convex : https://convex.dev/ +// Submitted by James Cowling <security@convex.dev> +convex.site + // Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ // Submitted by George Georgievsky <gug@cctld.ru> ac.ru @@ -12844,8 +12923,8 @@ test.ru // COSIMO GmbH : http://www.cosimo.de // Submitted by Rene Marticke <rmarticke@cosimo.de> dyn.cosidns.de -dynamisches-dns.de dnsupdater.de +dynamisches-dns.de internet-dns.de l-o-g-i-n.de dynamic-dns.info @@ -12853,22 +12932,22 @@ feste-ip.net knx-server.net static-access.net -// cPanel L.L.C. : https://www.cpanel.net/ -// Submitted by Dustin Scherer <public.suffix@cpanel.net> -*.cprapid.com +// Craft Docs Ltd : https://www.craft.do/ +// Submitted by Zsombor Fuszenecker <security@craft.do> +craft.me // Craynic, s.r.o. : http://www.craynic.com/ // Submitted by Ales Krajnik <ales.krajnik@craynic.com> realm.cz +// Crisp IM SAS : https://crisp.chat/ +// Submitted by Baptiste Jamin <hostmaster@crisp.chat> +on.crisp.email + // Cryptonomic : https://cryptonomic.net/ // Submitted by Andrew Cady <public-suffix-list@cryptonomic.net> *.cryptonomic.net -// Cupcake : https://cupcake.io/ -// Submitted by Jonathan Rudenberg <jonathan@cupcake.io> -cupcake.is - // Curv UG : https://curv-labs.de/ // Submitted by Marvin Wiesner <Marvin@curv-labs.de> curv.dev @@ -12881,12 +12960,9 @@ curv.dev *.ocp.customer-oci.com *.ocs.customer-oci.com -// Cyclic Software : https://www.cyclic.sh -// Submitted by Kam Lasater <dns-admin@cyclic.sh> -cyclic.app -cyclic.cloud -cyclic-app.com -cyclic.co.in +// cyber_Folks S.A. : https://cyberfolks.pl +// Submitted by Bartlomiej Kida <security@cyberfolks.pl> +cfolks.pl // cyon GmbH : https://www.cyon.ch/ // Submitted by Dominic Luechinger <dol@cyon.ch> @@ -12895,23 +12971,9 @@ cyon.site // Danger Science Group: https://dangerscience.com/ // Submitted by Skylar MacDonald <skylar@dangerscience.com> +platform0.app fnwk.site folionetwork.site -platform0.app - -// Daplie, Inc : https://daplie.com -// Submitted by AJ ONeal <aj@daplie.com> -daplie.me -localhost.daplie.me - -// Datto, Inc. : https://www.datto.com/ -// Submitted by Philipp Heckel <ph@datto.com> -dattolocal.com -dattorelay.com -dattoweb.com -mydatto.com -dattolocal.net -mydatto.net // Dansk.net : http://www.dansk.net/ // Submitted by Anani Voule <digital@digital.co.dk> @@ -12921,6 +12983,11 @@ firm.dk reg.dk store.dk +// Daplie, Inc : https://daplie.com +// Submitted by AJ ONeal <aj@daplie.com> +daplie.me +localhost.daplie.me + // dappnode.io : https://dappnode.io/ // Submitted by Abel Boldu / DAppNode Team <community@dappnode.io> dyndns.dappnode.io @@ -12933,6 +13000,7 @@ dyndns.dappnode.io // Dark, Inc. : https://darklang.com // Submitted by Paul Biggar <ops@darklang.com> builtwithdark.com +darklang.io // DataDetect, LLC. : https://datadetect.com // Submitted by Andrew Banchich <abanchich@sceven.com> @@ -12943,14 +13011,40 @@ instance.datadetect.com // Submitted by Richard Li <secalert@datawire.io> edgestack.me +// Datto, Inc. : https://www.datto.com/ +// Submitted by Philipp Heckel <ph@datto.com> +dattolocal.com +dattorelay.com +dattoweb.com +mydatto.com +dattolocal.net +mydatto.net + // DDNS5 : https://ddns5.com // Submitted by Cameron Elliott <cameron@cameronelliott.com> ddns5.com +// ddnss.de : https://www.ddnss.de/ +// Submitted by Robert Niedziela <webmaster@ddnss.de> +ddnss.de +dyn.ddnss.de +dyndns.ddnss.de +dyn-ip24.de +dyndns1.de +home-webserver.de +dyn.home-webserver.de +myhome-server.de +ddnss.org + // Debian : https://www.debian.org/ // Submitted by Peter Palfrader / Debian Sysadmin Team <dsa-publicsuffixlist@debian.org> debian.net +// Definima : http://www.definima.com/ +// Submitted by Maxence Bitterli <maxence@definima.com> +definima.io +definima.net + // Deno Land Inc : https://deno.com/ // Submitted by Luca Casonato <hostmaster@deno.com> deno.dev @@ -12965,6 +13059,24 @@ dedyn.io deta.app deta.dev +// dhosting.pl Sp. z o.o.: https://dhosting.pl/ +// Submitted by Michal Kokoszkiewicz <bok@dhosting.pl> +dfirma.pl +dkonto.pl +you2.pl + +// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ +// Submitted by Braxton Huggins <psl-maintainers@digitalocean.com> +ondigitalocean.app + +// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ +// Submitted by Robin H. Johnson <psl-maintainers@digitalocean.com> +*.digitaloceanspaces.com + +// DigitalPlat : https://www.digitalplat.org/ +// Submitted by Edward Hsing <contact@digitalplat.org> +us.kg + // Diher Solutions : https://diher.solutions // Submitted by Didi Hermawan <mail@diher.solutions> *.rss.my.id @@ -12983,6 +13095,10 @@ jozi.biz // Submitted by Norbert Auler <mail@dnshome.de> dnshome.de +// dnstrace.pro : https://dnstrace.pro/ +// Submitted by Chris Partridge <chris@partridge.tech> +bci.dnstrace.pro + // DotArai : https://www.dotarai.com/ // Submitted by Atsadawat Netcharadsang <atsadawat@dotarai.co.th> online.th @@ -13000,6 +13116,10 @@ shoparena.pl // Submitted by Andrew Farmer <andrew.farmer@dreamhost.com> dreamhosters.com +// Dreamyoungs, Inc. : https://durumis.com +// Submitted by Infra Team <infra@durumis.com> +durumis.com + // Drobo : http://www.drobo.com/ // Submitted by Ricardo Padilha <rpadilha@drobo.com> mydrobo.com @@ -13013,19 +13133,32 @@ drud.us // Submitted by Richard Harper <richard@duckdns.org> duckdns.org -// Bip : https://bip.sh -// Submitted by Joel Kennedy <joel@bip.sh> -bip.sh - -// bitbridge.net : Submitted by Craig Welch, abeliidev@gmail.com -bitbridge.net - // dy.fi : http://dy.fi/ // Submitted by Heikki Hannikainen <hessu@hes.iki.fi> dy.fi tunk.org // DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ +dyndns.biz +for-better.biz +for-more.biz +for-some.biz +for-the.biz +selfip.biz +webhop.biz +ftpaccess.cc +game-server.cc +myphotos.cc +scrapping.cc +blogdns.com +cechire.com +dnsalias.com +dnsdojo.com +doesntexist.com +dontexist.com +doomdns.com +dyn-o-saur.com +dynalias.com dyndns-at-home.com dyndns-at-work.com dyndns-blog.com @@ -13040,64 +13173,14 @@ dyndns-server.com dyndns-web.com dyndns-wiki.com dyndns-work.com -dyndns.biz -dyndns.info -dyndns.org -dyndns.tv -at-band-camp.net -ath.cx -barrel-of-knowledge.info -barrell-of-knowledge.info -better-than.tv -blogdns.com -blogdns.net -blogdns.org -blogsite.org -boldlygoingnowhere.org -broke-it.net -buyshouses.net -cechire.com -dnsalias.com -dnsalias.net -dnsalias.org -dnsdojo.com -dnsdojo.net -dnsdojo.org -does-it.net -doesntexist.com -doesntexist.org -dontexist.com -dontexist.net -dontexist.org -doomdns.com -doomdns.org -dvrdns.org -dyn-o-saur.com -dynalias.com -dynalias.net -dynalias.org -dynathome.net -dyndns.ws -endofinternet.net -endofinternet.org -endoftheinternet.org est-a-la-maison.com est-a-la-masion.com est-le-patron.com est-mon-blogueur.com -for-better.biz -for-more.biz -for-our.info -for-some.biz -for-the.biz -forgot.her.name -forgot.his.name from-ak.com from-al.com from-ar.com -from-az.net from-ca.com -from-co.net from-ct.com from-dc.com from-de.com @@ -13110,10 +13193,8 @@ from-il.com from-in.com from-ks.com from-ky.com -from-la.net from-ma.com from-md.com -from-me.org from-mi.com from-mn.com from-mo.com @@ -13126,7 +13207,6 @@ from-nh.com from-nj.com from-nm.com from-nv.com -from-ny.net from-oh.com from-ok.com from-or.com @@ -13144,45 +13224,18 @@ from-wa.com from-wi.com from-wv.com from-wy.com -ftpaccess.cc -fuettertdasnetz.de -game-host.org -game-server.cc getmyip.com -gets-it.net -go.dyndns.org gotdns.com -gotdns.org -groks-the.info -groks-this.info -ham-radio-op.net -here-for-more.info hobby-site.com -hobby-site.org -home.dyndns.org -homedns.org -homeftp.net -homeftp.org -homeip.net homelinux.com -homelinux.net -homelinux.org homeunix.com -homeunix.net -homeunix.org iamallama.com -in-the-band.net is-a-anarchist.com is-a-blogger.com is-a-bookkeeper.com -is-a-bruinsfan.org is-a-bulls-fan.com -is-a-candidate.org is-a-caterer.com -is-a-celticsfan.org is-a-chef.com -is-a-chef.net -is-a-chef.org is-a-conservative.com is-a-cpa.com is-a-cubicle-slave.com @@ -13191,31 +13244,25 @@ is-a-designer.com is-a-doctor.com is-a-financialadvisor.com is-a-geek.com -is-a-geek.net -is-a-geek.org is-a-green.com is-a-guru.com is-a-hard-worker.com is-a-hunter.com -is-a-knight.org is-a-landscaper.com is-a-lawyer.com is-a-liberal.com is-a-libertarian.com -is-a-linux-user.org is-a-llama.com is-a-musician.com is-a-nascarfan.com is-a-nurse.com is-a-painter.com -is-a-patsfan.org is-a-personaltrainer.com is-a-photographer.com is-a-player.com is-a-republican.com is-a-rockstar.com is-a-socialist.com -is-a-soxfan.org is-a-student.com is-a-teacher.com is-a-techie.com @@ -13227,113 +13274,150 @@ is-an-anarchist.com is-an-artist.com is-an-engineer.com is-an-entertainer.com -is-by.us is-certified.com -is-found.org is-gone.com is-into-anime.com is-into-cars.com is-into-cartoons.com is-into-games.com is-leet.com -is-lost.org is-not-certified.com -is-saved.org is-slick.com is-uberleet.com -is-very-bad.org -is-very-evil.org -is-very-good.org -is-very-nice.org -is-very-sweet.org is-with-theband.com isa-geek.com -isa-geek.net -isa-geek.org isa-hockeynut.com issmarterthanyou.com +likes-pie.com +likescandy.com +neat-url.com +saves-the-whales.com +selfip.com +sells-for-less.com +sells-for-u.com +servebbs.com +simple-url.com +space-to-rent.com +teaches-yoga.com +writesthisblog.com +ath.cx +fuettertdasnetz.de isteingeek.de istmein.de -kicks-ass.net -kicks-ass.org -knowsitall.info -land-4-sale.us lebtimnetz.de leitungsen.de -likes-pie.com -likescandy.com +traeumtgerade.de +barrel-of-knowledge.info +barrell-of-knowledge.info +dyndns.info +for-our.info +groks-the.info +groks-this.info +here-for-more.info +knowsitall.info +selfip.info +webhop.info +forgot.her.name +forgot.his.name +at-band-camp.net +blogdns.net +broke-it.net +buyshouses.net +dnsalias.net +dnsdojo.net +does-it.net +dontexist.net +dynalias.net +dynathome.net +endofinternet.net +from-az.net +from-co.net +from-la.net +from-ny.net +gets-it.net +ham-radio-op.net +homeftp.net +homeip.net +homelinux.net +homeunix.net +in-the-band.net +is-a-chef.net +is-a-geek.net +isa-geek.net +kicks-ass.net +office-on-the.net +podzone.net +scrapper-site.net +selfip.net +sells-it.net +servebbs.net +serveftp.net +thruhere.net +webhop.net merseine.nu mine.nu +shacknet.nu +blogdns.org +blogsite.org +boldlygoingnowhere.org +dnsalias.org +dnsdojo.org +doesntexist.org +dontexist.org +doomdns.org +dvrdns.org +dynalias.org +dyndns.org +go.dyndns.org +home.dyndns.org +endofinternet.org +endoftheinternet.org +from-me.org +game-host.org +gotdns.org +hobby-site.org +homedns.org +homeftp.org +homelinux.org +homeunix.org +is-a-bruinsfan.org +is-a-candidate.org +is-a-celticsfan.org +is-a-chef.org +is-a-geek.org +is-a-knight.org +is-a-linux-user.org +is-a-patsfan.org +is-a-soxfan.org +is-found.org +is-lost.org +is-saved.org +is-very-bad.org +is-very-evil.org +is-very-good.org +is-very-nice.org +is-very-sweet.org +isa-geek.org +kicks-ass.org misconfused.org -mypets.ws -myphotos.cc -neat-url.com -office-on-the.net -on-the-web.tv -podzone.net podzone.org readmyblog.org -saves-the-whales.com -scrapper-site.net -scrapping.cc -selfip.biz -selfip.com -selfip.info -selfip.net selfip.org -sells-for-less.com -sells-for-u.com -sells-it.net sellsyourhome.org -servebbs.com -servebbs.net servebbs.org -serveftp.net serveftp.org servegame.org -shacknet.nu -simple-url.com -space-to-rent.com stuff-4-sale.org -stuff-4-sale.us -teaches-yoga.com -thruhere.net -traeumtgerade.de -webhop.biz -webhop.info -webhop.net webhop.org +better-than.tv +dyndns.tv +on-the-web.tv worse-than.tv -writesthisblog.com - -// ddnss.de : https://www.ddnss.de/ -// Submitted by Robert Niedziela <webmaster@ddnss.de> -ddnss.de -dyn.ddnss.de -dyndns.ddnss.de -dyndns1.de -dyn-ip24.de -home-webserver.de -dyn.home-webserver.de -myhome-server.de -ddnss.org - -// Definima : http://www.definima.com/ -// Submitted by Maxence Bitterli <maxence@definima.com> -definima.net -definima.io - -// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ -// Submitted by Braxton Huggins <psl-maintainers@digitalocean.com> -ondigitalocean.app - -// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ -// Submitted by Robin H. Johnson <psl-maintainers@digitalocean.com> -*.digitaloceanspaces.com - -// dnstrace.pro : https://dnstrace.pro/ -// Submitted by Chris Partridge <chris@partridge.tech> -bci.dnstrace.pro +is-by.us +land-4-sale.us +stuff-4-sale.us +dyndns.ws +mypets.ws // Dynu.com : https://www.dynu.com/ // Submitted by Sue Ye <sue@dynu.com> @@ -13368,6 +13452,31 @@ e4.cz easypanel.app easypanel.host +// EasyWP : https://www.easywp.com +// Submitted by <infracloudteam@namecheap.com> +*.ewp.live + +// ECG Robotics, Inc: https://ecgrobotics.org +// Submitted by <frc1533@ecgrobotics.org> +onred.one +staging.onred.one + +// eDirect Corp. : https://hosting.url.com.tw/ +// Submitted by C.S. chang <cschang@corp.url.com.tw> +twmail.cc +twmail.net +twmail.org +mymailer.com.tw +url.tw + +// Electromagnetic Field : https://www.emfcamp.org +// Submitted by <noc@emfcamp.org> +at.emf.camp + +// Elefunc, Inc. : https://elefunc.com +// Submitted by Cetin Sert <domains@elefunc.com> +rt.ht + // Elementor : Elementor Ltd. // Submitted by Anton Barkan <antonb@elementor.com> elementor.cloud @@ -13378,7 +13487,7 @@ elementor.cool en-root.fr // Enalean SAS: https://www.enalean.com -// Submitted by Thomas Cottier <thomas.cottier@enalean.com> +// Submitted by Enalean Security Team <security@enalean.com> mytuleap.com tuleap-partners.com @@ -13387,11 +13496,6 @@ tuleap-partners.com encr.app encoreapi.com -// ECG Robotics, Inc: https://ecgrobotics.org -// Submitted by <frc1533@ecgrobotics.org> -onred.one -staging.onred.one - // encoway GmbH : https://www.encoway.de // Submitted by Marcel Daus <cloudops@encoway.de> eu.encoway.cloud @@ -13470,13 +13574,15 @@ us-2.evennode.com us-3.evennode.com us-4.evennode.com -// eDirect Corp. : https://hosting.url.com.tw/ -// Submitted by C.S. chang <cschang@corp.url.com.tw> -twmail.cc -twmail.net -twmail.org -mymailer.com.tw -url.tw +// Evervault : https://evervault.com +// Submitted by Hannah Neary <engineering@evervault.com> +relay.evervault.app +relay.evervault.dev + +// Expo : https://expo.dev/ +// Submitted by James Ide <psl@expo.dev> +expo.app +staging.expo.app // Fabrica Technologies, Inc. : https://www.fabrica.dev/ // Submitted by Eric Jiang <eric@fabrica.dev> @@ -13568,8 +13674,6 @@ u.channelsdvr.net edgecompute.app fastly-edge.com fastly-terrarium.com -fastlylb.net -map.fastlylb.net freetls.fastly.net map.fastly.net a.prod.fastly.net @@ -13577,6 +13681,8 @@ global.prod.fastly.net a.ssl.fastly.net b.ssl.fastly.net global.ssl.fastly.net +fastlylb.net +map.fastlylb.net // Fastmail : https://www.fastmail.com/ // Submitted by Marc Bradshaw <marc@fastmailteam.com> @@ -13590,6 +13696,12 @@ myfast.host fastvps.site myfast.space +// FearWorks Media Ltd. : https://fearworksmedia.co.uk +// submitted by Keith Fairley <domains@fearworksmedia.co.uk> +conn.uk +copro.uk +hosp.uk + // Fedora : https://fedoraproject.org/ // submitted by Patrick Uiterwijk <puiterwijk@fedoraproject.org> fedorainfracloud.org @@ -13598,12 +13710,6 @@ cloud.fedoraproject.org app.os.fedoraproject.org app.os.stg.fedoraproject.org -// FearWorks Media Ltd. : https://fearworksmedia.co.uk -// submitted by Keith Fairley <domains@fearworksmedia.co.uk> -conn.uk -copro.uk -hosp.uk - // Fermax : https://fermax.com/ // submitted by Koen Van Isterdael <k.vanisterdael@fermax.be> mydobiss.com @@ -13615,12 +13721,6 @@ fh-muenster.io // Filegear Inc. : https://www.filegear.com // Submitted by Jason Zhu <jason@owtware.com> filegear.me -filegear-au.me -filegear-de.me -filegear-gb.me -filegear-ie.me -filegear-jp.me -filegear-sg.me // Firebase, Inc. // Submitted by Chris Raynor <chris@firebase.com> @@ -13636,7 +13736,6 @@ flap.id // FlashDrive : https://flashdrive.io // Submitted by Eric Chan <support@flashdrive.io> -onflashdrive.app fldrv.com // FlutterFlow : https://flutterflow.io @@ -13646,12 +13745,8 @@ flutterflow.app // fly.io: https://fly.io // Submitted by Kurt Mackey <kurt@fly.io> fly.dev -edgeapp.net shw.io - -// Flynn : https://flynn.io -// Submitted by Jonathan Rudenberg <jonathan@flynn.io> -flynnhosting.net +edgeapp.net // Forgerock : https://www.forgerock.com // Submitted by Roderick Parr <roderick.parr@forgerock.com> @@ -13659,7 +13754,8 @@ forgeblocks.com id.forgerock.io // Framer : https://www.framer.com -// Submitted by Koen Rouwhorst <koenrh@framer.com> +// Submitted by Koen Rouwhorst <security@framer.com> +framer.ai framer.app framercanvas.com framer.media @@ -13667,14 +13763,6 @@ framer.photos framer.website framer.wiki -// Frusky MEDIA&PR : https://www.frusky.de -// Submitted by Victor Pupynin <hallo@frusky.de> -*.frusky.de - -// RavPage : https://www.ravpage.co.il -// Submitted by Roni Horowitz <roni@responder.co.il> -ravpage.co.il - // Frederik Braun https://frederik-braun.com // Submitted by Frederik Braun <fb@frederik-braun.com> 0e.vc @@ -13696,10 +13784,32 @@ freedesktop.org // Submitted by Cadence <contact@freemyip.com> freemyip.com +// Frusky MEDIA&PR : https://www.frusky.de +// Submitted by Victor Pupynin <hallo@frusky.de> +*.frusky.de + // FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at // Submitted by Daniel A. Maierhofer <vorstand@funkfeuer.at> wien.funkfeuer.at +// Future Versatile Group. : https://www.fvg-on.net/ +// T.Kabu <webmaster@fvg-on.net> +daemon.asia +dix.asia +mydns.bz +0am.jp +0g0.jp +0j0.jp +0t0.jp +mydns.jp +pgw.jp +wjg.jp +keyword-on.net +live-on.net +server-on.net +mydns.tw +mydns.vc + // Futureweb GmbH : https://www.futureweb.at // Submitted by Andreas Schnederle-Wagner <schnederle@futureweb.at> *.futurecms.at @@ -13717,6 +13827,8 @@ aliases121.com // GDS : https://www.gov.uk/service-manual/technology/managing-domain-names // Submitted by Stephen Ford <hostmaster@digital.cabinet-office.gov.uk> +campaign.gov.uk +service.gov.uk independent-commission.uk independent-inquest.uk independent-inquiry.uk @@ -13724,12 +13836,6 @@ independent-panel.uk independent-review.uk public-inquiry.uk royal-commission.uk -campaign.gov.uk -service.gov.uk - -// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk -// Submitted by Jamie Tanna <jamie.tanna@digital.cabinet-office.gov.uk> -api.gov.uk // Gehirn Inc. : https://www.gehirn.co.jp/ // Submitted by Kohei YOSHIDA <tech@gehirn.co.jp> @@ -13743,9 +13849,11 @@ gentlentapis.com lab.ms cdn-edges.net -// Ghost Foundation : https://ghost.org -// Submitted by Matt Hanley <security@ghost.org> -ghost.io +// Getlocalcert: https://www.getlocalcert.net +// Submitted by Robert Alexander <support@getlocalcert.net> +localcert.net +localhostcert.net +corpnet.work // GignoSystemJapan: http://gsj.bz // Submitted by GignoSystemJapan <kakutou-ec@gsj.bz> @@ -13889,89 +13997,73 @@ whitesnow.jp zombie.jp heteml.net -// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ -// Submitted by Tom Whitwell <gov-uk-paas-support@digital.cabinet-office.gov.uk> -cloudapps.digital -london.cloudapps.digital - -// GOV.UK Pay : https://www.payments.service.gov.uk/ -// Submitted by Richard Baker <richard.baker@digital.cabinet-office.gov.uk> -pymnt.uk - -// GlobeHosting, Inc. -// Submitted by Zoltan Egresi <egresi@globehosting.com> -ro.im +// GoDaddy Registry : https://registry.godaddy +// Submitted by Rohan Durrant <tldns@registry.godaddy> +graphic.design // GoIP DNS Services : http://www.goip.de // Submitted by Christian Poulter <milchstrasse@goip.de> goip.de // Google, Inc. -// Submitted by Eduardo Vela <evn@google.com> -*.run.app -web.app -*.0emm.com -appspot.com -*.r.appspot.com -codespot.com -googleapis.com -googlecode.com -pagespeedmobilizer.com -publishproxy.com -withgoogle.com -withyoutube.com -*.gateway.dev -cloud.goog -translate.goog -*.usercontent.goog -cloudfunctions.net +// Submitted by Shannon McCabe <public-suffix-editors@google.com> blogspot.ae blogspot.al blogspot.am +*.hosted.app +*.run.app +web.app +blogspot.com.ar +blogspot.co.at +blogspot.com.au blogspot.ba blogspot.be blogspot.bg blogspot.bj +blogspot.com.br +blogspot.com.by blogspot.ca blogspot.cf blogspot.ch blogspot.cl -blogspot.co.at -blogspot.co.id -blogspot.co.il -blogspot.co.ke -blogspot.co.nz -blogspot.co.uk -blogspot.co.za -blogspot.com -blogspot.com.ar -blogspot.com.au -blogspot.com.br -blogspot.com.by blogspot.com.co -blogspot.com.cy -blogspot.com.ee -blogspot.com.eg -blogspot.com.es -blogspot.com.mt -blogspot.com.ng -blogspot.com.tr -blogspot.com.uy +*.0emm.com +appspot.com +*.r.appspot.com +blogspot.com +codespot.com +googleapis.com +googlecode.com +pagespeedmobilizer.com +publishproxy.com +withgoogle.com +withyoutube.com blogspot.cv +blogspot.com.cy blogspot.cz blogspot.de +*.gateway.dev blogspot.dk +blogspot.com.ee +blogspot.com.eg +blogspot.com.es blogspot.fi blogspot.fr +cloud.goog +translate.goog +*.usercontent.goog blogspot.gr blogspot.hk blogspot.hr blogspot.hu +blogspot.co.id blogspot.ie +blogspot.co.il blogspot.in blogspot.is blogspot.it blogspot.jp +blogspot.co.ke blogspot.kr blogspot.li blogspot.lt @@ -13979,10 +14071,14 @@ blogspot.lu blogspot.md blogspot.mk blogspot.mr +blogspot.com.mt blogspot.mx blogspot.my +cloudfunctions.net +blogspot.com.ng blogspot.nl blogspot.no +blogspot.co.nz blogspot.pe blogspot.pt blogspot.qa @@ -13996,21 +14092,34 @@ blogspot.si blogspot.sk blogspot.sn blogspot.td +blogspot.com.tr blogspot.tw blogspot.ug +blogspot.co.uk +blogspot.com.uy blogspot.vn +blogspot.co.za // Goupile : https://goupile.fr // Submitted by Niels Martignene <hello@goupile.fr> goupile.fr +// GOV.UK Pay : https://www.payments.service.gov.uk/ +// Submitted by Richard Baker <richard.baker@digital.cabinet-office.gov.uk> +pymnt.uk + +// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ +// Submitted by Tom Whitwell <gov-uk-paas-support@digital.cabinet-office.gov.uk> +cloudapps.digital +london.cloudapps.digital + // Government of the Netherlands: https://www.government.nl // Submitted by <domeinnaam@minaz.nl> gov.nl -// Group 53, LLC : https://www.group53.com -// Submitted by Tyler Todd <noc@nova53.net> -awsmppl.com +// GrayJay Web Solutions Inc. : https://grayjaysports.ca +// Submitted by Matt Yamkowy <info@grayjaysports.ca> +grayjayleagues.com // GünstigBestellen : https://günstigbestellen.de // Submitted by Furkan Akkoc <info@hendelzon.de> @@ -14027,10 +14136,15 @@ caa.li ua.rs conf.se +// Häkkinen.fi +// Submitted by Eero Häkkinen <Eero+psl@Häkkinen.fi> +xn--hkkinen-5wa.fi +häkkinen.fi + // Handshake : https://handshake.org // Submitted by Mike Damm <md@md.vc> -hs.zone hs.run +hs.zone // Hashbang : https://hashbang.sh hashbang.sh @@ -14040,10 +14154,28 @@ hashbang.sh hasura.app hasura-app.io +// Hatena Co., Ltd. : https://hatena.co.jp +// Submitted by Masato Nakamura <blog-developers@hatena.ne.jp> +hatenablog.com +hatenadiary.com +hateblo.jp +hatenablog.jp +hatenadiary.jp +hatenadiary.org + // Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de // Submitted by Richard Zowalla <mi-admin@hs-heilbronn.de> pages.it.hs-heilbronn.de +// HeiyuSpace: https://lazycat.cloud +// Submitted by Xia Bin <admin@lazycat.cloud> +heiyu.space + +// Helio Networks : https://heliohost.org +// Submitted by Ben Frede <admin@heliohost.org> +helioho.st +heliohost.us + // Hepforge : https://www.hepforge.org // Submitted by David Grellscheid <admin@hepforge.org> hepforge.org @@ -14057,7 +14189,6 @@ herokussl.com // Submitted by Oren Eini <oren@ravendb.net> ravendb.cloud ravendb.community -ravendb.me development.run ravendb.run @@ -14079,7 +14210,6 @@ secaas.hk // Submitted by Danilo De Franco<info@hoplix.shop> hoplix.shop - // HOSTBIP REGISTRY : https://www.hostbip.com/ // Submitted by Atanunu Igbunuroghene <publicsuffixlist@hostbip.com> orx.biz @@ -14096,19 +14226,30 @@ sch.so // Submitted by Bohdan Dub <support@hostfly.com.ua> ie.ua -// HostyHosting (hostyhosting.com) +// HostyHosting (https://hostyhosting.com) hostyhosting.io -// Häkkinen.fi -// Submitted by Eero Häkkinen <Eero+psl@Häkkinen.fi> -xn--hkkinen-5wa.fi -häkkinen.fi +// Hypernode B.V. : https://www.hypernode.com/ +// Submitted by Cipriano Groenendal <security@nl.team.blue> +hypernode.io + +// I-O DATA DEVICE, INC. : http://www.iodata.com/ +// Submitted by Yuji Minagawa <domains-admin@iodata.jp> +iobb.net + +// i-registry s.r.o. : http://www.i-registry.cz/ +// Submitted by Martin Semrad <semrad@i-registry.cz> +co.cz // Ici la Lune : http://www.icilalune.com/ // Submitted by Simon Morvan <simon@icilalune.com> *.moonscale.io moonscale.net +// iDOT Services Limited : http://www.domain.gr.com +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +gr.com + // iki.fi // Submitted by Hannu Aronsson <haa@iki.fi> iki.fi @@ -14118,11 +14259,6 @@ iki.fi ibxos.it iliadboxos.it -// Impertrix Solutions : <https://impertrixcdn.com> -// Submitted by Zhixiang Zhao <csuite@impertrix.com> -impertrixcdn.com -impertrix.com - // Incsub, LLC: https://incsub.com/ // Submitted by Aaron Edwards <sysadmins@incsub.com> smushcdn.com @@ -14138,10 +14274,10 @@ in-berlin.de in-brb.de in-butter.de in-dsl.de -in-dsl.net -in-dsl.org in-vpn.de +in-dsl.net in-vpn.net +in-dsl.org in-vpn.org // info.at : http://www.info.at/ @@ -14149,7 +14285,7 @@ biz.at info.at // info.cx : http://info.cx -// Submitted by Jacob Slater <whois@igloo.to> +// Submitted by June Slater <whois@igloo.to> info.cx // Interlegis : http://www.interlegis.leg.br @@ -14190,6 +14326,15 @@ pixolino.com // Submitted by Vasiliy Sheredeko <piphon@gmail.com> na4u.ru +// IONOS SE : https://www.ionos.com/, +// IONOS Group SE: https://www.ionos-group.com/ +// submitted by Henrik Willert <security@ionos.com> +apps-1and1.com +live-website.com +apps-1and1.net +websitebuilder.online +app-ionos.space + // iopsys software solutions AB : https://iopsys.eu/ // Submitted by Roman Azarenko <roman.azarenko@iopsys.eu> iopsys.se @@ -14198,6 +14343,14 @@ iopsys.se // Submitted by Matthew Hardeman <mhardeman@ipifony.com> ipifony.net +// ir.md : https://nic.ir.md +// Submitted by Ali Soizi <info@nic.ir.md> +ir.md + +// is-a.dev : https://www.is-a.dev +// Submitted by William Harrison <admin@m.is-a.dev> +is-a.dev + // IServ GmbH : https://iserv.de // Submitted by Mario Hoberg <info@iserv.de> iservschule.de @@ -14207,10 +14360,6 @@ schulserver.de test-iserv.de iserv.dev -// I-O DATA DEVICE, INC. : http://www.iodata.com/ -// Submitted by Yuji Minagawa <domains-admin@iodata.jp> -iobb.net - // Jelastic, Inc. : https://jelastic.com/ // Submitted by Ihor Kolodyuk <ik@jelastic.com> mel.cloudlets.com.au @@ -14306,10 +14455,15 @@ myjino.ru // Submitted by Daniel Fariña <ingenieria@jotelulu.com> jotelulu.cloud +// JouwWeb B.V. : https://www.jouwweb.nl +// Submitted by Camilo Sperberg <tech@webador.com> +webadorsite.com +jouwweb.site + // Joyent : https://www.joyent.com/ // Submitted by Brian Bennett <brian.bennett@joyent.com> -*.triton.zone *.cns.joyent.com +*.triton.zone // JS.ORG : http://dns.js.org // Submitted by Stefan Keim <admin@js.org> @@ -14328,6 +14482,11 @@ ktistory.com // Submitted by Tomi Juntunen <erani@kapsi.fi> kapsi.fi +// Katholieke Universiteit Leuven: https://www.kuleuven.be +// Submitted by Abuse KU Leuven <abuse@kuleuven.be> +ezproxy.kuleuven.be +kuleuven.cloud + // Keyweb AG : https://www.keyweb.de // Submitted by Martin Dannehl <postmaster@keymachine.de> keymachine.de @@ -14345,23 +14504,14 @@ knightpoint.systems // Submitted by Iván Oliva <ivan.oliva@koobin.com> koobin.events -// KUROKU LTD : https://kuroku.ltd/ -// Submitted by DisposaBoy <security@oya.to> -oya.to - -// Katholieke Universiteit Leuven: https://www.kuleuven.be -// Submitted by Abuse KU Leuven <abuse@kuleuven.be> -kuleuven.cloud -ezproxy.kuleuven.be - -// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf -co.krd -edu.krd - // Krellian Ltd. : https://krellian.com // Submitted by Ben Francis <ben@krellian.com> -krellian.net webthings.io +krellian.net + +// KUROKU LTD : https://kuroku.ltd/ +// Submitted by DisposaBoy <security@oya.to> +oya.to // LCube - Professional hosting e.K. : https://www.lcube-webhosting.de // Submitted by Lars Laehn <info@lcube.de> @@ -14379,6 +14529,14 @@ lpusercontent.com // Submitted by Lelux Admin <publisuffix@lelux.site> lelux.site +// libp2p project : https://libp2p.io +// Submitted by Interplanetary Shipyard <psl@ipshipyard.com> +libp2p.direct + +// Libre IT Ltd : https://libre.nz +// Submitted by Tomas Maggio <support@libre.nz> +runcontainers.dev + // Lifetime Hosting : https://Lifetime.Hosting/ // Submitted by Mike Fillator <support@lifetime.hosting> co.business @@ -14389,14 +14547,10 @@ co.network co.place co.technology -// Lightmaker Property Manager, Inc. : https://app.lmpm.com/ -// Submitted by Greg Holland <greg.holland@lmpm.com> -app.lmpm.com - // linkyard ldt: https://www.linkyard.ch/ // Submitted by Mario Siegenthaler <mario.siegenthaler@linkyard.ch> -linkyard.cloud linkyard-cloud.ch +linkyard.cloud // Linode : https://linode.com // Submitted by <security@linode.com> @@ -14409,6 +14563,11 @@ ip.linodeusercontent.com // Submitted by Victor Velchev <admin@liquidnetlimited.com> we.bs +// Listen53 : https://www.l53.net +// Submitted by Gerry Keh <biz@l53.net> +filegear-sg.me +ggff.net + // Localcert : https://localcert.dev // Submitted by Lann Martin <security@localcert.dev> *.user.localcert.dev @@ -14425,14 +14584,14 @@ loginline.io loginline.services loginline.site -// Lokalized : https://lokalized.nl -// Submitted by Noah Taheij <noah@lokalized.nl> -servers.run - // Lõhmus Family, The // Submitted by Heiki Lõhmus <hostmaster at lohmus dot me> lohmus.me +// Lokalized : https://lokalized.nl +// Submitted by Noah Taheij <noah@lokalized.nl> +servers.run + // LubMAN UMCS Sp. z o.o : https://lubman.pl/ // Submitted by Ireneusz Maliszewski <ireneusz.maliszewski@lubman.pl> krasnik.pl @@ -14451,18 +14610,19 @@ lugs.org.uk // Lukanet Ltd : https://lukanet.com // Submitted by Anton Avramov <register@lukanet.com> barsy.bg -barsy.co.uk -barsyonline.co.uk +barsy.club barsycenter.com barsyonline.com -barsy.club barsy.de +barsy.dev barsy.eu +barsy.gr barsy.in barsy.info barsy.io barsy.me barsy.menu +barsyonline.menu barsy.mobi barsy.net barsy.online @@ -14470,42 +14630,43 @@ barsy.org barsy.pro barsy.pub barsy.ro +barsy.rs barsy.shop +barsyonline.shop barsy.site +barsy.store barsy.support barsy.uk +barsy.co.uk +barsyonline.co.uk // Magento Commerce // Submitted by Damien Tournoud <dtournoud@magento.cloud> *.magentosite.cloud -// May First - People Link : https://mayfirst.org/ -// Submitted by Jamie McClelland <info@mayfirst.org> -mayfirst.info -mayfirst.org - // Mail.Ru Group : https://hb.cldmail.ru // Submitted by Ilya Zaretskiy <zaretskiy@corp.mail.ru> hb.cldmail.ru -// Mail Transfer Platform : https://www.neupeer.com -// Submitted by Li Hui <lihui@neupeer.com> -cn.vu +// May First - People Link : https://mayfirst.org/ +// Submitted by Jamie McClelland <info@mayfirst.org> +mayfirst.info +mayfirst.org // Maze Play: https://www.mazeplay.com // Submitted by Adam Humpherys <adam@mws.dev> mazeplay.com -// mcpe.me : https://mcpe.me -// Submitted by Noa Heyl <hi@noa.dev> -mcpe.me - // McHost : https://mchost.ru // Submitted by Evgeniy Subbotin <e.subbotin@mchost.ru> mcdir.me mcdir.ru -mcpre.ru vps.mcdir.ru +mcpre.ru + +// mcpe.me : https://mcpe.me +// Submitted by Noa Heyl <hi@noa.dev> +mcpe.me // Mediatech : https://mediatech.by // Submitted by Evgeniy Kozhuhovskiy <ugenk@mediatech.by> @@ -14553,12 +14714,10 @@ co.pl // Managed by Corporate Domains // Microsoft Azure : https://home.azure *.azurecontainer.io -*.cloudapp.azure.com azure-api.net +azure-mobile.net azureedge.net azurefd.net -azurewebsites.net -azure-mobile.net azurestaticapps.net 1.azurestaticapps.net 2.azurestaticapps.net @@ -14572,26 +14731,27 @@ eastasia.azurestaticapps.net eastus2.azurestaticapps.net westeurope.azurestaticapps.net westus2.azurestaticapps.net +azurewebsites.net cloudapp.net trafficmanager.net blob.core.windows.net servicebus.windows.net +// MikroTik: https://mikrotik.com +// Submitted by MikroTik SysAdmin Team <support@mikrotik.com> +sn.mynetname.net + // minion.systems : http://minion.systems // Submitted by Robert Böttinger <r@minion.systems> csx.cc -// Mintere : https://mintere.com/ -// Submitted by Ben Aubin <security@mintere.com> -mintere.site - // MobileEducation, LLC : https://joinforte.com // Submitted by Grayson Martin <grayson.martin@mobileeducation.us> forte.id -// Mozilla Corporation : https://mozilla.com -// Submitted by Ben Francis <bfrancis@mozilla.com> -mozilla-iot.org +// MODX Systems LLC : https://modx.com +// Submitted by Elizabeth Southwell <elizabeth@modx.com> +modx.dev // Mozilla Foundation : https://mozilla.org/ // Submitted by glob <glob@mozilla.com> @@ -14606,8 +14766,8 @@ pp.ru // Mythic Beasts : https://www.mythic-beasts.com // Submitted by Paul Cammish <kelduum@mythic-beasts.com> hostedpi.com -customer.mythic-beasts.com caracal.mythic-beasts.com +customer.mythic-beasts.com fentiger.mythic-beasts.com lynx.mythic-beasts.com ocelot.mythic-beasts.com @@ -14627,6 +14787,10 @@ ui.nabu.casa // Submitted by Jan Jaeschke <jan.jaeschke@netatwork.de> cloud.nospamproxy.com +// Netfy Domains : https://netfy.domains +// Submitted by Suranga Ranasinghe <security@mavicsoft.com> +netfy.app + // Netlify : https://www.netlify.com // Submitted by Jessica Parsons <jessica@netlify.com> netlify.app @@ -14635,6 +14799,18 @@ netlify.app // Submitted by Trung Tran <Trung.Tran@neustar.biz> 4u.com +// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ +// Submitted by Jeff Wheelhouse <support@nearlyfreespeech.net> +nfshost.com + +// NFT.Storage : https://nft.storage/ +// Submitted by Vasco Santos <vasco.santos@protocol.ai> or <support@nft.storage> +ipfs.nftstorage.link + +// NGO.US Registry : https://nic.ngo.us +// Submitted by Alstra Solutions Ltd. Networking Team <admin@alstra.org> +ngo.us + // ngrok : https://ngrok.com/ // Submitted by Alan Shreve <alan@ngrok.com> ngrok.app @@ -14650,17 +14826,116 @@ jp.ngrok.io sa.ngrok.io us.ngrok.io ngrok.pizza +ngrok.pro // Nicolaus Copernicus University in Torun - MSK TORMAN (https://www.man.torun.pl) torun.pl // Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ -// Submitted by Nicholas Ford <nick@nimbushosting.co.uk> +// Submitted by Nicholas Ford <dev@nimbushosting.co.uk> nh-serv.co.uk +nimsite.uk -// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ -// Submitted by Jeff Wheelhouse <support@nearlyfreespeech.net> -nfshost.com +// No longer operated by CentralNic, these entries should be adopted and/or removed by current operators +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +ar.com +hu.com +kr.com +no.com +qc.com +uy.com + +// No-IP.com : https://noip.com/ +// Submitted by Deven Reza <publicsuffixlist@noip.com> +mmafan.biz +myftp.biz +no-ip.biz +no-ip.ca +fantasyleague.cc +gotdns.ch +3utilities.com +blogsyte.com +ciscofreak.com +damnserver.com +ddnsking.com +ditchyourip.com +dnsiskinky.com +dynns.com +geekgalaxy.com +health-carereform.com +homesecuritymac.com +homesecuritypc.com +myactivedirectory.com +mysecuritycamera.com +myvnc.com +net-freaks.com +onthewifi.com +point2this.com +quicksytes.com +securitytactics.com +servebeer.com +servecounterstrike.com +serveexchange.com +serveftp.com +servegame.com +servehalflife.com +servehttp.com +servehumour.com +serveirc.com +servemp3.com +servep2p.com +servepics.com +servequake.com +servesarcasm.com +stufftoread.com +unusualperson.com +workisboring.com +dvrcam.info +ilovecollege.info +no-ip.info +brasilia.me +ddns.me +dnsfor.me +hopto.me +loginto.me +noip.me +webhop.me +bounceme.net +ddns.net +eating-organic.net +mydissent.net +myeffect.net +mymediapc.net +mypsx.net +mysecuritycamera.net +nhlfan.net +no-ip.net +pgafan.net +privatizehealthinsurance.net +redirectme.net +serveblog.net +serveminecraft.net +sytes.net +cable-modem.org +collegefan.org +couchpotatofries.org +hopto.org +mlbfan.org +myftp.org +mysecuritycamera.org +nflfan.org +no-ip.org +read-books.org +ufcfan.org +zapto.org +no-ip.co.uk +golffan.us +noip.us +pointto.us + +// NodeArt : https://nodeart.io +// Submitted by Konstantin Nosov <Nosov@nodeart.io> +stage.nodeart.io // Noop : https://noop.app // Submitted by Nathaniel Schweinberg <noop@rearc.io> @@ -14679,6 +14954,10 @@ noop.app // Submitted by Laurent Pellegrino <security@noticeable.io> noticeable.news +// Notion Labs, Inc : https://www.notion.so/ +// Submitted by Jess Yao <trust-core-team@makenotion.com> +notion.site + // Now-DNS : https://now-dns.com // Submitted by Steve Russell <steve@now-dns.com> dnsking.ch @@ -14712,114 +14991,23 @@ zapto.xyz nsupdate.info nerdpol.ovh -// No-IP.com : https://noip.com/ -// Submitted by Deven Reza <publicsuffixlist@noip.com> -blogsyte.com -brasilia.me -cable-modem.org -ciscofreak.com -collegefan.org -couchpotatofries.org -damnserver.com -ddns.me -ditchyourip.com -dnsfor.me -dnsiskinky.com -dvrcam.info -dynns.com -eating-organic.net -fantasyleague.cc -geekgalaxy.com -golffan.us -health-carereform.com -homesecuritymac.com -homesecuritypc.com -hopto.me -ilovecollege.info -loginto.me -mlbfan.org -mmafan.biz -myactivedirectory.com -mydissent.net -myeffect.net -mymediapc.net -mypsx.net -mysecuritycamera.com -mysecuritycamera.net -mysecuritycamera.org -net-freaks.com -nflfan.org -nhlfan.net -no-ip.ca -no-ip.co.uk -no-ip.net -noip.us -onthewifi.com -pgafan.net -point2this.com -pointto.us -privatizehealthinsurance.net -quicksytes.com -read-books.org -securitytactics.com -serveexchange.com -servehumour.com -servep2p.com -servesarcasm.com -stufftoread.com -ufcfan.org -unusualperson.com -workisboring.com -3utilities.com -bounceme.net -ddns.net -ddnsking.com -gotdns.ch -hopto.org -myftp.biz -myftp.org -myvnc.com -no-ip.biz -no-ip.info -no-ip.org -noip.me -redirectme.net -servebeer.com -serveblog.net -servecounterstrike.com -serveftp.com -servegame.com -servehalflife.com -servehttp.com -serveirc.com -serveminecraft.net -servemp3.com -servepics.com -servequake.com -sytes.net -webhop.me -zapto.org - -// NodeArt : https://nodeart.io -// Submitted by Konstantin Nosov <Nosov@nodeart.io> -stage.nodeart.io - -// Nucleos Inc. : https://nucleos.com -// Submitted by Piotr Zduniak <piotr@nucleos.com> -pcloud.host - // NYC.mn : http://www.information.nyc.mn // Submitted by Matthew Brown <mattbrown@nyc.mn> nyc.mn +// O3O.Foundation : https://o3o.foundation/ +// Submitted by the prvcy.page Registry Team <psl@registry.prvcy.page> +prvcy.page + +// Obl.ong : <https://obl.ong> +// Submitted by Reese Armstrong <team@obl.ong> +obl.ong + // Observable, Inc. : https://observablehq.com // Submitted by Mike Bostock <dns@observablehq.com> +observablehq.cloud static.observableusercontent.com -// Octopodal Solutions, LLC. : https://ulterius.io/ -// Submitted by Andrew Sampson <andrew@ulterius.io> -cya.gg - // OMG.LOL : <https://omg.lol> // Submitted by Adam Newbold <adam@omg.lol> omg.lol @@ -14834,30 +15022,33 @@ omniwe.site // One.com: https://www.one.com/ // Submitted by Jacob Bunk Nielsen <jbn@one.com> -123hjemmeside.dk -123hjemmeside.no -123homepage.it -123kotisivu.fi -123minsida.se -123miweb.es -123paginaweb.pt -123sait.ru -123siteweb.fr 123webseite.at -123webseite.de 123website.be +simplesite.com.br 123website.ch +simplesite.com +123webseite.de +123hjemmeside.dk +123miweb.es +123kotisivu.fi +123siteweb.fr +simplesite.gr +123homepage.it 123website.lu 123website.nl +123hjemmeside.no service.one -simplesite.com -simplesite.com.br -simplesite.gr simplesite.pl +123paginaweb.pt +123minsida.se -// One Fold Media : http://www.onefoldmedia.com/ -// Submitted by Eddie Jones <eddie@onefoldmedia.com> -nid.io +// Open Domains : https://open-domains.net +// Submitted by William Harrison <admin@open-domains.net> +is-a-fullstack.dev +is-cool.dev +is-not-a.dev +localplayer.dev +is-local.org // Open Social : https://www.getopensocial.com/ // Submitted by Alexander Varwijk <security@getopensocial.com> @@ -14879,6 +15070,11 @@ operaunite.com // Submitted by Alexandre Linte <alexandre.linte@orange.com> tech.orange +// OsSav Technology Ltd. : https://ossav.com/ +// TLD Nic: http://nic.can.re - TLD Whois Server: whois.can.re +// Submitted by OsSav Technology Ltd. <support@ossav.com> +can.re + // Oursky Limited : https://authgear.com/, https://skygear.io/ // Submitted by Authgear Team <hello@authgear.com>, Skygear Developer <hello@skygear.io> authgear-staging.com @@ -14891,8 +15087,8 @@ outsystemscloud.com // OVHcloud: https://ovhcloud.com // Submitted by Vincent Cassé <vincent.casse@ovhcloud.com> -*.webpaas.ovh.net *.hosting.ovh.net +*.webpaas.ovh.net // OwnProvider GmbH: http://www.ownprovider.com // Submitted by Jan Moennich <jan.moennich@ownprovider.com> @@ -14915,37 +15111,31 @@ oy.lc // Submitted by Derek Myers <derek@pagefog.com> pgfog.com -// Pagefront : https://www.pagefronthq.com/ -// Submitted by Jason Kriss <jason@pagefronthq.com> -pagefrontapp.com - // PageXL : https://pagexl.com // Submitted by Yann Guichard <yann@pagexl.com> pagexl.com +// Pantheon Systems, Inc. : https://pantheon.io/ +// Submitted by Gary Dylina <gary@pantheon.io> +gotpantheon.com +pantheonsite.io + // Paywhirl, Inc : https://paywhirl.com/ // Submitted by Daniel Netzer <dan@paywhirl.com> *.paywhirl.com // pcarrier.ca Software Inc: https://pcarrier.ca/ // Submitted by Pierre Carrier <pc@rrier.ca> -bar0.net -bar1.net -bar2.net -rdv.to - -// .pl domains (grandfathered) -art.pl -gliwice.pl -krakow.pl -poznan.pl -wroc.pl -zakopane.pl +*.xmit.co +xmit.dev +madethis.site +srv.us +gh.srv.us +gl.srv.us -// Pantheon Systems, Inc. : https://pantheon.io/ -// Submitted by Gary Dylina <gary@pantheon.io> -pantheonsite.io -gotpantheon.com +// PE Ulyanov Kirill Sergeevich : https://airy.host +// Submitted by Kirill Ulyanov <k.ulyanov@airy.host> +lk3.ru // Peplink | Pepwave : http://peplink.com/ // Submitted by Steve Leung <steveleung@peplink.com> @@ -14955,10 +15145,6 @@ mypep.link // Submitted by Kenneth Van Alstyne <kvanalstyne@perspecta.com> perspecta.cloud -// PE Ulyanov Kirill Sergeevich : https://airy.host -// Submitted by Kirill Ulyanov <k.ulyanov@airy.host> -lk3.ru - // Planet-Work : https://www.planet-work.com/ // Submitted by Frédéric VANNIÈRE <f.vanniere@planet-work.com> on-web.fr @@ -14979,12 +15165,6 @@ platter-app.com platter-app.dev platterp.us -// Plesk : https://www.plesk.com/ -// Submitted by Anton Akhtyamov <program-managers@plesk.com> -pdns.page -plesk.page -pleskns.com - // Pley AB : https://www.pley.com/ // Submitted by Henning Pohl <infra@pley.com> pley.games @@ -15008,8 +15188,8 @@ pstmn.io mock.pstmn.io httpbin.org -//prequalifyme.today : https://prequalifyme.today -//Submitted by DeepakTiwari deepak@ivylead.io +// prequalifyme.today : https://prequalifyme.today +// Submitted by DeepakTiwari deepak@ivylead.io prequalifyme.today // prgmr.com : https://prgmr.com/ @@ -15020,10 +15200,6 @@ xen.prgmr.com // Submitted by registry <lendl@nic.at> priv.at -// privacytools.io : https://www.privacytools.io/ -// Submitted by Jonah Aragon <jonah@privacytools.io> -prvcy.page - // Protocol Labs : https://protocol.ai/ // Submitted by Michael Burns <noc@protocol.ai> *.dweb.link @@ -15046,6 +15222,24 @@ pubtls.org pythonanywhere.com eu.pythonanywhere.com +// QA2 +// Submitted by Daniel Dent (https://www.danieldent.com/) +qa2.com + +// QCX +// Submitted by Cassandra Beelen <cassandra@beelen.one> +qcx.io +*.sys.qcx.io + +// QNAP System Inc : https://www.qnap.com +// Submitted by Nick Chang <cloudadmin@qnap.com> +myqnapcloud.cn +alpha-myqnapcloud.com +dev-myqnapcloud.com +mycloudnas.com +mynascloud.com +myqnapcloud.com + // QOTO, Org. // Submitted by Jeffrey Phillips Freeman <jeffrey.freeman@qoto.org> qoto.io @@ -15062,37 +15256,6 @@ ladesk.com // Submitted by Dani Biro <dani@pymet.com> qbuser.com -// Rad Web Hosting: https://radwebhosting.com -// Submitted by Scott Claeys <s.claeys@radwebhosting.com> -cloudsite.builders - -// Redgate Software: https://red-gate.com -// Submitted by Andrew Farries <andrew.farries@red-gate.com> -instances.spawn.cc - -// Redstar Consultants : https://www.redstarconsultants.com/ -// Submitted by Jons Slemmer <jons@redstarconsultants.com> -instantcloud.cn - -// Russian Academy of Sciences -// Submitted by Tech Support <support@rasnet.ru> -ras.ru - -// QA2 -// Submitted by Daniel Dent (https://www.danieldent.com/) -qa2.com - -// QCX -// Submitted by Cassandra Beelen <cassandra@beelen.one> -qcx.io -*.sys.qcx.io - -// QNAP System Inc : https://www.qnap.com -// Submitted by Nick Chang <nickchang@qnap.com> -dev-myqnapcloud.com -alpha-myqnapcloud.com -myqnapcloud.com - // Quip : https://quip.com // Submitted by Patrick Linehan <plinehan@quip.com> *.quipelements.com @@ -15107,16 +15270,32 @@ vaporcloud.io rackmaze.com rackmaze.net -// Rakuten Games, Inc : https://dev.viberplay.io -// Submitted by Joshua Zhang <public-suffix@rgames.jp> -g.vbrplsbx.io +// Rad Web Hosting: https://radwebhosting.com +// Submitted by Scott Claeys <s.claeys@radwebhosting.com> +cloudsite.builders +myradweb.net +servername.us + +// Radix FZC : http://domains.in.net +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +web.in +in.net + +// Raidboxes GmbH : https://raidboxes.de +// Submitted by Auke Tembrink <hostmaster@raidboxes.de> +myrdbx.io +site.rb-hosting.io // Rancher Labs, Inc : https://rancher.com // Submitted by Vincent Fiduccia <domains@rancher.com> -*.on-k3s.io *.on-rancher.cloud +*.on-k3s.io *.on-rio.io +// RavPage : https://www.ravpage.co.il +// Submitted by Roni Horowitz <roni@responder.co.il> +ravpage.co.il + // Read The Docs, Inc : https://www.readthedocs.org // Submitted by David Fischer <team@readthedocs.org> readthedocs.io @@ -15125,17 +15304,50 @@ readthedocs.io // Submitted by Tim Kramer <tkramer@rhcloud.com> rhcloud.com +// Redgate Software: https://red-gate.com +// Submitted by Andrew Farries <andrew.farries@red-gate.com> +instances.spawn.cc + // Render : https://render.com // Submitted by Anurag Goel <dev@render.com> -app.render.com onrender.com +app.render.com // Repl.it : https://repl.it -// Submitted by Lincoln Bergeson <lincoln@replit.com> +// Submitted by Lincoln Bergeson <psl@repl.it> +replit.app +id.replit.app firewalledreplit.co id.firewalledreplit.co repl.co id.repl.co +replit.dev +archer.replit.dev +bones.replit.dev +canary.replit.dev +global.replit.dev +hacker.replit.dev +id.replit.dev +janeway.replit.dev +kim.replit.dev +kira.replit.dev +kirk.replit.dev +odo.replit.dev +paris.replit.dev +picard.replit.dev +pike.replit.dev +prerelease.replit.dev +reed.replit.dev +riker.replit.dev +sisko.replit.dev +spock.replit.dev +staging.replit.dev +sulu.replit.dev +tarpit.replit.dev +teams.replit.dev +tucker.replit.dev +wesley.replit.dev +worf.replit.dev repl.run // Resin.io : https://resin.io @@ -15160,6 +15372,11 @@ adimo.co.uk // Submitted by Micah Anderson <micah@riseup.net> itcouldbewor.se +// Roar Domains LLC : https://roar.basketball/ +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +aus.basketball +nz.basketball + // Rochester Institute of Technology : http://www.rit.edu/ // Submitted by Jennifer Herting <jchits@rit.edu> git-pages.rit.edu @@ -15191,6 +15408,10 @@ xn--90a1af.xn--p1acf xn--41a.xn--p1acf я.рус +// Russian Academy of Sciences +// Submitted by Tech Support <support@rasnet.ru> +ras.ru + // SAKURA Internet Inc. : https://www.sakura.ad.jp/ // Submitted by Internet Service Department <rs-vendor-ml@sakura.ad.jp> 180r.com @@ -15242,11 +15463,19 @@ from.tv sakura.tv // Salesforce.com, Inc. https://salesforce.com/ -// Submitted by Michael Biven <mbiven@salesforce.com> and Aaron Romeo <aaron.romeo@salesforce.com> +// Submitted by Salesforce Public Suffix List Team <public-suffix-list@salesforce.com> *.builder.code.com *.dev-builder.code.com *.stg-builder.code.com *.001.test.code-builder-stg.platform.salesforce.com +*.d.crm.dev +*.w.crm.dev +*.wa.crm.dev +*.wb.crm.dev +*.wc.crm.dev +*.wd.crm.dev +*.we.crm.dev +*.wf.crm.dev // Sandstorm Development Group, Inc. : https://sandcats.io/ // Submitted by Asheesh Laroia <asheesh@sandstorm.io> @@ -15254,8 +15483,8 @@ sandcats.io // SBE network solutions GmbH : https://www.sbe.de/ // Submitted by Norman Meilick <nm@sbe.de> -logoip.de logoip.com +logoip.de // Scaleway : https://www.scaleway.com/ // Submitted by Rémy Léone <rleone@scaleway.com> @@ -15301,6 +15530,10 @@ service.gov.scot // Submitted by Shante Adam <shante@skyhat.io> scrysec.com +// Scrypted : https://scrypted.app +// Submitted by Koushik Dutta <public-suffix-list@scrypted.app> +client.scrypted.io + // Securepoint GmbH : https://www.securepoint.de // Submitted by Erik Anders <erik.anders@securepoint.de> firewall-gateway.com @@ -15322,13 +15555,17 @@ seidat.net // Submitted by Yuriy Romadin <contact@sellfy.com> sellfy.store +// Sendmsg: https://www.sendmsg.co.il +// Submitted by Assaf Stern <domains@comstar.co.il> +minisite.ms + // Senseering GmbH : https://www.senseering.de // Submitted by Felix Mönckemeyer <f.moenckemeyer@senseering.de> senseering.net -// Sendmsg: https://www.sendmsg.co.il -// Submitted by Assaf Stern <domains@comstar.co.il> -minisite.ms +// Servebolt AS: https://servebolt.com +// Submitted by Daniel Kjeserud <cloudops@servebolt.com> +servebolt.cloud // Service Magnet : https://myservicemagnet.com // Submitted by Dave Sanders <dave@myservicemagnet.com> @@ -15340,10 +15577,13 @@ biz.ua co.ua pp.ua -// Shift Crypto AG : https://shiftcrypto.ch -// Submitted by alex <alex@shiftcrypto.ch> -shiftcrypto.dev -shiftcrypto.io +// Shanghai Accounting Society : https://www.sasf.org.cn +// Submitted by Information Administration <info@sasf.org.cn> +as.sh.cn + +// Sheezy.Art : https://sheezy.art +// Submitted by Nyoom <admin@sheezy.art> +sheezy.games // ShiftEdit : https://shiftedit.net/ // Submitted by Adam Jimenez <adam@shiftcreate.com> @@ -15387,6 +15627,10 @@ bounty-full.com alpha.bounty-full.com beta.bounty-full.com +// Small Technology Foundation : https://small-tech.org +// Submitted by Aral Balkan <aral@small-tech.org> +small-web.org + // Smallregistry by Promopixel SARL: https://www.smallregistry.net // Former AFNIC's SLDs // Submitted by Jérôme Lipowicz <support@promopixel.com> @@ -15401,18 +15645,14 @@ pharmacien.fr port.fr veterinaire.fr -// Small Technology Foundation : https://small-tech.org -// Submitted by Aral Balkan <aral@small-tech.org> -small-web.org - // Smoove.io : https://www.smoove.io/ // Submitted by Dan Kozak <dan@smoove.io> vp4.me // Snowflake Inc : https://www.snowflake.com/ -// Submitted by Faith Olapade <faith.olapade@snowflake.com> -snowflake.app -privatelink.snowflake.app +// Submitted by Sam Haar <psl@snowflake.com> +*.snowflake.app +*.privatelink.snowflake.app streamlit.app streamlitapp.com @@ -15420,41 +15660,18 @@ streamlitapp.com // Submitted by Ian Streeter <ian@snowplowanalytics.com> try-snowplow.com -// SourceHut : https://sourcehut.org -// Submitted by Drew DeVault <sir@cmpwn.com> -srht.site - -// Stackhero : https://www.stackhero.io -// Submitted by Adrien Gillon <adrien+public-suffix-list@stackhero.io> -stackhero-network.com - -// Staclar : https://staclar.com -// Submitted by Q Misell <q@staclar.com> -musician.io -// Submitted by Matthias Merkel <matthias.merkel@staclar.com> -novecore.site - -// staticland : https://static.land -// Submitted by Seth Vincent <sethvincent@gmail.com> -static.land -dev.static.land -sites.static.land - -// Storebase : https://www.storebase.io -// Submitted by Tony Schirmer <tony@storebase.io> -storebase.store - -// Strategic System Consulting (eApps Hosting): https://www.eapps.com/ -// Submitted by Alex Oancea <aoancea@cloudscale365.com> -vps-host.net -atl.jelastic.vps-host.net -njs.jelastic.vps-host.net -ric.jelastic.vps-host.net +// Software Consulting Michal Zalewski : https://www.mafelo.com +// Submitted by Michal Zalewski <security@mafelo.com> +mafelo.net // Sony Interactive Entertainment LLC : https://sie.com/ // Submitted by David Coles <david.coles@sony.com> playstation-cloud.com +// SourceHut : https://sourcehut.org +// Submitted by Drew DeVault <sir@cmpwn.com> +srht.site + // SourceLair PC : https://www.sourcelair.com // Submitted by Antonis Kalipetis <akalipetis@sourcelair.com> apps.lair.io @@ -15464,6 +15681,10 @@ apps.lair.io // Submitted by Reza Akhavan <spacekit.io@gmail.com> spacekit.io +// SparrowHost : https://sparrowhost.in/ +// Submitted by Anant Pandey <info@sparrowhost.in> +ind.mom + // SpeedPartner GmbH: https://www.speedpartner.de/ // Submitted by Stefan Neufeind <info@speedpartner.de> customer.speedpartner.de @@ -15490,10 +15711,51 @@ myspreadshop.pl myspreadshop.se myspreadshop.co.uk +// StackBlitz : https://stackblitz.com +// Submitted by Dominic Elm <hello@stackblitz.com> +w-corp-staticblitz.com +w-credentialless-staticblitz.com +w-staticblitz.com + +// Stackhero : https://www.stackhero.io +// Submitted by Adrien Gillon <adrien+public-suffix-list@stackhero.io> +stackhero-network.com + +// STACKIT : https://www.stackit.de/en/ +// Submitted by STACKIT-DNS Team (Simon Stier) <stackit-dns@mail.schwarz> +runs.onstackit.cloud +stackit.gg +stackit.rocks +stackit.run +stackit.zone + +// Staclar : https://staclar.com +// Submitted by Q Misell <q@staclar.com> +// Submitted by Matthias Merkel <matthias.merkel@staclar.com> +musician.io +novecore.site + // Standard Library : https://stdlib.com // Submitted by Jacob Lee <jacob@stdlib.com> api.stdlib.com +// stereosense GmbH : https://www.involve.me +// Submitted by Florian Burmann <publicsuffix@involve.me> +feedback.ac +forms.ac +assessments.cx +calculators.cx +funnels.cx +paynow.cx +quizzes.cx +researched.cx +tests.cx +surveys.so + +// Storebase : https://www.storebase.io +// Submitted by Tony Schirmer <tony@storebase.io> +storebase.store + // Storipress : https://storipress.com // Submitted by Benno Liu <benno@storipress.com> storipress.app @@ -15502,15 +15764,33 @@ storipress.app // Submitted by Philip Hutchins <hostmaster@storj.io> storj.farm -// Studenten Net Twente : http://www.snt.utwente.nl/ -// Submitted by Silke Hofstra <syscom@snt.utwente.nl> -utwente.io +// Strapi : https://strapi.io/ +// Submitted by Florent Baldino <security@strapi.io> +strapiapp.com +media.strapiapp.com + +// Strategic System Consulting (eApps Hosting): https://www.eapps.com/ +// Submitted by Alex Oancea <aoancea@cloudscale365.com> +vps-host.net +atl.jelastic.vps-host.net +njs.jelastic.vps-host.net +ric.jelastic.vps-host.net + +// Streak : https://streak.com +// Submitted by Blake Kadatz <eng@streak.com> +streak-link.com +streaklinks.com +streakusercontent.com // Student-Run Computing Facility : https://www.srcf.net/ // Submitted by Edwin Balani <sysadmins@srcf.net> soc.srcf.net user.srcf.net +// Studenten Net Twente : http://www.snt.utwente.nl/ +// Submitted by Silke Hofstra <syscom@snt.utwente.nl> +utwente.io + // Sub 6 Limited: http://www.sub6.com // Submitted by Dan Miller <dm@sub6.com> temp-dns.com @@ -15520,12 +15800,11 @@ temp-dns.com supabase.co supabase.in supabase.net -su.paba.se // Symfony, SAS : https://symfony.com/ // Submitted by Fabien Potencier <fabien@symfony.com> -*.s5y.io *.sensiosite.cloud +*.s5y.io // Syncloud : https://syncloud.org // Submitted by Boris Rybalkin <syncloud@syncloud.it> @@ -15547,14 +15826,14 @@ dsmynas.net familyds.net dsmynas.org familyds.org -vpnplus.to direct.quickconnect.to +vpnplus.to // Tabit Technologies Ltd. : https://tabit.cloud/ // Submitted by Oren Agiv <oren@tabit.cloud> -tabitorder.co.il -mytabit.co.il mytabit.com +mytabit.co.il +tabitorder.co.il // TAIFUN Software AG : http://taifun-software.de // Submitted by Bjoern Henke <dev-server@taifun-software.de> @@ -15564,14 +15843,20 @@ taifun-dns.de // Submitted by David Anderson <danderson@tailscale.com> beta.tailscale.net ts.net +*.c.ts.net -// TASK geographical domains (www.task.gda.pl/uslugi/dns) +// TASK geographical domains (https://www.task.gda.pl/uslugi/dns) gda.pl gdansk.pl gdynia.pl med.pl sopot.pl +// tawk.to, Inc : https://www.tawk.to +// Submitted by tawk.to developer team <dev-accounts@tawk.to> +p.tawk.email +p.tawkto.email + // team.blue https://team.blue // Submitted by Cedric Dubois <cedric.dubois@team.blue> site.tb-hosting.com @@ -15594,11 +15879,11 @@ telebit.io reservd.com thingdustdata.com cust.dev.thingdust.io +reservd.dev.thingdust.io cust.disrec.thingdust.io +reservd.disrec.thingdust.io cust.prod.thingdust.io cust.testing.thingdust.io -reservd.dev.thingdust.io -reservd.disrec.thingdust.io reservd.testing.thingdust.io // ticket i/O GmbH : https://ticket.io @@ -15612,7 +15897,7 @@ azimuth.network tlon.network // Tor Project, Inc. : https://torproject.org -// Submitted by Antoine Beaupré <anarcat@torproject.org +// Submitted by Antoine Beaupré <anarcat@torproject.org> torproject.net pages.torproject.net @@ -15660,8 +15945,6 @@ tuxfamily.org // TwoDNS : https://www.twodns.de/ // Submitted by TwoDNS-Support <support@two-dns.de> dd-dns.de -diskstation.eu -diskstation.org dray-dns.de draydns.de dyn-vpn.de @@ -15672,6 +15955,8 @@ my-wan.de syno-ds.de synology-diskstation.de synology-ds.de +diskstation.eu +diskstation.org // Typedream : https://typedream.com // Submitted by Putri Karunia <putri@typedream.com> @@ -15683,20 +15968,29 @@ pro.typeform.com // Uberspace : https://uberspace.de // Submitted by Moritz Werner <mwerner@jonaspasche.com> -uber.space *.uberspace.de +uber.space // UDR Limited : http://www.udr.hk.com // Submitted by registry <hostmaster@udr.hk.com> hk.com -hk.org -ltd.hk inc.hk +ltd.hk +hk.org // UK Intis Telecom LTD : https://it.com // Submitted by ITComdomains <to@it.com> it.com +// Unison Computing, PBC : https://unison.cloud +// Submitted by Simon Højberg <security@unison.cloud> +unison-services.cloud + +// United Gameserver GmbH : https://united-gameserver.de +// Submitted by Stefan Schwarz <sysadm@united-gameserver.de> +virtual-user.de +virtualuser.de + // UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ // see also: whois -h whois.udr.org.yt help // Submitted by Atanunu Igbunuroghene <publicsuffixlist@udr.org.yt> @@ -15706,10 +16000,14 @@ biz.wf sch.wf org.yt -// United Gameserver GmbH : https://united-gameserver.de -// Submitted by Stefan Schwarz <sysadm@united-gameserver.de> -virtualuser.de -virtual-user.de +// University of Banja Luka : https://unibl.org +// Domains for Republic of Srpska administrative entity. +// Submitted by Marko Ivanovic <kormang@hotmail.rs> +rs.ba + +// University of Bielsko-Biala regional domain: http://dns.bielsko.pl/ +// Submitted by Marcin <dns@ath.bielsko.pl> +bielsko.pl // Upli : https://upli.io // Submitted by Lenny Bakkalian <lenny.bakkalian@gmail.com> @@ -15720,13 +16018,18 @@ upli.io urown.cloud dnsupdate.info -// .US -// Submitted by Ed Moore <Ed.Moore@lib.de.us> -lib.de.us +// US REGISTRY LLC : http://us.org +// Submitted by Gavin Brown <gavin.brown@centralnic.com> +us.org -// VeryPositive SIA : http://very.lv -// Submitted by Danko Aleksejevs <danko@very.lv> -2038.io +// V.UA Domain Administrator : https://domain.v.ua/ +// Submitted by Serhii Rostilo <sergey@rostilo.kiev.ua> +v.ua + +// Val Town, Inc : https://val.town/ +// Submitted by Tom MacWright <security@val.town> +express.val.run +web.val.run // Vercel, Inc : https://vercel.com/ // Submitted by Connor Davis <security@vercel.com> @@ -15734,6 +16037,10 @@ vercel.app vercel.dev now.sh +// VeryPositive SIA : http://very.lv +// Submitted by Danko Aleksejevs <danko@very.lv> +2038.io + // Viprinet Europe GmbH : http://www.viprinet.com // Submitted by Simon Kissel <hostmaster@viprinet.com> router.management @@ -15746,51 +16053,6 @@ v-info.info // Submitted by Nathan van Bakel <info@voorloper.com> voorloper.cloud -// Voxel.sh DNS : https://voxel.sh/dns/ -// Submitted by Mia Rehlinger <dns@voxel.sh> -neko.am -nyaa.am -be.ax -cat.ax -es.ax -eu.ax -gg.ax -mc.ax -us.ax -xy.ax -nl.ci -xx.gl -app.gp -blog.gt -de.gt -to.gt -be.gy -cc.hn -io.kg -jp.kg -tv.kg -uk.kg -us.kg -de.ls -at.md -de.md -jp.md -to.md -indie.porn -vxl.sh -ch.tc -me.tc -we.tc -nyan.to -at.vg -blog.vu -dev.vu -me.vu - -// V.UA Domain Administrator : https://domain.v.ua/ -// Submitted by Serhii Rostilo <sergey@rostilo.kiev.ua> -v.ua - // Vultr Objects : https://www.vultr.com/products/object-storage/ // Submitted by Niels Maumenee <storage@vultr.com> *.vultrobjects.com @@ -15799,42 +16061,70 @@ v.ua // Submitted by Masayuki Note <masa@blade.wafflecell.com> wafflecell.com +// Webflow, Inc. : https://www.webflow.com +// Submitted by Webflow Security Team <security@webflow.com> +webflow.io +webflowtest.io + // WebHare bv: https://www.webhare.com/ // Submitted by Arnold Hendriks <info@webhare.com> *.webhare.dev // WebHotelier Technologies Ltd: https://www.webhotelier.net/ // Submitted by Apostolos Tsakpinis <apostolos.tsakpinis@gmail.com> -reserve-online.net -reserve-online.com bookonline.app hotelwithflight.com +reserve-online.com +reserve-online.net -// WeDeploy by Liferay, Inc. : https://www.wedeploy.com -// Submitted by Henrique Vicente <security@wedeploy.com> -wedeploy.io -wedeploy.me -wedeploy.sh +// WebPros International, LLC : https://webpros.com/ +// Submitted by Nicolas Rochelemagne <public.suffix@webpros.com> +cprapid.com +pleskns.com +wp2.host +pdns.page +plesk.page +wpsquared.site + +// WebWaddle Ltd: https://webwaddle.com/ +// Submitted by Merlin Glander <hostmaster@webwaddle.com> +*.wadl.top // Western Digital Technologies, Inc : https://www.wdc.com // Submitted by Jung Jin <jungseok.jin@wdc.com> remotewd.com +// Whatbox Inc. : https://whatbox.ca/ +// Submitted by Anthony Ryan <servers@whatbox.ca> +box.ca + // WIARD Enterprises : https://wiardweb.com // Submitted by Kidd Hustle <kiddhustle@wiardweb.com> pages.wiardweb.com // Wikimedia Labs : https://wikitech.wikimedia.org // Submitted by Arturo Borrero Gonzalez <aborrero@wikimedia.org> -wmflabs.org toolforge.org wmcloud.org +wmflabs.org + +// William Harrison : https://wdh.gg +// Submitted by William Harrison <domains@wdh.gg> +*.wdh.app // WISP : https://wisp.gg // Submitted by Stepan Fedotov <stepan@wisp.gg> panel.gg daemon.panel.gg +// Wix.com, Inc. : https://www.wix.com +// Submitted by Shahar Talmi / Alon Kochba <publicsuffixlist@wix.com> +wixsite.com +wixstudio.com +editorx.io +wixstudio.io +wix.run + // Wizard Zines : https://wizardzines.com // Submitted by Julia Evans <julia@wizardzines.com> messwithdns.com @@ -15860,13 +16150,6 @@ weeklylottery.org.uk wpenginepowered.com js.wpenginepowered.com -// Wix.com, Inc. : https://www.wix.com -// Submitted by Shahar Talmi <shahar@wix.com> -wixsite.com -editorx.io -wixstudio.io -wix.run - // XenonCloud GbR: https://xenoncloud.net // Submitted by Julian Uphoff <publicsuffixlist@xenoncloud.net> half.host @@ -15922,6 +16205,10 @@ za.org // Submitted by Julian Alker <security@zap-hosting.com> zap.cloud +// Zeabur : https://zeabur.com/ +// Submitted by Zeabur Team <contact@zeabur.com> +zeabur.app + // Zine EOOD : https://zine.bg/ // Submitted by Martin Angelov <martin@zine.bg> bss.design diff --git a/contrib/publicsuffix/idn.pl b/contrib/publicsuffix/idn.pl index dd46d652a..dd46d652a 100644..100755 --- a/contrib/publicsuffix/idn.pl +++ b/contrib/publicsuffix/idn.pl diff --git a/contrib/zstd/CMakeLists.txt b/contrib/zstd/CMakeLists.txt index 4601e53ce..9fba28fa4 100644 --- a/contrib/zstd/CMakeLists.txt +++ b/contrib/zstd/CMakeLists.txt @@ -24,4 +24,4 @@ SET(ZSTDSRC zstd_opt.c) ADD_LIBRARY(rspamd-zstd STATIC ${ZSTDSRC}) -ADD_DEFINITIONS(-DZSTD_DISABLE_ASM)
\ No newline at end of file +ADD_DEFINITIONS(-DZSTD_DISABLE_ASM -DZSTD_DISABLE_DEPRECATE_WARNINGS)
\ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..2f256afcb --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,84 @@ +import globals from "globals"; +import js from "@eslint/js"; +import stylistic from "@stylistic/eslint-plugin"; + +export default [ + js.configs.all, + stylistic.configs["all-flat"], + {ignores: ["interface/js/lib/"]}, + { + languageOptions: { + ecmaVersion: 2016, + globals: { + ...globals.browser, + define: false, + }, + sourceType: "script", + }, + plugins: { + "@stylistic": stylistic, + }, + rules: { + "@stylistic/array-bracket-newline": ["error", "consistent"], + "@stylistic/array-element-newline": "off", + "@stylistic/brace-style": ["error", "1tbs", {allowSingleLine: true}], + "@stylistic/comma-dangle": ["error", "only-multiline"], + "@stylistic/dot-location": ["error", "property"], + "@stylistic/function-call-argument-newline": "off", + "@stylistic/function-paren-newline": "off", + "@stylistic/indent-binary-ops": "off", + "@stylistic/max-len": ["error", {code: 128}], + "@stylistic/max-statements-per-line": ["error", {max: 2}], + "@stylistic/multiline-comment-style": "off", + "@stylistic/multiline-ternary": ["error", "always-multiline"], + "@stylistic/newline-per-chained-call": ["error", {ignoreChainWithDepth: 5}], + "@stylistic/no-extra-parens": ["error", "functions"], + "@stylistic/object-property-newline": ["error", {allowAllPropertiesOnSameLine: true}], + "@stylistic/padded-blocks": ["error", "never"], + "@stylistic/quote-props": ["error", "consistent-as-needed"], + "@stylistic/quotes": ["error", "double", {avoidEscape: true}], + "@stylistic/semi": ["error", "always"], + "@stylistic/space-before-function-paren": ["error", { + anonymous: "always", + named: "never", + }], + + "camelcase": "off", + "capitalized-comments": "off", + "curly": ["error", "multi-line"], + "func-names": "off", + "func-style": ["error", "declaration"], + "id-length": ["error", {min: 1}], + "line-comment-position": "off", + "logical-assignment-operators": ["error", "never"], + "max-lines": "off", + "max-lines-per-function": "off", + "max-params": ["warn", 6], + "max-statements": ["warn", 55], + "multiline-comment-style": "off", + "no-continue": "off", + "no-inline-comments": "off", + "no-invalid-this": "off", + "no-magic-numbers": "off", + "no-negated-condition": "off", + "no-plusplus": "off", + "no-ternary": "off", + "no-unused-vars": ["error", {caughtErrors: "none"}], + "object-shorthand": "off", + "one-var": ["error", {initialized: "never"}], + "prefer-named-capture-group": "off", + "prefer-object-has-own": "off", + "prefer-spread": "off", + "prefer-template": "off", + "require-unicode-regexp": "off", + "sort-keys": "off", + }, + }, + { + files: ["**/*.mjs"], + languageOptions: {ecmaVersion: 2020, sourceType: "module"}, + rules: { + "sort-keys": "error", + }, + }, +]; diff --git a/interface/js/app/libft.js b/interface/js/app/libft.js index 1e81cfd26..84ee2667e 100644 --- a/interface/js/app/libft.js +++ b/interface/js/app/libft.js @@ -249,7 +249,7 @@ define(["jquery", "app/common", "footable"], }); /* eslint-enable no-underscore-dangle */ - /* eslint-disable consistent-this, no-underscore-dangle, one-var-declaration-per-line */ + /* eslint-disable consistent-this, no-underscore-dangle */ FooTable.actionFilter = FooTable.Filtering.extend({ construct: function (instance) { this._super(instance); @@ -304,6 +304,7 @@ define(["jquery", "app/common", "footable"], const selected = self.$action.val(); if (selected !== self.def) { const not = self.$not.is(":checked"); + // eslint-disable-next-line no-useless-assignment let query = null; if (selected === "reject") { @@ -319,7 +320,7 @@ define(["jquery", "app/common", "footable"], self.filter(); } }); - /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */ + /* eslint-enable consistent-this, no-underscore-dangle */ const columns = (table in columnsCustom) ? columnsDefault.map((column) => $.extend({}, column, columnsCustom[table][column.name])) @@ -600,6 +601,7 @@ define(["jquery", "app/common", "footable"], item.id = item["message-id"]; if (table === "history") { + // eslint-disable-next-line no-useless-assignment let rcpt = {}; if (!item.rcpt_mime.length) { rcpt = format_rcpt(true, false); diff --git a/interface/js/app/stats.js b/interface/js/app/stats.js index 04b4a75c5..8ac4eacdd 100644 --- a/interface/js/app/stats.js +++ b/interface/js/app/stats.js @@ -35,6 +35,7 @@ define(["jquery", "app/common", "d3pie", "d3"], const hours = seconds % 31536000 % 2628000 % 86400 / 3600 >> 0; const minutes = seconds % 31536000 % 2628000 % 86400 % 3600 / 60 >> 0; /* eslint-enable no-bitwise */ + // eslint-disable-next-line no-useless-assignment let out = null; if (years > 0) { if (months > 0) { diff --git a/interface/js/app/symbols.js b/interface/js/app/symbols.js index a720a696d..3ff5d5a4b 100644 --- a/interface/js/app/symbols.js +++ b/interface/js/app/symbols.js @@ -130,7 +130,7 @@ define(["jquery", "app/common", "footable"], const [{data}] = json; const items = process_symbols_data(data); - /* eslint-disable consistent-this, no-underscore-dangle, one-var-declaration-per-line */ + /* eslint-disable consistent-this, no-underscore-dangle */ FooTable.groupFilter = FooTable.Filtering.extend({ construct: function (instance) { this._super(instance); @@ -183,7 +183,7 @@ define(["jquery", "app/common", "footable"], } } }); - /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */ + /* eslint-enable consistent-this, no-underscore-dangle */ common.tables.symbols = FooTable.init("#symbolsTable", { breakpoints: common.breakpoints, diff --git a/lualib/lua_bayes_learn.lua b/lualib/lua_bayes_learn.lua index ea97db6f8..82f044d7d 100644 --- a/lualib/lua_bayes_learn.lua +++ b/lualib/lua_bayes_learn.lua @@ -18,6 +18,7 @@ limitations under the License. local lua_util = require "lua_util" local lua_verdict = require "lua_verdict" +local logger = require "rspamd_logger" local N = "lua_bayes" local exports = {} @@ -56,16 +57,16 @@ exports.autolearn = function(task, conf) local mime_rcpts = 'undef' local mr = task:get_recipients('mime') if mr then + local r_addrs = {} for _, r in ipairs(mr) do - if mime_rcpts == 'undef' then - mime_rcpts = r.addr - else - mime_rcpts = mime_rcpts .. ',' .. r.addr - end + r_addrs[#r_addrs + 1] = r.addr + end + if #r_addrs > 0 then + mime_rcpts = table.concat(r_addrs, ',') end end - lua_util.debugm(N, task, 'id: %s, from: <%s>: can autolearn %s: score %s %s %s, mime_rcpts: <%s>', + logger.info(task, 'id: %s, from: <%s>: can autolearn %s: score %s %s %s, mime_rcpts: <%s>', task:get_header('Message-Id') or '<undef>', from and from[1].addr or 'undef', verdict, diff --git a/lualib/lua_cfg_transform.lua b/lualib/lua_cfg_transform.lua index 02d8526e5..265ca34c0 100644 --- a/lualib/lua_cfg_transform.lua +++ b/lualib/lua_cfg_transform.lua @@ -17,102 +17,165 @@ limitations under the License. local logger = require "rspamd_logger" local lua_util = require "lua_util" local rspamd_util = require "rspamd_util" -local fun = require "fun" -local function is_implicit(t) - local mt = getmetatable(t) +-- Converts surbl module config to rbl module +local function surbl_section_convert(cfg, section) + local rbl_section = cfg.rbl.rbls + local wl = section.whitelist + if section.rules then + for name, value in section.rules:pairs() do + if rbl_section[name] then + logger.warnx(rspamd_config, 'conflicting names in surbl and rbl rules: %s, prefer surbl rule!', + name) + end + local converted = { + urls = true, + ignore_defaults = true, + } - return mt and mt.class and mt.class == 'ucl.type.impl_array' -end + if wl then + converted.whitelist = wl + end -local function metric_pairs(t) - -- collect the keys - local keys = {} - local implicit_array = is_implicit(t) - - local function gen_keys(tbl) - if implicit_array then - for _, v in ipairs(tbl) do - if v.name then - table.insert(keys, { v.name, v }) - v.name = nil - else - -- Very tricky to distinguish: - -- group {name = "foo" ... } + group "blah" { ... } - for gr_name, gr in pairs(v) do - if type(gr_name) ~= 'number' then - -- We can also have implicit arrays here - local gr_implicit = is_implicit(gr) - - if gr_implicit then - for _, gr_elt in ipairs(gr) do - table.insert(keys, { gr_name, gr_elt }) - end - else - table.insert(keys, { gr_name, gr }) - end - end - end + for k, v in value:pairs() do + local skip = false + -- Rename + if k == 'suffix' then + k = 'rbl' end - end - else - if tbl.name then - table.insert(keys, { tbl.name, tbl }) - tbl.name = nil - else - for k, v in pairs(tbl) do - if type(k) ~= 'number' then - -- We can also have implicit arrays here - local sym_implicit = is_implicit(v) - - if sym_implicit then - for _, elt in ipairs(v) do - table.insert(keys, { k, elt }) - end - else - table.insert(keys, { k, v }) - end + if k == 'ips' then + k = 'returncodes' + end + if k == 'bits' then + k = 'returnbits' + end + if k == 'noip' then + k = 'no_ip' + end + -- Crappy legacy + if k == 'options' then + if v == 'noip' or v == 'no_ip' then + converted.no_ip = true + skip = true end end + if k:match('check_') then + local n = k:match('check_(.*)') + k = n + end + + if k == 'dkim' and v then + converted.dkim_domainonly = false + converted.dkim_match_from = true + end + + if k == 'emails' and v then + -- To match surbl behaviour + converted.emails_domainonly = true + end + + if not skip then + converted[k] = lua_util.deepcopy(v) + end end + rbl_section[name] = lua_util.override_defaults(rbl_section[name], converted) end end +end + - gen_keys(t) +-- Converts surbl module config to rbl module +local function emails_section_convert(cfg, section) + local rbl_section = cfg.rbl.rbls + local wl = section.whitelist + if section.rules then + for name, value in section.rules:pairs() do + if rbl_section[name] then + logger.warnx(rspamd_config, 'conflicting names in emails and rbl rules: %s, prefer emails rule!', + name) + end + local converted = { + emails = true, + ignore_defaults = true, + } - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i][1], keys[i][2] + if wl then + converted.whitelist = wl + end + + for k, v in value:pairs() do + local skip = false + -- Rename + if k == 'dnsbl' then + k = 'rbl' + end + if k == 'check_replyto' then + k = 'replyto' + end + if k == 'hashlen' then + k = 'hash_len' + end + if k == 'encoding' then + k = 'hash_format' + end + if k == 'domain_only' then + k = 'emails_domainonly' + end + if k == 'delimiter' then + k = 'emails_delimiter' + end + if k == 'skip_body' then + skip = true + if v then + -- Hack + converted.emails = false + converted.replyto = true + else + converted.emails = true + end + end + if k == 'expect_ip' then + -- Another stupid hack + if not converted.return_codes then + converted.returncodes = {} + end + local symbol = value.symbol or name + converted.returncodes[symbol] = { v } + skip = true + end + + if not skip then + converted[k] = lua_util.deepcopy(v) + end + end + rbl_section[name] = lua_util.override_defaults(rbl_section[name], converted) end end end local function group_transform(cfg, k, v) - if v.name then - k = v.name + if v:at('name') then + k = v:at('name'):unwrap() end local new_group = { symbols = {} } - if v.enabled then - new_group.enabled = v.enabled + if v:at('enabled') then + new_group.enabled = v:at('enabled'):unwrap() end - if v.disabled then - new_group.disabled = v.disabled + if v:at('disabled') then + new_group.disabled = v:at('disabled'):unwrap() end if v.max_score then - new_group.max_score = v.max_score + new_group.max_score = v:at('max_score'):unwrap() end - if v.symbol then - for sk, sv in metric_pairs(v.symbol) do - if sv.name then - sk = sv.name + if v:at('symbol') then + for sk, sv in v:at('symbol'):pairs() do + if sv:at('name') then + sk = sv:at('name'):unwrap() sv.name = nil -- Remove field end @@ -120,26 +183,28 @@ local function group_transform(cfg, k, v) end end - if not cfg.group then + if not cfg:at('group') then cfg.group = {} end - if cfg.group[k] then - cfg.group[k] = lua_util.override_defaults(cfg.group[k], new_group) + if cfg:at('group'):at(k) then + cfg:at('group')[k] = lua_util.override_defaults(cfg:at('group')[k]:unwrap(), new_group) else - cfg.group[k] = new_group + cfg:at('group')[k] = new_group end logger.infox("overriding group %s from the legacy metric settings", k) end local function symbol_transform(cfg, k, v) + local groups = cfg:at('group') -- first try to find any group where there is a definition of this symbol - for gr_n, gr in pairs(cfg.group) do - if gr.symbols and gr.symbols[k] then + for gr_n, gr in groups:pairs() do + local symbols = gr:at('symbols') + if symbols and symbols:at(k) then -- We override group symbol with ungrouped symbol logger.infox("overriding group symbol %s in the group %s", k, gr_n) - gr.symbols[k] = lua_util.override_defaults(gr.symbols[k], v) + symbols[k] = lua_util.override_defaults(symbols:at(k):unwrap(), v:unwrap()) return end end @@ -148,86 +213,52 @@ local function symbol_transform(cfg, k, v) if not sym or not sym.group then -- Otherwise we just use group 'ungrouped' - if not cfg.group.ungrouped then - cfg.group.ungrouped = { - symbols = {} + if not groups:at('ungrouped') then + groups.ungrouped = { + symbols = { + [k] = v + } } + else + groups:at('ungrouped'):at('symbols')[k] = v end - cfg.group.ungrouped.symbols[k] = v logger.debugx("adding symbol %s to the group 'ungrouped'", k) end end -local function test_groups(groups) - for gr_name, gr in pairs(groups) do - if not gr.symbols then - local cnt = 0 - for _, _ in pairs(gr) do - cnt = cnt + 1 - end - - if cnt == 0 then - logger.debugx('group %s is empty', gr_name) - else - logger.infox('group %s has no symbols', gr_name) - end - end +local function convert_metric(cfg, metric) + if metric:type() ~= 'object' then + logger.errx('invalid metric definition: %s', metric) + return end -end -local function convert_metric(cfg, metric) - if metric.actions then - cfg.actions = lua_util.override_defaults(cfg.actions, metric.actions) + if metric:at('actions') then + local existing_actions = cfg:at('actions') and cfg:at('actions'):unwrap() or {} + cfg.actions = lua_util.override_defaults(existing_actions, metric:at('actions'):unwrap()) logger.infox("overriding actions from the legacy metric settings") end - if metric.unknown_weight then - cfg.actions.unknown_weight = metric.unknown_weight + if metric:at('unknown_weight') then + logger.infox("overriding unknown weight from the legacy metric settings") + cfg:at('actions').unknown_weight = metric:at('unknown_weight'):unwrap() end - if metric.subject then + if metric:at('subject') then logger.infox("overriding subject from the legacy metric settings") - cfg.actions.subject = metric.subject + cfg:at('actions').subject = metric:at('subject'):unwrap() end - if metric.group then - for k, v in metric_pairs(metric.group) do + if metric:at('group') then + for k, v in metric:at('group'):pairs() do group_transform(cfg, k, v) end - else - if not cfg.group then - cfg.group = { - ungrouped = { - symbols = {} - } - } - end end - if metric.symbol then - for k, v in metric_pairs(metric.symbol) do + if metric:at('symbol') then + for k, v in metric:at('symbol'):pairs() do symbol_transform(cfg, k, v) end end - - return cfg -end - --- Converts a table of groups indexed by number (implicit array) to a --- merged group definition -local function merge_groups(groups) - local ret = {} - for k, gr in pairs(groups) do - if type(k) == 'number' then - for key, sec in pairs(gr) do - ret[key] = sec - end - else - ret[k] = gr - end - end - - return ret end -- Checks configuration files for statistics @@ -245,155 +276,37 @@ local function check_statistics_sanity() end end --- Converts surbl module config to rbl module -local function surbl_section_convert(cfg, section) - local rbl_section = cfg.rbl.rbls - local wl = section.whitelist - for name, value in pairs(section.rules or {}) do - if rbl_section[name] then - logger.warnx(rspamd_config, 'conflicting names in surbl and rbl rules: %s, prefer surbl rule!', - name) - end - local converted = { - urls = true, - ignore_defaults = true, - } - - if wl then - converted.whitelist = wl - end - - for k, v in pairs(value) do - local skip = false - -- Rename - if k == 'suffix' then - k = 'rbl' - end - if k == 'ips' then - k = 'returncodes' - end - if k == 'bits' then - k = 'returnbits' - end - if k == 'noip' then - k = 'no_ip' - end - -- Crappy legacy - if k == 'options' then - if v == 'noip' or v == 'no_ip' then - converted.no_ip = true - skip = true - end - end - if k:match('check_') then - local n = k:match('check_(.*)') - k = n - end - - if k == 'dkim' and v then - converted.dkim_domainonly = false - converted.dkim_match_from = true - end - - if k == 'emails' and v then - -- To match surbl behaviour - converted.emails_domainonly = true - end - - if not skip then - converted[k] = lua_util.deepcopy(v) - end - end - rbl_section[name] = lua_util.override_defaults(rbl_section[name], converted) - end -end - --- Converts surbl module config to rbl module -local function emails_section_convert(cfg, section) - local rbl_section = cfg.rbl.rbls - local wl = section.whitelist - for name, value in pairs(section.rules or {}) do - if rbl_section[name] then - logger.warnx(rspamd_config, 'conflicting names in emails and rbl rules: %s, prefer emails rule!', - name) - end - local converted = { - emails = true, - ignore_defaults = true, - } +return function(cfg) + local ret = false - if wl then - converted.whitelist = wl - end + if cfg:at('metric') then + local metric = cfg:at('metric') - for k, v in pairs(value) do - local skip = false - -- Rename - if k == 'dnsbl' then - k = 'rbl' - end - if k == 'check_replyto' then - k = 'replyto' - end - if k == 'hashlen' then - k = 'hash_len' - end - if k == 'encoding' then - k = 'hash_format' - end - if k == 'domain_only' then - k = 'emails_domainonly' - end - if k == 'delimiter' then - k = 'emails_delimiter' - end - if k == 'skip_body' then - skip = true - if v then - -- Hack - converted.emails = false - converted.replyto = true - else - converted.emails = true - end - end - if k == 'expect_ip' then - -- Another stupid hack - if not converted.return_codes then - converted.returncodes = {} + -- There are two things that we can have (old `metric_pairs` logic) + -- 1. A metric is a single metric definition like: metric { name = "default", ... } + -- 2. A metric is a list of metrics like: metric { "default": ... } + if metric:at('actions') or metric:at('name') then + convert_metric(cfg, metric) + else + for _, v in cfg:at('metric'):pairs() do + if v:type() == 'object' then + logger.infox('converting metric element %s', v) + convert_metric(cfg, v) end - local symbol = value.symbol or name - converted.returncodes[symbol] = { v } - skip = true - end - - if not skip then - converted[k] = lua_util.deepcopy(v) end end - rbl_section[name] = lua_util.override_defaults(rbl_section[name], converted) - end -end - -return function(cfg) - local ret = false - - if cfg['metric'] then - for _, v in metric_pairs(cfg.metric) do - cfg = convert_metric(cfg, v) - end ret = true end - if cfg.symbols then - for k, v in metric_pairs(cfg.symbols) do + if cfg:at('symbols') then + for k, v in cfg:at('symbols'):pairs() do symbol_transform(cfg, k, v) end end check_statistics_sanity() - if not cfg.actions then + if not cfg:at('actions') then logger.errx('no actions defined') else -- Perform sanity check for actions @@ -402,24 +315,26 @@ return function(cfg) 'rewrite subject', 'rewrite_subject', 'quarantine', 'reject', 'discard' } - if not cfg.actions['no action'] and not cfg.actions['no_action'] and - not cfg.actions['accept'] then + local actions = cfg:at('actions') + if not actions:at('no action') and not actions:at('no_action') and + not actions:at('accept') then for _, d in ipairs(actions_defs) do - if cfg.actions[d] then - - local action_score = nil - if type(cfg.actions[d]) == 'number' then - action_score = cfg.actions[d] - elseif type(cfg.actions[d]) == 'table' and cfg.actions[d]['score'] then - action_score = cfg.actions[d]['score'] + if actions:at(d) then + + local action_score + local act = actions:at(d) + if act:type() ~= 'object' then + action_score = act:unwrap() + elseif act:type() == 'object' and act:at('score') then + action_score = act:at('score'):unwrap() end - if type(cfg.actions[d]) ~= 'table' and not action_score then - cfg.actions[d] = nil + if act:type() ~= 'object' and not action_score then + actions[d] = nil elseif type(action_score) == 'number' and action_score < 0 then - cfg.actions['no_action'] = cfg.actions[d] - 0.001 + actions['no_action'] = actions:at(d):unwrap() - 0.001 logger.infox(rspamd_config, 'set no_action score to: %s, as action %s has negative score', - cfg.actions['no_action'], d) + actions:at('no_action'):unwrap(), d) break end end @@ -433,7 +348,7 @@ return function(cfg) actions_set['grow_factor'] = true actions_set['subject'] = true - for k, _ in pairs(cfg.actions) do + for k, _ in cfg:at('actions'):pairs() do if not actions_set[k] then logger.warnx(rspamd_config, 'unknown element in actions section: %s', k) end @@ -452,13 +367,13 @@ return function(cfg) for i = 1, (#actions_order - 1) do local act = actions_order[i] - if cfg.actions[act] and type(cfg.actions[act]) == 'number' then - local score = cfg.actions[act] + if actions:at(act) and actions:at(act):type() ~= 'object' then + local score = actions:at(act):unwrap() for j = i + 1, #actions_order do local next_act = actions_order[j] - if cfg.actions[next_act] and type(cfg.actions[next_act]) == 'number' then - local next_score = cfg.actions[next_act] + if actions:at(next_act) and actions:at(next_act):type() == 'number' then + local next_score = actions:at(next_act):unwrap() if next_score <= score then logger.errx(rspamd_config, 'invalid actions thresholds order: action %s (%s) must have lower ' .. 'score than action %s (%s)', act, score, next_act, next_score) @@ -470,15 +385,18 @@ return function(cfg) end end - if not cfg.group then - logger.errx('no symbol groups defined') - else - if cfg.group[1] then - -- We need to merge groups - cfg.group = merge_groups(cfg.group) - ret = true + -- DKIM signing/ARC legacy + for _, mod in ipairs({ 'dkim_signing', 'arc' }) do + if cfg:at(mod) then + if cfg:at(mod):at('auth_only') then + if cfg:at(mod):at('sign_authenticated') then + logger.warnx(rspamd_config, + 'both auth_only (%s) and sign_authenticated (%s) for %s are specified, prefer auth_only', + cfg:at(mod):at('auth_only'):unwrap(), cfg:at(mod):at('sign_authenticated'):unwrap(), mod) + end + cfg:at(mod).sign_authenticated = cfg:at(mod):at('auth_only') + end end - test_groups(cfg.group) end -- Deal with dkim settings @@ -504,101 +422,22 @@ return function(cfg) end end - -- Again: legacy stuff :( - if not cfg.dkim.sign_headers then - local sec = cfg.dkim_signing - if sec and sec[1] then - sec = cfg.dkim_signing[1] - end - - if sec and sec.sign_headers then - cfg.dkim.sign_headers = sec.sign_headers - end - end - - -- DKIM signing/ARC legacy - for _, mod in ipairs({ 'dkim_signing', 'arc' }) do - if cfg[mod] then - if cfg[mod].auth_only ~= nil then - if cfg[mod].sign_authenticated ~= nil then - logger.warnx(rspamd_config, - 'both auth_only (%s) and sign_authenticated (%s) for %s are specified, prefer auth_only', - cfg[mod].auth_only, cfg[mod].sign_authenticated, mod) - end - cfg[mod].sign_authenticated = cfg[mod].auth_only - end - end - end - - if cfg.dkim and cfg.dkim.sign_headers and type(cfg.dkim.sign_headers) == 'table' then - -- Flatten - cfg.dkim.sign_headers = table.concat(cfg.dkim.sign_headers, ':') - end - -- Try to find some obvious issues with configuration - for k, v in pairs(cfg) do - if type(v) == 'table' and v[k] and type(v[k]) == 'table' then + for k, v in cfg:pairs() do + if v:type() == 'object' and v:at(k) and v:at(k):type() == 'object' then logger.errx('nested section: %s { %s { ... } }, it is likely a configuration error', k, k) end end -- If neural network is enabled we MUST have `check_all_filters` flag - if cfg.neural then - if not cfg.options then - cfg.options = {} - end - - if not cfg.options.check_all_filters then - logger.infox(rspamd_config, 'enable `options.check_all_filters` for neural network') - cfg.options.check_all_filters = true - end - end - - -- Deal with IP_SCORE - if cfg.ip_score and (cfg.ip_score.servers or cfg.redis.servers) then - logger.warnx(rspamd_config, 'ip_score module is deprecated in honor of reputation module!') - - if not cfg.reputation then - cfg.reputation = { - rules = {} - } - end + if cfg:at('neural') then - if not cfg.reputation.rules then - cfg.reputation.rules = {} - end - - if not fun.any(function(_, v) - return v.selector and v.selector.ip - end, - cfg.reputation.rules) then - logger.infox(rspamd_config, 'attach ip reputation element to use it') - - cfg.reputation.rules.ip_score = { - selector = { - ip = {}, - }, - backend = { - redis = {}, - } - } - - if cfg.ip_score.servers then - cfg.reputation.rules.ip_score.backend.redis.servers = cfg.ip_score.servers + if cfg:at('options') then + if not cfg:at('options'):at('check_all_filters') then + logger.infox(rspamd_config, 'enable `options.check_all_filters` for neural network') + cfg:at('options')['check_all_filters'] = true end - - if cfg.symbols and cfg.symbols['IP_SCORE'] then - local t = cfg.symbols['IP_SCORE'] - - if not cfg.symbols['SENDER_REP_SPAM'] then - cfg.symbols['SENDER_REP_SPAM'] = t - cfg.symbols['SENDER_REP_HAM'] = t - cfg.symbols['SENDER_REP_HAM'].weight = -(t.weight or 0) - end - end - else - logger.infox(rspamd_config, 'ip reputation already exists, do not do any IP_SCORE transforms') end end @@ -613,7 +452,7 @@ return function(cfg) end surbl_section_convert(cfg, cfg.surbl) logger.infox(rspamd_config, 'converted surbl rules to rbl rules') - cfg.surbl = {} + cfg.surbl = nil end if cfg.emails then @@ -627,7 +466,7 @@ return function(cfg) end emails_section_convert(cfg, cfg.emails) logger.infox(rspamd_config, 'converted emails rules to rbl rules') - cfg.emails = {} + cfg.emails = nil end -- Common misprint options.upstreams -> options.upstream diff --git a/lualib/lua_maps.lua b/lualib/lua_maps.lua index a912039be..2699ea214 100644 --- a/lualib/lua_maps.lua +++ b/lualib/lua_maps.lua @@ -97,18 +97,6 @@ local external_map_schema = ts.shape { local rspamd_http = require "rspamd_http" local ucl = require "ucl" -local function url_encode_string(str) - str = string.gsub(str, "([^%w _%%%-%.~])", - function(c) - return string.format("%%%02X", string.byte(c)) - end) - str = string.gsub(str, " ", "+") - return str -end - -assert(url_encode_string('上海+中國') == '%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B') -assert(url_encode_string('? and the Mysterians') == '%3F+and+the+Mysterians') - local function query_external_map(map_config, upstreams, key, callback, task) local http_method = (map_config.method == 'body' or map_config.method == 'form') and 'POST' or 'GET' local upstream = upstreams:get_upstream_round_robin() @@ -127,7 +115,7 @@ local function query_external_map(map_config, upstreams, key, callback, task) key = key } elseif map_config.method == 'query' then - url = string.format('%s?key=%s', url, url_encode_string(tostring(key))) + url = string.format('%s?key=%s', url, lua_util.url_encode_string(tostring(key))) end elseif type(key) == 'table' then if map_config.method == 'body' then @@ -150,7 +138,7 @@ local function query_external_map(map_config, upstreams, key, callback, task) local params_table = {} for k, v in pairs(key) do if type(v) == 'string' then - table.insert(params_table, string.format('%s=%s', url_encode_string(k), url_encode_string(v))) + table.insert(params_table, string.format('%s=%s', lua_util.url_encode_string(k), lua_util.url_encode_string(v))) end end url = string.format('%s?%s', url, table.concat(params_table, '&')) diff --git a/lualib/lua_redis.lua b/lualib/lua_redis.lua index 818d955e9..2c77c100a 100644 --- a/lualib/lua_redis.lua +++ b/lualib/lua_redis.lua @@ -1162,8 +1162,11 @@ local function prepare_redis_call(script) end -- Call load script on each server, set loaded flag - script.in_flight = #servers - for _, s in ipairs(servers) do + if not script.servers_ready then + script.servers_ready = { } -- possible values for each server: unsent, tempfail, failed, done + end + + for idx, s in ipairs(servers) do local cur_opts = { host = s:get_addr(), timeout = script.redis_params['timeout'], @@ -1172,6 +1175,11 @@ local function prepare_redis_call(script) upstream = s } + -- By default we start from unsent status + if not script.servers_ready[idx] then + script.servers_ready[idx] = "unsent" + end + if script.redis_params['username'] then cur_opts['username'] = script.redis_params['username'] end @@ -1190,46 +1198,89 @@ local function prepare_redis_call(script) return options end +local function is_all_servers_ready(script) + for _, s in ipairs(script.servers_ready) do + if s == "unsent" or s == "tempfail" then + return false + end + end + + -- We assume that permanent errors are not recoverable, so we will just skip those servers + return true +end + +local function is_all_servers_failed(script) + for _, s in ipairs(script.servers_ready) do + if s == "unsent" or s == "tempfail" or s == "done" then + return false + end + end + + return true +end + +local function script_description(script) + return script.filename and ("from file: " .. script.filename) + or ("with id: " .. script.id) +end + local function load_script_task(script, task, is_write) local rspamd_redis = require "rspamd_redis" local opts = prepare_redis_call(script) - for _, opt in ipairs(opts) do - opt.task = task - opt.is_write = is_write - opt.callback = function(err, data) - if err then - logger.errx(task, 'cannot upload script to %s: %s; registered from: %s:%s', - opt.upstream:get_addr():to_string(true), - err, script.caller.short_src, script.caller.currentline) - opt.upstream:fail() - script.fatal_error = err - else - opt.upstream:ok() - logger.infox(task, - "uploaded redis script to %s %s %s, sha: %s", - opt.upstream:get_addr():to_string(true), - script.filename and "from file" or "with id", script.filename or script.id, data) - script.sha = data -- We assume that sha is the same on all servers - end - script.in_flight = script.in_flight - 1 - - if script.in_flight == 0 then - script_set_loaded(script) - end - end + for idx, opt in ipairs(opts) do + if script.servers_ready[idx] ~= 'done' then + opt.task = task + opt.is_write = is_write + opt.callback = function(err, data) + if err then + if string.match(err, 'ERR') then + logger.errx(task, 'cannot upload script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + opt.upstream:fail() + script.servers_ready[idx] = "failed" + return + else + -- Assume temporary error + logger.infox(task, 'temporary error uploading script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + script.servers_ready[idx] = "tempfail" + return + end + else + opt.upstream:ok() + logger.infox(task, + "uploaded redis script to %s %s, sha: %s", + opt.upstream:get_addr():to_string(true), + script_description(script), data) + script.sha = data -- We assume that sha is the same on all servers + script.servers_ready[idx] = "done" + end + if is_all_servers_ready(script) then + script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" + end + end -- callback - local ret = rspamd_redis.make_request(opt) + local ret = rspamd_redis.make_request(opt) - if not ret then - logger.errx('cannot execute redis request to load script on %s', - opt.upstream:get_addr()) - script.in_flight = script.in_flight - 1 - opt.upstream:fail() + if not ret then + logger.errx('cannot execute redis request to load script on %s', + opt.upstream:get_addr()) + script.servers_ready[idx] = "failed" + opt.upstream:fail() + end end - if script.in_flight == 0 then + if is_all_servers_ready(script) then script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" end end end @@ -1238,44 +1289,62 @@ local function load_script_taskless(script, cfg, ev_base, is_write) local rspamd_redis = require "rspamd_redis" local opts = prepare_redis_call(script) - for _, opt in ipairs(opts) do - opt.config = cfg - opt.ev_base = ev_base - opt.is_write = is_write - opt.callback = function(err, data) - if err then - logger.errx(cfg, 'cannot upload script to %s: %s; registered from: %s:%s, filename: %s', - opt.upstream:get_addr():to_string(true), - err, script.caller.short_src, script.caller.currentline, script.filename) - opt.upstream:fail() - script.fatal_error = err - else - opt.upstream:ok() - logger.infox(cfg, - "uploaded redis script to %s %s %s, sha: %s", - opt.upstream:get_addr():to_string(true), - script.filename and "from file" or "with id", script.filename or script.id, - data) - script.sha = data -- We assume that sha is the same on all servers - script.fatal_error = nil - end - script.in_flight = script.in_flight - 1 + for idx, opt in ipairs(opts) do + if script.servers_ready[idx] ~= 'done' then + opt.config = cfg + opt.ev_base = ev_base + opt.is_write = is_write + opt.callback = function(err, data) + if err then + if string.match(err, 'ERR') then + logger.errx(cfg, 'cannot upload script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + opt.upstream:fail() + script.servers_ready[idx] = "failed" + return + else + -- Assume temporary error + logger.infox(cfg, 'temporary error uploading script %s to %s: %s; registered from: %s:%s', + script_description(script), + opt.upstream:get_addr():to_string(true), + err, script.caller.short_src, script.caller.currentline) + script.servers_ready[idx] = "tempfail" + return + end + else + opt.upstream:ok() + logger.infox(cfg, + "uploaded redis script %s to %s, sha: %s", + script_description(script), + opt.upstream:get_addr():to_string(true), + data) + script.sha = data -- We assume that sha is the same on all servers + script.servers_ready[idx] = "done" + end - if script.in_flight == 0 then - script_set_loaded(script) + if is_all_servers_ready(script) then + script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script to any server" + end end - end - local ret = rspamd_redis.make_request(opt) + local ret = rspamd_redis.make_request(opt) - if not ret then - logger.errx('cannot execute redis request to load script on %s', - opt.upstream:get_addr()) - script.in_flight = script.in_flight - 1 - opt.upstream:fail() + if not ret then + logger.errx('cannot execute redis request to load script %s on %s', + script_description(script), + opt.upstream:get_addr()) + script.servers_ready[idx] = "failed" + opt.upstream:fail() + end end - if script.in_flight == 0 then + if is_all_servers_ready(script) then script_set_loaded(script) + elseif is_all_servers_failed(script) then + script.fatal_error = "cannot upload script " .. script_description(script) .. " to any server" end end end @@ -1306,12 +1375,17 @@ local function add_redis_script(script, redis_params, caller_level, maybe_filena rspamd_config:add_on_load(function(cfg, ev_base, worker) local mult = 0.0 rspamd_config:add_periodic(ev_base, 0.0, function() - if not new_script.sha then + if not new_script.sha and not new_script.fatal_error then load_redis_script(new_script, cfg, ev_base, worker) - mult = mult + 1 + + -- Do not wait for more than 10 seconds to retry + if mult < 10 then + mult = mult + 1 + end return 1.0 * mult -- Check one more time in one second end + -- All loaded, we are good to go return false end, false) end) @@ -1386,10 +1460,12 @@ local function exec_redis_script(id, params, callback, keys, args) callback(err, data) elseif string.match(err, 'NOSCRIPT') then -- Schedule restart + logger.infox(params.task, 'redis script %s is not loaded (NOSCRIPT returned), scheduling reload', + script_description(script)) script.sha = nil if can_reload then table.insert(script.waitq, do_call) - if script.in_flight == 0 then + if not script.servers_ready then -- Reload scripts if this has not been initiated yet if params.task then load_script_task(script, params.task) @@ -1437,6 +1513,8 @@ local function exec_redis_script(id, params, callback, keys, args) do_call(true) else -- Delayed until scripts are loaded + logger.infox(params.task or rspamd_config, 'redis script %s is not loaded, trying to load', + script_description(script)) if not params.task then table.insert(script.waitq, do_call) else diff --git a/lualib/lua_scanners/icap.lua b/lualib/lua_scanners/icap.lua index 682562d85..2e3ced034 100644 --- a/lualib/lua_scanners/icap.lua +++ b/lualib/lua_scanners/icap.lua @@ -245,7 +245,7 @@ local function icap_check(task, content, digest, rule, maybe_part) local req_hlen = 2 if maybe_part then table.insert(req_headers, - string.format('GET http://%s/%s HTTP/1.0\r\n', in_client_ip, maybe_part:get_filename())) + string.format('GET http://%s/%s HTTP/1.0\r\n', in_client_ip, lua_util.url_encode_string(maybe_part:get_filename()))) if rule.use_specific_content_type then table.insert(http_headers, string.format('Content-Type: %s/%s\r\n', maybe_part:get_detected_type())) --else diff --git a/lualib/lua_util.lua b/lualib/lua_util.lua index 650ad5db1..470925b95 100644 --- a/lualib/lua_util.lua +++ b/lualib/lua_util.lua @@ -755,9 +755,29 @@ local function override_defaults(def, override) local res = {} - for k, v in pairs(override) do - if type(v) == 'table' then - if def[k] and type(def[k]) == 'table' then + -- Allow transparent ucl + local pairs_func, def_pairs_func = pairs, pairs + local type_func, def_type_func = type, type + if type(override[0]) == 'userdata' then + pairs_func = function(t) + return t:pairs() + end + type_func = function(t) + return t:type() + end + end + if type(def[0]) == 'userdata' then + def_pairs_func = function(t) + return t:pairs() + end + def_type_func = function(t) + return t:type() + end + end + + for k, v in pairs_func(override) do + if type_func(v) == 'table' then + if def[k] and def_type_func(def[k]) == 'table' then -- Recursively override elements res[k] = override_defaults(def[k], v) else @@ -768,7 +788,7 @@ local function override_defaults(def, override) end end - for k, v in pairs(def) do + for k, v in def_pairs_func(def) do if type(res[k]) == 'nil' then res[k] = v end @@ -1667,6 +1687,30 @@ local function join_path(...) end exports.join_path = join_path +---[[[ +-- @function lua_util.url_encode_string(str) +-- URL encodes a string +-- +-- @param {string} str string to encode +-- @return {string} URL encoded string +-- +---]]] +local function url_encode_string(str) + if str == nil then + return '' + end + str = string.gsub(str, "([^%w _%%%-%.~])", + function(c) + return string.format("%%%02X", string.byte(c)) + end) + str = string.gsub(str, " ", "+") + return str +end +exports.url_encode_string = url_encode_string + +assert(url_encode_string('上海+中國') == '%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B') +assert(url_encode_string('? and the Mysterians') == '%3F+and+the+Mysterians') + -- Short unit test for sanity if path_sep == '/' then assert(join_path('/path', 'to', 'file') == '/path/to/file') diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..67504a812 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2554 @@ +{ + "name": "rspamd", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@stylistic/eslint-plugin": "*", + "eslint": "^9.7.0", + "postcss-html": "*", + "stylelint": ">=13.6.0", + "stylelint-config-standard": "*" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", + "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.4.1" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", + "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", + "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", + "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", + "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.3.0.tgz", + "integrity": "sha512-rtiz6u5gRyyEZp36FcF1/gHJbsbT3qAgXZ1qkad6Nr/xJ9wrSJkiSFFQhpYVTIZ7FJNRJurEcumZDCwN9dEI4g==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "2.3.0", + "@stylistic/eslint-plugin-jsx": "2.3.0", + "@stylistic/eslint-plugin-plus": "2.3.0", + "@stylistic/eslint-plugin-ts": "2.3.0", + "@types/eslint": "^8.56.10" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.3.0.tgz", + "integrity": "sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-2.3.0.tgz", + "integrity": "sha512-tsQ0IEKB195H6X9A4iUSgLLLKBc8gUBWkBIU8tp1/3g2l8stu+PtMQVV/VmK1+3bem5FJCyvfcZIQ/WF1fsizA==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^2.3.0", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-2.3.0.tgz", + "integrity": "sha512-xboPWGUU5yaPlR+WR57GwXEuY4PSlPqA0C3IdNA/+1o2MuBi95XgDJcZiJ9N+aXsqBXAPIpFFb+WQ7QEHo4f7g==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^7.12.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/utils": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", + "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/typescript-estree": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@stylistic/eslint-plugin-ts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.3.0.tgz", + "integrity": "sha512-wqOR38/uz/0XPnHX68ftp8sNMSAqnYGjovOTN7w00xnjS6Lxr3Sk7q6AaxWWqbMvOj7V2fQiMC5HWAbTruJsCg==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "2.3.0", + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^7.12.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/utils": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", + "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/typescript-estree": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", + "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", + "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", + "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", + "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-functions-list": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz", + "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==", + "dev": true, + "engines": { + "node": ">=12 || >=16" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz", + "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.7.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-html": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.7.0.tgz", + "integrity": "sha512-MfcMpSUIaR/nNgeVS8AyvyDugXlADjN9AcV7e5rDfrF1wduIAGSkL4q2+wgrZgA3sHVAHLDO9FuauHhZYW2nBw==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.0", + "js-tokens": "^9.0.0", + "postcss": "^8.4.0", + "postcss-safe-parser": "^6.0.0" + }, + "engines": { + "node": "^12 || >=14" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.7.0.tgz", + "integrity": "sha512-Q1ATiXlz+wYr37a7TGsfvqYn2nSR3T/isw3IWlZQzFzCNoACHuGBb6xBplZXz56/uDRJHIygxjh7jbV/8isewA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13", + "@csstools/selector-specificity": "^3.1.1", + "@dual-bundle/import-meta-resolve": "^4.1.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.2", + "css-tree": "^2.3.1", + "debug": "^4.3.5", + "fast-glob": "^3.3.2", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^9.0.0", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^5.3.1", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.34.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.7", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.1", + "postcss": "^8.4.39", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^7.0.0", + "postcss-selector-parser": "^6.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^7.1.0", + "supports-hyperlinks": "^3.0.0", + "svg-tags": "^1.0.0", + "table": "^6.8.2", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "36.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz", + "integrity": "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "dependencies": { + "stylelint-config-recommended": "^14.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.0.0.tgz", + "integrity": "sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==", + "dev": true, + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/postcss-safe-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", + "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index dd1bca360..58fc7ba62 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,9 @@ { "devDependencies": { - "eslint": "*", "@stylistic/eslint-plugin": "*", + "eslint": "^9.7.0", + "postcss-html": "*", "stylelint": ">=13.6.0", - "stylelint-config-standard": "*", - "postcss-html": "*" - }, - "eslintIgnore": [ - "*.min.js", - "index.html", - "prism.js" - ] + "stylelint-config-standard": "*" + } } diff --git a/rules/archives.lua b/rules/archives.lua index 83ac27df8..c582b93bd 100644 --- a/rules/archives.lua +++ b/rules/archives.lua @@ -2,19 +2,19 @@ local rspamd_regexp = require "rspamd_regexp" local lua_maps = require "lua_maps" local clickbait_map = lua_maps.map_add_from_ucl( - { - string.format('%s/maps.d/%s', rspamd_paths.CONFDIR, 'exe_clickbait.inc'), - string.format('%s/local.d/maps.d/%s', rspamd_paths.LOCAL_CONFDIR, 'exe_clickbait.inc') - }, - 'regexp', - 'Inappropriate descriptions for executables' + { + string.format('%s/maps.d/%s', rspamd_paths.CONFDIR, 'exe_clickbait.inc'), + string.format('%s/local.d/maps.d/%s', rspamd_paths.LOCAL_CONFDIR, 'exe_clickbait.inc') + }, + 'regexp', + 'Inappropriate descriptions for executables' ) local exe_re = rspamd_regexp.create_cached([[/\.exe$|\.com$/i]]) local img_re = rspamd_regexp.create_cached([[/\.img$/i]]) local rar_re = rspamd_regexp.create_cached([[/\.rar$|\.r[0-9]{2}$/i]]) -local id = rspamd_config:register_symbol{ +local id = rspamd_config:register_symbol { callback = function(task) local num_checked = 0 local have_subject_clickbait = false @@ -52,7 +52,7 @@ local id = rspamd_config:register_symbol{ local name = info.name if img_re:match(name) then - local ratio = info.uncompressed_size/info.compressed_size + local ratio = info.uncompressed_size / info.compressed_size if ratio >= 500 then task:insert_result('UDF_COMPRESSION_500PLUS', 1.0) end @@ -86,7 +86,7 @@ local id = rspamd_config:register_symbol{ type = 'callback', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'exe file in archive with clickbait filename', group = 'malware', name = 'EXE_ARCHIVE_CLICKBAIT_FILENAME', @@ -96,7 +96,7 @@ rspamd_config:register_symbol{ type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'exe file in archive with clickbait subject', group = 'malware', name = 'EXE_ARCHIVE_CLICKBAIT_SUBJECT', @@ -106,47 +106,47 @@ rspamd_config:register_symbol{ type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'exe file in archive', group = 'malware', name = 'EXE_IN_ARCHIVE', one_shot = true, parent = id, - score = 0.5, + score = 1.5, type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'rar with wrong extension containing exe file', group = 'malware', name = 'EXE_IN_MISIDENTIFIED_RAR', one_shot = true, parent = id, - score = 2.0, + score = 5.0, type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'rar with wrong extension', group = 'malware', name = 'MISIDENTIFIED_RAR', one_shot = true, parent = id, - score = 2.0, + score = 4.0, type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'single file container bearing executable', group = 'malware', name = 'SINGLE_FILE_ARCHIVE_WITH_EXE', one_shot = true, parent = id, - score = 1.0, + score = 5.0, type = 'virtual', } -rspamd_config:register_symbol{ +rspamd_config:register_symbol { description = 'very well compressed img file in archive', name = 'UDF_COMPRESSION_500PLUS', one_shot = true, diff --git a/rules/misc.lua b/rules/misc.lua index faf4a8fb8..4ddb00dfb 100644 --- a/rules/misc.lua +++ b/rules/misc.lua @@ -862,3 +862,71 @@ rspamd_config.COMPLETELY_EMPTY = { group = 'blankspam', score = 15 } + +-- Preserve compatibility +local rdns_auth_and_local_conf = lua_util.config_check_local_or_authed(rspamd_config, 'once_received', + false, false) +-- Check for the hostname if it was not set +local rnds_check_id = rspamd_config:register_symbol { + name = 'RDNS_CHECK', + callback = function(task) + if not task:get_hostname() then + -- Try to resolve + local task_ip = task:get_ip() + if task_ip and task_ip:is_valid() then + local rspamd_logger = require "rspamd_logger" + local function rdns_dns_cb(_, to_resolve, results, err) + if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then + rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) + task:insert_result('RDNS_DNSFAIL', 1.0) + end + + if not results then + task:insert_result('RDNS_NONE', 1.0) + else + rspamd_logger.infox(task, 'source hostname has not been passed to Rspamd from MTA, ' .. + 'but we could resolve source IP address PTR %s as "%s"', + to_resolve, results[1]) + task:set_hostname(results[1]) + end + end + task:get_resolver():resolve_ptr({ task = task, + name = task_ip:to_string(), + callback = rdns_dns_cb, + forced = true + }) + end + end + end, + type = 'prefilter', + -- TODO: settings might need to use this symbol if they depend on hostname... + priority = lua_util.symbols_priorities.top - 1, + description = 'Check if hostname has been resolved by MTA', + condition = function(task) + local task_ip = task:get_ip() + if ((not rdns_auth_and_local_conf[1] and task:get_user()) or + (not rdns_auth_and_local_conf[2] and task_ip and task_ip:is_local())) then + return false + end + + return true + end +} + +rspamd_config:register_symbol { + type = 'virtual', + name = 'RDNS_DNSFAIL', + score = 0.0, + description = 'DNS failure resolving RDNS', + group = 'hfilter', + parent = rnds_check_id, + +} +rspamd_config:register_symbol { + type = 'virtual', + name = 'RDNS_NONE', + score = 2.0, + description = 'DNS failure resolving RDNS', + group = 'hfilter', + parent = rnds_check_id, +}
\ No newline at end of file diff --git a/src/client/rspamc.cxx b/src/client/rspamc.cxx index 1e0830493..1c67e4167 100644 --- a/src/client/rspamc.cxx +++ b/src/client/rspamc.cxx @@ -524,16 +524,13 @@ rspamc_password_callback(const char *option_name, else { /* Strip trailing spaces */ auto *map = (char *) locked_mmap.value().get_map(); - auto *end = map + locked_mmap.value().get_size() - 1; - - while (g_ascii_isspace(*end) && end > map) { - end--; - } - - end++; - value_view = std::string_view{map, static_cast<std::size_t>(end - map + 1)}; - processed_passwd.assign(std::begin(value_view), std::end(value_view)); - processed_passwd.push_back('\0'); + value_view = std::string_view{map, locked_mmap->get_size()}; + auto right = value_view.end() - 1; + for (; right > value_view.cbegin() && g_ascii_isspace(*right); --right) + ; + std::string_view str{value_view.begin(), static_cast<size_t>(right - value_view.begin()) + 1}; + processed_passwd.assign(std::begin(str), std::end(str)); + processed_passwd.push_back('\0'); /* Null-terminate for C part */ } } else { diff --git a/src/client/rspamdclient.c b/src/client/rspamdclient.c index 2b8d0e9bb..bcb3cf67c 100644 --- a/src/client/rspamdclient.c +++ b/src/client/rspamdclient.c @@ -302,12 +302,10 @@ rspamd_client_init(struct rspamd_http_context *http_ctx, conn->timeout = timeout; if (key) { - conn->key = rspamd_pubkey_from_base32(key, 0, RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + conn->key = rspamd_pubkey_from_base32(key, 0, RSPAMD_KEYPAIR_KEX); if (conn->key) { - conn->keypair = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + conn->keypair = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); rspamd_http_connection_set_key(conn->http_conn, conn->keypair); } else { diff --git a/src/controller.c b/src/controller.c index d91f99098..386448f93 100644 --- a/src/controller.c +++ b/src/controller.c @@ -1945,7 +1945,7 @@ rspamd_controller_learn_fin_task(void *ud) if (task->err != NULL) { msg_info_session("cannot learn <%s>: %e", - MESSAGE_FIELD(task, message_id), task->err); + MESSAGE_FIELD_CHECK(task, message_id), task->err); rspamd_controller_send_error(conn_ent, task->err->code, "%s", task->err->message); @@ -1957,14 +1957,14 @@ rspamd_controller_learn_fin_task(void *ud) msg_info_task("<%s> learned message as %s: %s", rspamd_inet_address_to_string(session->from_addr), session->is_spam ? "spam" : "ham", - MESSAGE_FIELD(task, message_id)); + MESSAGE_FIELD_CHECK(task, message_id)); rspamd_controller_send_string(conn_ent, "{\"success\":true}"); return TRUE; } if (!rspamd_task_process(task, RSPAMD_TASK_PROCESS_LEARN)) { msg_info_task("cannot learn <%s>: %e", - MESSAGE_FIELD(task, message_id), task->err); + MESSAGE_FIELD_CHECK(task, message_id), task->err); if (task->err) { rspamd_controller_send_error(conn_ent, task->err->code, "%s", @@ -1985,7 +1985,7 @@ rspamd_controller_learn_fin_task(void *ud) msg_info_task("<%s> learned message as %s: %s", rspamd_inet_address_to_string(session->from_addr), session->is_spam ? "spam" : "ham", - MESSAGE_FIELD(task, message_id)); + MESSAGE_FIELD_CHECK(task, message_id)); rspamd_controller_send_string(conn_ent, "{\"success\":true}"); } @@ -2114,7 +2114,7 @@ rspamd_controller_handle_learn_common( if (!rspamd_task_process(task, RSPAMD_TASK_PROCESS_LEARN)) { msg_warn_session("<%s> message cannot be processed", - MESSAGE_FIELD(task, message_id)); + MESSAGE_FIELD_CHECK(task, message_id)); goto end; } diff --git a/src/fuzzy_storage.c b/src/fuzzy_storage.c index 445289511..5fd3303dc 100644 --- a/src/fuzzy_storage.c +++ b/src/fuzzy_storage.c @@ -1088,8 +1088,7 @@ rspamd_fuzzy_make_reply(struct rspamd_fuzzy_cmd *cmd, len, session->reply.hdr.nonce, session->nm, - session->reply.hdr.mac, - RSPAMD_CRYPTOBOX_MODE_25519); + session->reply.hdr.mac); } else if (default_disabled) { /* Hash is from a forbidden flag by default, and there is no encryption override */ @@ -1668,8 +1667,7 @@ rspamd_fuzzy_decrypt_command(struct fuzzy_session *s, unsigned char *buf, gsize } /* Now process the remote pubkey */ - rk = rspamd_pubkey_from_bin(hdr.pubkey, sizeof(hdr.pubkey), - RSPAMD_KEYPAIR_KEX, RSPAMD_CRYPTOBOX_MODE_25519); + rk = rspamd_pubkey_from_bin(hdr.pubkey, sizeof(hdr.pubkey), RSPAMD_KEYPAIR_KEX); if (rk == NULL) { msg_err("bad key; ip=%s", @@ -1683,7 +1681,7 @@ rspamd_fuzzy_decrypt_command(struct fuzzy_session *s, unsigned char *buf, gsize /* Now decrypt request */ if (!rspamd_cryptobox_decrypt_nm_inplace(buf, buflen, hdr.nonce, rspamd_pubkey_get_nm(rk, key->key), - hdr.mac, RSPAMD_CRYPTOBOX_MODE_25519)) { + hdr.mac)) { msg_err("decryption failed; ip=%s", rspamd_inet_address_to_string(s->addr)); rspamd_pubkey_unref(rk); @@ -2771,8 +2769,7 @@ fuzzy_add_keypair_from_ucl(const ucl_object_t *obj, khash_t(rspamd_fuzzy_keys_ha return NULL; } - if (rspamd_keypair_alg(kp) != RSPAMD_CRYPTOBOX_MODE_25519 || - rspamd_keypair_type(kp) != RSPAMD_KEYPAIR_KEX) { + if (rspamd_keypair_type(kp) != RSPAMD_KEYPAIR_KEX) { return FALSE; } @@ -2837,7 +2834,7 @@ fuzzy_add_keypair_from_ucl(const ucl_object_t *obj, khash_t(rspamd_fuzzy_keys_ha } } - msg_debug("loaded keypair %*bs", rspamd_cryptobox_pk_bytes(RSPAMD_CRYPTOBOX_MODE_25519), pk); + msg_debug("loaded keypair %*bs", crypto_box_publickeybytes(), pk); return key; } diff --git a/src/libcryptobox/cryptobox.c b/src/libcryptobox/cryptobox.c index aa093d01e..eeeed020c 100644 --- a/src/libcryptobox/cryptobox.c +++ b/src/libcryptobox/cryptobox.c @@ -38,19 +38,8 @@ #endif #ifdef HAVE_OPENSSL #include <openssl/opensslv.h> -/* Openssl >= 1.0.1d is required for GCM verification */ -#if OPENSSL_VERSION_NUMBER >= 0x1000104fL -#define HAVE_USABLE_OPENSSL 1 -#endif -#endif - -#ifdef HAVE_USABLE_OPENSSL #include <openssl/evp.h> -#include <openssl/ec.h> -#include <openssl/ecdh.h> -#include <openssl/ecdsa.h> -#include <openssl/rand.h> -#define CRYPTOBOX_CURVE_NID NID_X9_62_prime256v1 +#include <openssl/rsa.h> #endif #include <signal.h> @@ -324,759 +313,327 @@ void rspamd_cryptobox_deinit(struct rspamd_cryptobox_library_ctx *ctx) } } -void rspamd_cryptobox_keypair(rspamd_pk_t pk, rspamd_sk_t sk, - enum rspamd_cryptobox_mode mode) +void rspamd_cryptobox_keypair(rspamd_pk_t pk, rspamd_sk_t sk) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - ottery_rand_bytes(sk, rspamd_cryptobox_MAX_SKBYTES); - sk[0] &= 248; - sk[31] &= 127; - sk[31] |= 64; - - crypto_scalarmult_base(pk, sk); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EC_KEY *ec_sec; - const BIGNUM *bn_sec; - - const EC_POINT *ec_pub; - gsize len; - - ec_sec = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - g_assert(ec_sec != NULL); - g_assert(EC_KEY_generate_key(ec_sec) != 0); - - bn_sec = EC_KEY_get0_private_key(ec_sec); - g_assert(bn_sec != NULL); - ec_pub = EC_KEY_get0_public_key(ec_sec); - g_assert(ec_pub != NULL); -#if OPENSSL_VERSION_MAJOR >= 3 - unsigned char *buf = NULL; /* Thanks openssl for this API (no) */ - len = EC_POINT_point2buf(EC_KEY_get0_group(ec_sec), ec_pub, - POINT_CONVERSION_UNCOMPRESSED, &buf, NULL); - g_assert(len <= (int) rspamd_cryptobox_pk_bytes(mode)); - memcpy(pk, buf, len); - OPENSSL_free(buf); -#else - BIGNUM *bn_pub; - bn_pub = EC_POINT_point2bn(EC_KEY_get0_group(ec_sec), - ec_pub, POINT_CONVERSION_UNCOMPRESSED, NULL, NULL); - len = BN_num_bytes(bn_pub); - g_assert(len <= (int) rspamd_cryptobox_pk_bytes(mode)); - BN_bn2bin(bn_pub, pk); - BN_free(bn_pub); -#endif + ottery_rand_bytes(sk, rspamd_cryptobox_MAX_SKBYTES); + sk[0] &= 248; + sk[31] &= 127; + sk[31] |= 64; - len = BN_num_bytes(bn_sec); - g_assert(len <= (int) sizeof(rspamd_sk_t)); - BN_bn2bin(bn_sec, sk); + crypto_scalarmult_base(pk, sk); +} - EC_KEY_free(ec_sec); -#endif - } +void rspamd_cryptobox_keypair_sig(rspamd_sig_pk_t pk, rspamd_sig_sk_t sk) +{ + crypto_sign_keypair(pk, sk); } -void rspamd_cryptobox_keypair_sig(rspamd_sig_pk_t pk, rspamd_sig_sk_t sk, - enum rspamd_cryptobox_mode mode) +void rspamd_cryptobox_nm(rspamd_nm_t nm, + const rspamd_pk_t pk, const rspamd_sk_t sk) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_sign_keypair(pk, sk); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EC_KEY *ec_sec; - const BIGNUM *bn_sec; - const EC_POINT *ec_pub; - gsize len; - - ec_sec = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - g_assert(ec_sec != NULL); - g_assert(EC_KEY_generate_key(ec_sec) != 0); - - bn_sec = EC_KEY_get0_private_key(ec_sec); - g_assert(bn_sec != NULL); - ec_pub = EC_KEY_get0_public_key(ec_sec); - g_assert(ec_pub != NULL); - -#if OPENSSL_VERSION_MAJOR >= 3 - unsigned char *buf = NULL; /* Thanks openssl for this API (no) */ - len = EC_POINT_point2buf(EC_KEY_get0_group(ec_sec), ec_pub, - POINT_CONVERSION_UNCOMPRESSED, &buf, NULL); - g_assert(len <= (int) rspamd_cryptobox_pk_bytes(mode)); - memcpy(pk, buf, len); - OPENSSL_free(buf); -#else - BIGNUM *bn_pub; - bn_pub = EC_POINT_point2bn(EC_KEY_get0_group(ec_sec), - ec_pub, POINT_CONVERSION_UNCOMPRESSED, NULL, NULL); - len = BN_num_bytes(bn_pub); - g_assert(len <= (int) rspamd_cryptobox_pk_bytes(mode)); - BN_bn2bin(bn_pub, pk); - BN_free(bn_pub); -#endif + unsigned char s[32]; + unsigned char e[32]; - len = BN_num_bytes(bn_sec); - g_assert(len <= (int) sizeof(rspamd_sk_t)); - BN_bn2bin(bn_sec, sk); - EC_KEY_free(ec_sec); -#endif + memcpy(e, sk, 32); + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + + if (crypto_scalarmult(s, e, pk) != -1) { + hchacha(s, n0, nm, 20); } + + rspamd_explicit_memzero(e, 32); } -#if OPENSSL_VERSION_MAJOR >= 3 -/* Compatibility function for OpenSSL 3.0 - thanks for breaking all API one more time */ -EC_POINT *ec_point_bn2point_compat(const EC_GROUP *group, - const BIGNUM *bn, EC_POINT *point, BN_CTX *ctx) +void rspamd_cryptobox_sign(unsigned char *sig, unsigned long long *siglen_p, + const unsigned char *m, gsize mlen, + const rspamd_sig_sk_t sk) { - size_t buf_len = 0; - unsigned char *buf; - EC_POINT *ret; - - if ((buf_len = BN_num_bytes(bn)) == 0) - buf_len = 1; - if ((buf = OPENSSL_malloc(buf_len)) == NULL) { - return NULL; - } + crypto_sign_detached(sig, siglen_p, m, mlen, sk); +} - if (!BN_bn2binpad(bn, buf, buf_len)) { - OPENSSL_free(buf); - return NULL; - } +#ifdef HAVE_OPENSSL +bool rspamd_cryptobox_verify_evp_ed25519(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + struct evp_pkey_st *pub_key) +{ + bool ret = false; - if (point == NULL) { - if ((ret = EC_POINT_new(group)) == NULL) { - OPENSSL_free(buf); - return NULL; - } - } - else - ret = point; - - if (!EC_POINT_oct2point(group, ret, buf, buf_len, ctx)) { - if (ret != point) - EC_POINT_clear_free(ret); - OPENSSL_free(buf); - return NULL; + if (siglen == crypto_sign_bytes()) { + rspamd_pk_t pk; + size_t len_pk = sizeof(rspamd_pk_t); + EVP_PKEY_get_raw_public_key(pub_key, pk, &len_pk); + ret = (crypto_sign_verify_detached(sig, digest, dlen, pk) == 0); } - OPENSSL_free(buf); return ret; } -#else -#define ec_point_bn2point_compat EC_POINT_bn2point -#endif -void rspamd_cryptobox_nm(rspamd_nm_t nm, - const rspamd_pk_t pk, const rspamd_sk_t sk, - enum rspamd_cryptobox_mode mode) +bool rspamd_cryptobox_verify_evp_ecdsa(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + EVP_PKEY *pub_key) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - unsigned char s[32]; - unsigned char e[32]; + bool ret = false; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub_key, NULL); + g_assert(pctx != NULL); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + const EVP_MD *md = EVP_get_digestbynid(nid); - memcpy(e, sk, 32); - e[0] &= 248; - e[31] &= 127; - e[31] |= 64; + g_assert(EVP_PKEY_verify_init(pctx) == 1); + g_assert(EVP_PKEY_CTX_set_signature_md(pctx, md) == 1); - if (crypto_scalarmult(s, e, pk) != -1) { - hchacha(s, n0, nm, 20); - } + ret = (EVP_PKEY_verify(pctx, sig, siglen, digest, dlen) == 1); - rspamd_explicit_memzero(e, 32); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EC_KEY *lk; - EC_POINT *ec_pub; - BIGNUM *bn_pub, *bn_sec; - int len; - unsigned char s[32]; - - lk = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - g_assert(lk != NULL); - - bn_pub = BN_bin2bn(pk, rspamd_cryptobox_pk_bytes(mode), NULL); - g_assert(bn_pub != NULL); - bn_sec = BN_bin2bn(sk, sizeof(rspamd_sk_t), NULL); - g_assert(bn_sec != NULL); - - g_assert(EC_KEY_set_private_key(lk, bn_sec) == 1); - ec_pub = ec_point_bn2point_compat(EC_KEY_get0_group(lk), bn_pub, NULL, NULL); - g_assert(ec_pub != NULL); - len = ECDH_compute_key(s, sizeof(s), ec_pub, lk, NULL); - g_assert(len == sizeof(s)); - - /* Still do hchacha iteration since we are not using SHA1 KDF */ - hchacha(s, n0, nm, 20); + EVP_PKEY_CTX_free(pctx); + EVP_MD_CTX_free(mdctx); - EC_KEY_free(lk); - EC_POINT_free(ec_pub); - BN_free(bn_sec); - BN_free(bn_pub); -#endif - } + return ret; } - -void rspamd_cryptobox_sign(unsigned char *sig, unsigned long long *siglen_p, - const unsigned char *m, gsize mlen, - const rspamd_sk_t sk, - enum rspamd_cryptobox_mode mode) +bool rspamd_cryptobox_verify_evp_rsa(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + EVP_PKEY *pub_key) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_sign_detached(sig, siglen_p, m, mlen, sk); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EC_KEY *lk; - BIGNUM *bn_sec; - EVP_MD_CTX *sha_ctx; - unsigned char h[64]; - unsigned int diglen = rspamd_cryptobox_signature_bytes(mode); - - /* Prehash */ - sha_ctx = EVP_MD_CTX_create(); - g_assert(EVP_DigestInit(sha_ctx, EVP_sha512()) == 1); - EVP_DigestUpdate(sha_ctx, m, mlen); - EVP_DigestFinal(sha_ctx, h, NULL); - - /* Key setup */ - lk = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - g_assert(lk != NULL); - bn_sec = BN_bin2bn(sk, sizeof(rspamd_sk_t), NULL); - g_assert(bn_sec != NULL); - g_assert(EC_KEY_set_private_key(lk, bn_sec) == 1); - - /* ECDSA */ - g_assert(ECDSA_sign(0, h, sizeof(h), sig, &diglen, lk) == 1); - g_assert(diglen <= sizeof(rspamd_signature_t)); - - if (siglen_p) { - *siglen_p = diglen; - } + bool ret = false; - EC_KEY_free(lk); - EVP_MD_CTX_destroy(sha_ctx); - BN_free(bn_sec); -#endif - } + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub_key, NULL); + g_assert(pctx != NULL); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + const EVP_MD *md = EVP_get_digestbynid(nid); + + g_assert(EVP_PKEY_verify_init(pctx) == 1); + g_assert(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) == 1); + g_assert(EVP_PKEY_CTX_set_signature_md(pctx, md) == 1); + + ret = (EVP_PKEY_verify(pctx, sig, siglen, digest, dlen) == 1); + + EVP_PKEY_CTX_free(pctx); + EVP_MD_CTX_free(mdctx); + + return ret; } +#endif bool rspamd_cryptobox_verify(const unsigned char *sig, gsize siglen, const unsigned char *m, gsize mlen, - const rspamd_pk_t pk, - enum rspamd_cryptobox_mode mode) + const rspamd_sig_pk_t pk) { bool ret = false; - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - if (siglen == rspamd_cryptobox_signature_bytes(RSPAMD_CRYPTOBOX_MODE_25519)) { - ret = (crypto_sign_verify_detached(sig, m, mlen, pk) == 0); - } - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EC_KEY *lk; - EC_POINT *ec_pub; - BIGNUM *bn_pub; - EVP_MD_CTX *sha_ctx; - unsigned char h[64]; - - /* Prehash */ - sha_ctx = EVP_MD_CTX_create(); - g_assert(EVP_DigestInit(sha_ctx, EVP_sha512()) == 1); - EVP_DigestUpdate(sha_ctx, m, mlen); - EVP_DigestFinal(sha_ctx, h, NULL); - - /* Key setup */ - lk = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - g_assert(lk != NULL); - bn_pub = BN_bin2bn(pk, rspamd_cryptobox_pk_bytes(mode), NULL); - g_assert(bn_pub != NULL); - ec_pub = ec_point_bn2point_compat(EC_KEY_get0_group(lk), bn_pub, NULL, NULL); - g_assert(ec_pub != NULL); - g_assert(EC_KEY_set_public_key(lk, ec_pub) == 1); - - /* ECDSA */ - ret = ECDSA_verify(0, h, sizeof(h), sig, siglen, lk) == 1; - - EC_KEY_free(lk); - EVP_MD_CTX_destroy(sha_ctx); - BN_free(bn_pub); - EC_POINT_free(ec_pub); -#endif + if (siglen == crypto_sign_bytes()) { + ret = (crypto_sign_verify_detached(sig, m, mlen, pk) == 0); } return ret; } -static gsize -rspamd_cryptobox_encrypt_ctx_len(enum rspamd_cryptobox_mode mode) -{ - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return sizeof(chacha_state) + CRYPTOBOX_ALIGNMENT; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - return sizeof(EVP_CIPHER_CTX *) + CRYPTOBOX_ALIGNMENT; -#endif - } - - return 0; -} - -static gsize -rspamd_cryptobox_auth_ctx_len(enum rspamd_cryptobox_mode mode) -{ - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return sizeof(crypto_onetimeauth_state) + RSPAMD_ALIGNOF(crypto_onetimeauth_state); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - return sizeof(void *); -#endif - } - - return 0; -} - static void * rspamd_cryptobox_encrypt_init(void *enc_ctx, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, - enum rspamd_cryptobox_mode mode) + const rspamd_nm_t nm) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - chacha_state *s; + chacha_state *s; - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - xchacha_init(s, - (const chacha_key *) nm, - (const chacha_iv24 *) nonce, - 20); - - return s; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s; - - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - memset(s, 0, sizeof(*s)); - *s = EVP_CIPHER_CTX_new(); - g_assert(EVP_EncryptInit_ex(*s, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1); - g_assert(EVP_CIPHER_CTX_ctrl(*s, EVP_CTRL_GCM_SET_IVLEN, - rspamd_cryptobox_nonce_bytes(mode), NULL) == 1); - g_assert(EVP_EncryptInit_ex(*s, NULL, NULL, nm, nonce) == 1); - - return s; -#endif - } + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); + xchacha_init(s, + (const chacha_key *) nm, + (const chacha_iv24 *) nonce, + 20); - return NULL; + return s; } static void * -rspamd_cryptobox_auth_init(void *auth_ctx, void *enc_ctx, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_auth_init(void *auth_ctx, void *enc_ctx) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; - unsigned char RSPAMD_ALIGNED(32) subkey[CHACHA_BLOCKBYTES]; - - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - memset(subkey, 0, sizeof(subkey)); - chacha_update(enc_ctx, subkey, subkey, sizeof(subkey)); - crypto_onetimeauth_init(mac_ctx, subkey); - rspamd_explicit_memzero(subkey, sizeof(subkey)); - - return mac_ctx; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - auth_ctx = enc_ctx; + crypto_onetimeauth_state *mac_ctx; + unsigned char RSPAMD_ALIGNED(32) subkey[CHACHA_BLOCKBYTES]; - return auth_ctx; -#endif - } + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + memset(subkey, 0, sizeof(subkey)); + chacha_update(enc_ctx, subkey, subkey, sizeof(subkey)); + crypto_onetimeauth_init(mac_ctx, subkey); + rspamd_explicit_memzero(subkey, sizeof(subkey)); - return NULL; + return mac_ctx; } static gboolean rspamd_cryptobox_encrypt_update(void *enc_ctx, const unsigned char *in, gsize inlen, - unsigned char *out, gsize *outlen, - enum rspamd_cryptobox_mode mode) + unsigned char *out, gsize *outlen) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - gsize r; - chacha_state *s; - - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - - r = chacha_update(s, in, out, inlen); - - if (outlen != NULL) { - *outlen = r; - } - - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = enc_ctx; - int r; + gsize r; + chacha_state *s; - r = inlen; - g_assert(EVP_EncryptUpdate(*s, out, &r, in, inlen) == 1); + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - if (outlen) { - *outlen = r; - } + r = chacha_update(s, in, out, inlen); - return TRUE; -#endif + if (outlen != NULL) { + *outlen = r; } - return FALSE; + return TRUE; } static gboolean -rspamd_cryptobox_auth_update(void *auth_ctx, const unsigned char *in, gsize inlen, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_auth_update(void *auth_ctx, const unsigned char *in, gsize inlen) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; + crypto_onetimeauth_state *mac_ctx; - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - crypto_onetimeauth_update(mac_ctx, in, inlen); + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + crypto_onetimeauth_update(mac_ctx, in, inlen); - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - return TRUE; -#endif - } - - return FALSE; + return TRUE; } static gsize -rspamd_cryptobox_encrypt_final(void *enc_ctx, unsigned char *out, gsize remain, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_encrypt_final(void *enc_ctx, unsigned char *out, gsize remain) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - chacha_state *s; - - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - return chacha_final(s, out); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = enc_ctx; - int r = remain; + chacha_state *s; - g_assert(EVP_EncryptFinal_ex(*s, out, &r) == 1); - - return r; -#endif - } - - return 0; + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); + return chacha_final(s, out); } static gboolean -rspamd_cryptobox_auth_final(void *auth_ctx, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_auth_final(void *auth_ctx, rspamd_mac_t sig) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; - - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - crypto_onetimeauth_final(mac_ctx, sig); - - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = auth_ctx; - - g_assert(EVP_CIPHER_CTX_ctrl(*s, EVP_CTRL_GCM_GET_TAG, - sizeof(rspamd_mac_t), sig) == 1); + crypto_onetimeauth_state *mac_ctx; - return TRUE; -#endif - } + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + crypto_onetimeauth_final(mac_ctx, sig); - return FALSE; + return TRUE; } static void * rspamd_cryptobox_decrypt_init(void *enc_ctx, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, - enum rspamd_cryptobox_mode mode) + const rspamd_nm_t nm) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - - chacha_state *s; + chacha_state *s; - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - xchacha_init(s, - (const chacha_key *) nm, - (const chacha_iv24 *) nonce, - 20); + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); + xchacha_init(s, + (const chacha_key *) nm, + (const chacha_iv24 *) nonce, + 20); - return s; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s; - - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - memset(s, 0, sizeof(*s)); - *s = EVP_CIPHER_CTX_new(); - g_assert(EVP_DecryptInit_ex(*s, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1); - g_assert(EVP_CIPHER_CTX_ctrl(*s, EVP_CTRL_GCM_SET_IVLEN, - rspamd_cryptobox_nonce_bytes(mode), NULL) == 1); - g_assert(EVP_DecryptInit_ex(*s, NULL, NULL, nm, nonce) == 1); - - return s; -#endif - } - - return NULL; + return s; } static void * -rspamd_cryptobox_auth_verify_init(void *auth_ctx, void *enc_ctx, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_auth_verify_init(void *auth_ctx, void *enc_ctx) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; - unsigned char RSPAMD_ALIGNED(32) subkey[CHACHA_BLOCKBYTES]; + crypto_onetimeauth_state *mac_ctx; + unsigned char RSPAMD_ALIGNED(32) subkey[CHACHA_BLOCKBYTES]; - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - memset(subkey, 0, sizeof(subkey)); - chacha_update(enc_ctx, subkey, subkey, sizeof(subkey)); - crypto_onetimeauth_init(mac_ctx, subkey); - rspamd_explicit_memzero(subkey, sizeof(subkey)); + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + memset(subkey, 0, sizeof(subkey)); + chacha_update(enc_ctx, subkey, subkey, sizeof(subkey)); + crypto_onetimeauth_init(mac_ctx, subkey); + rspamd_explicit_memzero(subkey, sizeof(subkey)); - return mac_ctx; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - auth_ctx = enc_ctx; - - return auth_ctx; -#endif - } - - return NULL; + return mac_ctx; } static gboolean rspamd_cryptobox_decrypt_update(void *enc_ctx, const unsigned char *in, gsize inlen, - unsigned char *out, gsize *outlen, - enum rspamd_cryptobox_mode mode) + unsigned char *out, gsize *outlen) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - gsize r; - chacha_state *s; - - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - r = chacha_update(s, in, out, inlen); + gsize r; + chacha_state *s; - if (outlen != NULL) { - *outlen = r; - } + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); + r = chacha_update(s, in, out, inlen); - return TRUE; + if (outlen != NULL) { + *outlen = r; } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = enc_ctx; - int r; - - r = outlen ? *outlen : inlen; - g_assert(EVP_DecryptUpdate(*s, out, &r, in, inlen) == 1); - if (outlen) { - *outlen = r; - } - - return TRUE; -#endif - } + return TRUE; } static gboolean rspamd_cryptobox_auth_verify_update(void *auth_ctx, - const unsigned char *in, gsize inlen, - enum rspamd_cryptobox_mode mode) + const unsigned char *in, gsize inlen) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; - - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - crypto_onetimeauth_update(mac_ctx, in, inlen); + crypto_onetimeauth_state *mac_ctx; - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - /* We do not need to authenticate as a separate process */ - return TRUE; -#else -#endif - } + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + crypto_onetimeauth_update(mac_ctx, in, inlen); - return FALSE; + return TRUE; } static gboolean -rspamd_cryptobox_decrypt_final(void *enc_ctx, unsigned char *out, gsize remain, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_decrypt_final(void *enc_ctx, unsigned char *out, gsize remain) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - chacha_state *s; + chacha_state *s; - s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); - chacha_final(s, out); + s = cryptobox_align_ptr(enc_ctx, CRYPTOBOX_ALIGNMENT); + chacha_final(s, out); - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = enc_ctx; - int r = remain; - - if (EVP_DecryptFinal_ex(*s, out, &r) < 0) { - return FALSE; - } - - return TRUE; -#endif - } - - return FALSE; + return TRUE; } static gboolean -rspamd_cryptobox_auth_verify_final(void *auth_ctx, const rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_auth_verify_final(void *auth_ctx, const rspamd_mac_t sig) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - rspamd_mac_t mac; - crypto_onetimeauth_state *mac_ctx; + rspamd_mac_t mac; + crypto_onetimeauth_state *mac_ctx; - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - crypto_onetimeauth_final(mac_ctx, mac); - - if (crypto_verify_16(mac, sig) != 0) { - return FALSE; - } + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + crypto_onetimeauth_final(mac_ctx, mac); - return TRUE; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = auth_ctx; - - if (EVP_CIPHER_CTX_ctrl(*s, EVP_CTRL_GCM_SET_TAG, 16, (unsigned char *) sig) != 1) { - return FALSE; - } - - return TRUE; -#endif + if (crypto_verify_16(mac, sig) != 0) { + return FALSE; } - return FALSE; + return TRUE; } static void -rspamd_cryptobox_cleanup(void *enc_ctx, void *auth_ctx, - enum rspamd_cryptobox_mode mode) +rspamd_cryptobox_cleanup(void *enc_ctx, void *auth_ctx) { - if (G_LIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - crypto_onetimeauth_state *mac_ctx; - - mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); - rspamd_explicit_memzero(mac_ctx, sizeof(*mac_ctx)); - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - EVP_CIPHER_CTX **s = enc_ctx; + crypto_onetimeauth_state *mac_ctx; - EVP_CIPHER_CTX_cleanup(*s); - EVP_CIPHER_CTX_free(*s); -#endif - } + mac_ctx = cryptobox_align_ptr(auth_ctx, CRYPTOBOX_ALIGNMENT); + rspamd_explicit_memzero(mac_ctx, sizeof(*mac_ctx)); } void rspamd_cryptobox_encrypt_nm_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, const rspamd_nm_t nm, - rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) + rspamd_mac_t sig) { gsize r; void *enc_ctx, *auth_ctx; - enc_ctx = g_alloca(rspamd_cryptobox_encrypt_ctx_len(mode)); - auth_ctx = g_alloca(rspamd_cryptobox_auth_ctx_len(mode)); + enc_ctx = g_alloca(sizeof(chacha_state) + CRYPTOBOX_ALIGNMENT); + auth_ctx = g_alloca(sizeof(crypto_onetimeauth_state) + RSPAMD_ALIGNOF(crypto_onetimeauth_state)); - enc_ctx = rspamd_cryptobox_encrypt_init(enc_ctx, nonce, nm, mode); - auth_ctx = rspamd_cryptobox_auth_init(auth_ctx, enc_ctx, mode); + enc_ctx = rspamd_cryptobox_encrypt_init(enc_ctx, nonce, nm); + auth_ctx = rspamd_cryptobox_auth_init(auth_ctx, enc_ctx); - rspamd_cryptobox_encrypt_update(enc_ctx, data, len, data, &r, mode); - rspamd_cryptobox_encrypt_final(enc_ctx, data + r, len - r, mode); + rspamd_cryptobox_encrypt_update(enc_ctx, data, len, data, &r); + rspamd_cryptobox_encrypt_final(enc_ctx, data + r, len - r); - rspamd_cryptobox_auth_update(auth_ctx, data, len, mode); - rspamd_cryptobox_auth_final(auth_ctx, sig, mode); + rspamd_cryptobox_auth_update(auth_ctx, data, len); + rspamd_cryptobox_auth_final(auth_ctx, sig); - rspamd_cryptobox_cleanup(enc_ctx, auth_ctx, mode); + rspamd_cryptobox_cleanup(enc_ctx, auth_ctx); } static void @@ -1098,8 +655,7 @@ rspamd_cryptobox_flush_outbuf(struct rspamd_cryptobox_segment *st, void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segments, gsize cnt, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) + const rspamd_nm_t nm, rspamd_mac_t sig) { struct rspamd_cryptobox_segment *cur = segments, *start_seg = segments; unsigned char outbuf[CHACHA_BLOCKBYTES * 16]; @@ -1107,11 +663,11 @@ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segme unsigned char *out, *in; gsize r, remain, inremain, seg_offset; - enc_ctx = g_alloca(rspamd_cryptobox_encrypt_ctx_len(mode)); - auth_ctx = g_alloca(rspamd_cryptobox_auth_ctx_len(mode)); + enc_ctx = g_alloca(sizeof(chacha_state) + CRYPTOBOX_ALIGNMENT); + auth_ctx = g_alloca(sizeof(crypto_onetimeauth_state) + RSPAMD_ALIGNOF(crypto_onetimeauth_state)); - enc_ctx = rspamd_cryptobox_encrypt_init(enc_ctx, nonce, nm, mode); - auth_ctx = rspamd_cryptobox_auth_init(auth_ctx, enc_ctx, mode); + enc_ctx = rspamd_cryptobox_encrypt_init(enc_ctx, nonce, nm); + auth_ctx = rspamd_cryptobox_auth_init(auth_ctx, enc_ctx); remain = sizeof(outbuf); out = outbuf; @@ -1131,9 +687,8 @@ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segme if (remain == 0) { rspamd_cryptobox_encrypt_update(enc_ctx, outbuf, sizeof(outbuf), - outbuf, NULL, mode); - rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf), - mode); + outbuf, NULL); + rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf)); rspamd_cryptobox_flush_outbuf(start_seg, outbuf, sizeof(outbuf), seg_offset); start_seg = cur; @@ -1145,9 +700,8 @@ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segme else { memcpy(out, cur->data, remain); rspamd_cryptobox_encrypt_update(enc_ctx, outbuf, sizeof(outbuf), - outbuf, NULL, mode); - rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf), - mode); + outbuf, NULL); + rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf)); rspamd_cryptobox_flush_outbuf(start_seg, outbuf, sizeof(outbuf), seg_offset); seg_offset = 0; @@ -1165,12 +719,10 @@ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segme outbuf, sizeof(outbuf), outbuf, - NULL, - mode); + NULL); rspamd_cryptobox_auth_update(auth_ctx, outbuf, - sizeof(outbuf), - mode); + sizeof(outbuf)); memcpy(in, outbuf, sizeof(outbuf)); in += sizeof(outbuf); inremain -= sizeof(outbuf); @@ -1190,46 +742,44 @@ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segme } rspamd_cryptobox_encrypt_update(enc_ctx, outbuf, sizeof(outbuf) - remain, - outbuf, &r, mode); + outbuf, &r); out = outbuf + r; - rspamd_cryptobox_encrypt_final(enc_ctx, out, sizeof(outbuf) - remain - r, - mode); + rspamd_cryptobox_encrypt_final(enc_ctx, out, sizeof(outbuf) - remain - r); - rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf) - remain, - mode); - rspamd_cryptobox_auth_final(auth_ctx, sig, mode); + rspamd_cryptobox_auth_update(auth_ctx, outbuf, sizeof(outbuf) - remain); + rspamd_cryptobox_auth_final(auth_ctx, sig); rspamd_cryptobox_flush_outbuf(start_seg, outbuf, sizeof(outbuf) - remain, seg_offset); - rspamd_cryptobox_cleanup(enc_ctx, auth_ctx, mode); + rspamd_cryptobox_cleanup(enc_ctx, auth_ctx); } gboolean rspamd_cryptobox_decrypt_nm_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, const rspamd_nm_t nm, - const rspamd_mac_t sig, enum rspamd_cryptobox_mode mode) + const rspamd_mac_t sig) { gsize r = 0; gboolean ret = TRUE; void *enc_ctx, *auth_ctx; - enc_ctx = g_alloca(rspamd_cryptobox_encrypt_ctx_len(mode)); - auth_ctx = g_alloca(rspamd_cryptobox_auth_ctx_len(mode)); + enc_ctx = g_alloca(sizeof(chacha_state) + CRYPTOBOX_ALIGNMENT); + auth_ctx = g_alloca(sizeof(crypto_onetimeauth_state) + RSPAMD_ALIGNOF(crypto_onetimeauth_state)); - enc_ctx = rspamd_cryptobox_decrypt_init(enc_ctx, nonce, nm, mode); - auth_ctx = rspamd_cryptobox_auth_verify_init(auth_ctx, enc_ctx, mode); + enc_ctx = rspamd_cryptobox_decrypt_init(enc_ctx, nonce, nm); + auth_ctx = rspamd_cryptobox_auth_verify_init(auth_ctx, enc_ctx); - rspamd_cryptobox_auth_verify_update(auth_ctx, data, len, mode); + rspamd_cryptobox_auth_verify_update(auth_ctx, data, len); - if (!rspamd_cryptobox_auth_verify_final(auth_ctx, sig, mode)) { + if (!rspamd_cryptobox_auth_verify_final(auth_ctx, sig)) { ret = FALSE; } else { - rspamd_cryptobox_decrypt_update(enc_ctx, data, len, data, &r, mode); - ret = rspamd_cryptobox_decrypt_final(enc_ctx, data + r, len - r, mode); + rspamd_cryptobox_decrypt_update(enc_ctx, data, len, data, &r); + ret = rspamd_cryptobox_decrypt_final(enc_ctx, data + r, len - r); } - rspamd_cryptobox_cleanup(enc_ctx, auth_ctx, mode); + rspamd_cryptobox_cleanup(enc_ctx, auth_ctx); return ret; } @@ -1238,14 +788,13 @@ gboolean rspamd_cryptobox_decrypt_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, const rspamd_pk_t pk, const rspamd_sk_t sk, - const rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) + const rspamd_mac_t sig) { unsigned char nm[rspamd_cryptobox_MAX_NMBYTES]; gboolean ret; - rspamd_cryptobox_nm(nm, pk, sk, mode); - ret = rspamd_cryptobox_decrypt_nm_inplace(data, len, nonce, nm, sig, mode); + rspamd_cryptobox_nm(nm, pk, sk); + ret = rspamd_cryptobox_decrypt_nm_inplace(data, len, nonce, nm, sig); rspamd_explicit_memzero(nm, sizeof(nm)); @@ -1255,13 +804,12 @@ rspamd_cryptobox_decrypt_inplace(unsigned char *data, gsize len, void rspamd_cryptobox_encrypt_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, const rspamd_pk_t pk, const rspamd_sk_t sk, - rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) + rspamd_mac_t sig) { unsigned char nm[rspamd_cryptobox_MAX_NMBYTES]; - rspamd_cryptobox_nm(nm, pk, sk, mode); - rspamd_cryptobox_encrypt_nm_inplace(data, len, nonce, nm, sig, mode); + rspamd_cryptobox_nm(nm, pk, sk); + rspamd_cryptobox_encrypt_nm_inplace(data, len, nonce, nm, sig); rspamd_explicit_memzero(nm, sizeof(nm)); } @@ -1269,13 +817,12 @@ void rspamd_cryptobox_encryptv_inplace(struct rspamd_cryptobox_segment *segments gsize cnt, const rspamd_nonce_t nonce, const rspamd_pk_t pk, const rspamd_sk_t sk, - rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode) + rspamd_mac_t sig) { unsigned char nm[rspamd_cryptobox_MAX_NMBYTES]; - rspamd_cryptobox_nm(nm, pk, sk, mode); - rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, nonce, nm, sig, mode); + rspamd_cryptobox_nm(nm, pk, sk); + rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, nonce, nm, sig); rspamd_explicit_memzero(nm, sizeof(nm)); } @@ -1288,9 +835,9 @@ void rspamd_cryptobox_siphash(unsigned char *out, const unsigned char *in, } /* - * Password-Based Key Derivation Function 2 (PKCS #5 v2.0). - * Code based on IEEE Std 802.11-2007, Annex H.4.2. - */ +* Password-Based Key Derivation Function 2 (PKCS #5 v2.0). +* Code based on IEEE Std 802.11-2007, Annex H.4.2. +*/ static gboolean rspamd_cryptobox_pbkdf2(const char *pass, gsize pass_len, const uint8_t *salt, gsize salt_len, uint8_t *key, gsize key_len, @@ -1327,9 +874,9 @@ rspamd_cryptobox_pbkdf2(const char *pass, gsize pass_len, uint8_t k[crypto_generichash_blake2b_BYTES_MAX]; /* - * We use additional blake2 iteration to store large key - * XXX: it is not compatible with the original implementation but safe - */ + * We use additional blake2 iteration to store large key + * XXX: it is not compatible with the original implementation but safe + */ crypto_generichash_blake2b(k, sizeof(k), pass, pass_len, NULL, 0); crypto_generichash_blake2b(d1, sizeof(d1), asalt, salt_len + 4, @@ -1347,9 +894,9 @@ rspamd_cryptobox_pbkdf2(const char *pass, gsize pass_len, uint8_t k[crypto_generichash_blake2b_BYTES_MAX]; /* - * We use additional blake2 iteration to store large key - * XXX: it is not compatible with the original implementation but safe - */ + * We use additional blake2 iteration to store large key + * XXX: it is not compatible with the original implementation but safe + */ crypto_generichash_blake2b(k, sizeof(k), pass, pass_len, NULL, 0); crypto_generichash_blake2b(d2, sizeof(d2), d1, sizeof(d1), @@ -1402,84 +949,6 @@ rspamd_cryptobox_pbkdf(const char *pass, gsize pass_len, return ret; } -unsigned int rspamd_cryptobox_pk_bytes(enum rspamd_cryptobox_mode mode) -{ - if (G_UNLIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return 32; - } - else { - return 65; - } -} - -unsigned int rspamd_cryptobox_pk_sig_bytes(enum rspamd_cryptobox_mode mode) -{ - if (G_UNLIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return 32; - } - else { - return 65; - } -} - -unsigned int rspamd_cryptobox_nonce_bytes(enum rspamd_cryptobox_mode mode) -{ - if (G_UNLIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return 24; - } - else { - return 16; - } -} - - -unsigned int rspamd_cryptobox_sk_bytes(enum rspamd_cryptobox_mode mode) -{ - return 32; -} - -unsigned int rspamd_cryptobox_sk_sig_bytes(enum rspamd_cryptobox_mode mode) -{ - if (G_UNLIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return 64; - } - else { - return 32; - } -} - -unsigned int rspamd_cryptobox_signature_bytes(enum rspamd_cryptobox_mode mode) -{ - static unsigned int ssl_keylen; - - if (G_UNLIKELY(mode == RSPAMD_CRYPTOBOX_MODE_25519)) { - return 64; - } - else { -#ifndef HAVE_USABLE_OPENSSL - g_assert(0); -#else - if (ssl_keylen == 0) { - EC_KEY *lk; - lk = EC_KEY_new_by_curve_name(CRYPTOBOX_CURVE_NID); - ssl_keylen = ECDSA_size(lk); - EC_KEY_free(lk); - } -#endif - return ssl_keylen; - } -} - -unsigned int rspamd_cryptobox_nm_bytes(enum rspamd_cryptobox_mode mode) -{ - return 32; -} - -unsigned int rspamd_cryptobox_mac_bytes(enum rspamd_cryptobox_mode mode) -{ - return 16; -} - void rspamd_cryptobox_hash_init(rspamd_cryptobox_hash_state_t *p, const unsigned char *key, gsize keylen) { crypto_generichash_blake2b_state *st = cryptobox_align_ptr(p, @@ -1489,8 +958,8 @@ void rspamd_cryptobox_hash_init(rspamd_cryptobox_hash_state_t *p, const unsigned } /** - * Update hash with data portion - */ +* Update hash with data portion +*/ void rspamd_cryptobox_hash_update(rspamd_cryptobox_hash_state_t *p, const unsigned char *data, gsize len) { crypto_generichash_blake2b_state *st = cryptobox_align_ptr(p, @@ -1499,8 +968,8 @@ void rspamd_cryptobox_hash_update(rspamd_cryptobox_hash_state_t *p, const unsign } /** - * Output hash to the buffer of rspamd_cryptobox_HASHBYTES length - */ +* Output hash to the buffer of rspamd_cryptobox_HASHBYTES length +*/ void rspamd_cryptobox_hash_final(rspamd_cryptobox_hash_state_t *p, unsigned char *out) { crypto_generichash_blake2b_state *st = cryptobox_align_ptr(p, @@ -1509,8 +978,8 @@ void rspamd_cryptobox_hash_final(rspamd_cryptobox_hash_state_t *p, unsigned char } /** - * One in all function - */ +* One in all function +*/ void rspamd_cryptobox_hash(unsigned char *out, const unsigned char *data, gsize len, @@ -1729,8 +1198,8 @@ rspamd_cryptobox_fast_hash_final(rspamd_cryptobox_fast_hash_state_t *st) } /** - * One in all function - */ +* One in all function +*/ static inline uint64_t rspamd_cryptobox_fast_hash_machdep(const void *data, gsize len, uint64_t seed) diff --git a/src/libcryptobox/cryptobox.h b/src/libcryptobox/cryptobox.h index 8382c8f68..afe9c4f9a 100644 --- a/src/libcryptobox/cryptobox.h +++ b/src/libcryptobox/cryptobox.h @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #ifndef CRYPTOBOX_H_ #define CRYPTOBOX_H_ #include "config.h" +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#endif + #include <sodium.h> #ifdef __cplusplus @@ -35,18 +40,18 @@ struct rspamd_cryptobox_segment { #define RSPAMD_HAS_TARGET_ATTR 1 #endif -#define rspamd_cryptobox_MAX_NONCEBYTES 24 -#define rspamd_cryptobox_MAX_PKBYTES 65 -#define rspamd_cryptobox_MAX_SKBYTES 32 -#define rspamd_cryptobox_MAX_MACBYTES 16 -#define rspamd_cryptobox_MAX_NMBYTES 32 +#define rspamd_cryptobox_MAX_NONCEBYTES crypto_box_NONCEBYTES +#define rspamd_cryptobox_MAX_PKBYTES crypto_box_PUBLICKEYBYTES +#define rspamd_cryptobox_MAX_SKBYTES crypto_box_SECRETKEYBYTES +#define rspamd_cryptobox_MAX_MACBYTES crypto_box_MACBYTES +#define rspamd_cryptobox_MAX_NMBYTES crypto_box_BEFORENMBYTES #define rspamd_cryptobox_SIPKEYBYTES 16 #define rspamd_cryptobox_HASHBYTES 64 #define rspamd_cryptobox_HASHKEYBYTES 64 #define rspamd_cryptobox_HASHSTATEBYTES sizeof(crypto_generichash_blake2b_state) + 64 -#define rspamd_cryptobox_MAX_SIGSKBYTES 64 -#define rspamd_cryptobox_MAX_SIGPKBYTES 32 -#define rspamd_cryptobox_MAX_SIGBYTES 72 +#define rspamd_cryptobox_MAX_SIGSKBYTES crypto_sign_SECRETKEYBYTES +#define rspamd_cryptobox_MAX_SIGPKBYTES crypto_sign_PUBLICKEYBYTES +#define rspamd_cryptobox_MAX_SIGBYTES crypto_sign_BYTES #define CPUID_AVX2 0x1 #define CPUID_AVX 0x2 @@ -67,10 +72,6 @@ typedef unsigned char rspamd_signature_t[rspamd_cryptobox_MAX_SIGBYTES]; typedef unsigned char rspamd_sig_pk_t[rspamd_cryptobox_MAX_SIGPKBYTES]; typedef unsigned char rspamd_sig_sk_t[rspamd_cryptobox_MAX_SIGSKBYTES]; -enum rspamd_cryptobox_mode { - RSPAMD_CRYPTOBOX_MODE_25519 = 0, - RSPAMD_CRYPTOBOX_MODE_NIST -}; struct rspamd_cryptobox_library_ctx { char *cpu_extensions; @@ -80,172 +81,190 @@ struct rspamd_cryptobox_library_ctx { }; /** - * Init cryptobox library - */ +* Init cryptobox library +*/ struct rspamd_cryptobox_library_ctx *rspamd_cryptobox_init(void); void rspamd_cryptobox_deinit(struct rspamd_cryptobox_library_ctx *); /** - * Generate new keypair - * @param pk public key buffer - * @param sk secret key buffer - */ -void rspamd_cryptobox_keypair(rspamd_pk_t pk, rspamd_sk_t sk, - enum rspamd_cryptobox_mode mode); +* Generate new keypair +* @param pk public key buffer +* @param sk secret key buffer +*/ +void rspamd_cryptobox_keypair(rspamd_pk_t pk, rspamd_sk_t sk); /** - * Generate new keypair for signing - * @param pk public key buffer - * @param sk secret key buffer - */ -void rspamd_cryptobox_keypair_sig(rspamd_sig_pk_t pk, rspamd_sig_sk_t sk, - enum rspamd_cryptobox_mode mode); +* Generate new keypair for signing +* @param pk public key buffer +* @param sk secret key buffer +*/ +void rspamd_cryptobox_keypair_sig(rspamd_sig_pk_t pk, rspamd_sig_sk_t sk); /** - * Encrypt data inplace adding signature to sig afterwards - * @param data input buffer - * @param pk remote pubkey - * @param sk local secret key - * @param sig output signature - */ +* Encrypt data inplace adding signature to sig afterwards +* @param data input buffer +* @param pk remote pubkey +* @param sk local secret key +* @param sig output signature +*/ void rspamd_cryptobox_encrypt_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, - const rspamd_pk_t pk, const rspamd_sk_t sk, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_pk_t pk, const rspamd_sk_t sk, rspamd_mac_t sig); /** - * Encrypt segments of data inplace adding signature to sig afterwards - * @param segments segments of data - * @param cnt count of segments - * @param pk remote pubkey - * @param sk local secret key - * @param sig output signature - */ +* Encrypt segments of data inplace adding signature to sig afterwards +* @param segments segments of data +* @param cnt count of segments +* @param pk remote pubkey +* @param sk local secret key +* @param sig output signature +*/ void rspamd_cryptobox_encryptv_inplace(struct rspamd_cryptobox_segment *segments, gsize cnt, const rspamd_nonce_t nonce, - const rspamd_pk_t pk, const rspamd_sk_t sk, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_pk_t pk, const rspamd_sk_t sk, rspamd_mac_t sig); /** - * Decrypt and verify data chunk inplace - * @param data data to decrypt - * @param len length of data - * @param pk remote pubkey - * @param sk local privkey - * @param sig signature input - * @return TRUE if input has been verified successfully - */ +* Decrypt and verify data chunk inplace +* @param data data to decrypt +* @param len length of data +* @param pk remote pubkey +* @param sk local privkey +* @param sig signature input +* @return TRUE if input has been verified successfully +*/ gboolean rspamd_cryptobox_decrypt_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, - const rspamd_pk_t pk, const rspamd_sk_t sk, const rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_pk_t pk, const rspamd_sk_t sk, const rspamd_mac_t sig); /** - * Encrypt segments of data inplace adding signature to sig afterwards - * @param segments segments of data - * @param cnt count of segments - * @param pk remote pubkey - * @param sk local secret key - * @param sig output signature - */ +* Encrypt segments of data inplace adding signature to sig afterwards +* @param segments segments of data +* @param cnt count of segments +* @param pk remote pubkey +* @param sk local secret key +* @param sig output signature +*/ void rspamd_cryptobox_encrypt_nm_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_nm_t nm, rspamd_mac_t sig); /** - * Encrypt segments of data inplace adding signature to sig afterwards - * @param segments segments of data - * @param cnt count of segments - * @param pk remote pubkey - * @param sk local secret key - * @param sig output signature - */ +* Encrypt segments of data inplace adding signature to sig afterwards +* @param segments segments of data +* @param cnt count of segments +* @param pk remote pubkey +* @param sk local secret key +* @param sig output signature +*/ void rspamd_cryptobox_encryptv_nm_inplace(struct rspamd_cryptobox_segment *segments, gsize cnt, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_nm_t nm, rspamd_mac_t sig); /** - * Decrypt and verify data chunk inplace - * @param data data to decrypt - * @param len length of data - * @param pk remote pubkey - * @param sk local privkey - * @param sig signature input - * @return TRUE if input has been verified successfully - */ +* Decrypt and verify data chunk inplace +* @param data data to decrypt +* @param len length of data +* @param pk remote pubkey +* @param sk local privkey +* @param sig signature input +* @return TRUE if input has been verified successfully +*/ gboolean rspamd_cryptobox_decrypt_nm_inplace(unsigned char *data, gsize len, const rspamd_nonce_t nonce, - const rspamd_nm_t nm, const rspamd_mac_t sig, - enum rspamd_cryptobox_mode mode); + const rspamd_nm_t nm, const rspamd_mac_t sig); /** - * Generate shared secret from local sk and remote pk - * @param nm shared secret - * @param pk remote pubkey - * @param sk local privkey - */ -void rspamd_cryptobox_nm(rspamd_nm_t nm, const rspamd_pk_t pk, - const rspamd_sk_t sk, enum rspamd_cryptobox_mode mode); +* Generate shared secret from local sk and remote pk +* @param nm shared secret +* @param pk remote pubkey +* @param sk local privkey +*/ +void rspamd_cryptobox_nm(rspamd_nm_t nm, const rspamd_pk_t pk, const rspamd_sk_t sk); /** - * Create digital signature for the specified message and place result in `sig` - * @param sig signature target - * @param siglen_p pointer to signature length (might be NULL) - * @param m input message - * @param mlen input length - * @param sk secret key - */ +* Create digital signature for the specified message and place result in `sig` +* @param sig signature target +* @param siglen_p pointer to signature length (might be NULL) +* @param m input message +* @param mlen input length +* @param sk secret key +*/ void rspamd_cryptobox_sign(unsigned char *sig, unsigned long long *siglen_p, const unsigned char *m, gsize mlen, - const rspamd_sk_t sk, - enum rspamd_cryptobox_mode mode); + const rspamd_sig_sk_t sk); /** - * Verifies digital signature for the specified message using the specified - * pubkey - * @param sig signature source - * @param m input message - * @param mlen message length - * @param pk public key for verification - * @return true if signature is valid, false otherwise - */ +* Verifies digital signature for the specified message using the specified +* pubkey +* @param sig signature source +* @param m input message +* @param mlen message length +* @param pk public key for verification +* @return true if signature is valid, false otherwise +*/ bool rspamd_cryptobox_verify(const unsigned char *sig, gsize siglen, const unsigned char *m, gsize mlen, - const rspamd_pk_t pk, - enum rspamd_cryptobox_mode mode); + const rspamd_sig_pk_t pk); +#ifdef HAVE_OPENSSL /** - * Securely clear the buffer specified - * @param buf buffer to zero - * @param buflen length of buffer + * Verifies digital signature for specified raw digest with specified pubkey + * @param nid signing algorithm nid + * @param sig signature source + * @param digest raw digest + * @param pub_key public key for verification + * @return true if signature is valid, false otherwise */ +bool rspamd_cryptobox_verify_evp_ed25519(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + EVP_PKEY *pub_key); +bool rspamd_cryptobox_verify_evp_ecdsa(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + EVP_PKEY *pub_key); +bool rspamd_cryptobox_verify_evp_rsa(int nid, + const unsigned char *sig, + gsize siglen, + const unsigned char *digest, + gsize dlen, + EVP_PKEY *pub_key); +#endif + +/** +* Securely clear the buffer specified +* @param buf buffer to zero +* @param buflen length of buffer +*/ #define rspamd_explicit_memzero sodium_memzero /** - * Constant time memcmp - * @param b1_ - * @param b2_ - * @param len - * @return - */ +* Constant time memcmp +* @param b1_ +* @param b2_ +* @param len +* @return +*/ #define rspamd_cryptobox_memcmp sodium_memcmp /** - * Calculates siphash-2-4 for a message - * @param out (8 bytes output) - * @param in - * @param inlen - * @param k key (must be 16 bytes) - */ +* Calculates siphash-2-4 for a message +* @param out (8 bytes output) +* @param in +* @param inlen +* @param k key (must be 16 bytes) +*/ void rspamd_cryptobox_siphash(unsigned char *out, const unsigned char *in, unsigned long long inlen, const rspamd_sipkey_t k); @@ -257,16 +276,16 @@ enum rspamd_cryptobox_pbkdf_type { /** - * Derive key from password using the specified algorithm - * @param pass input password - * @param pass_len length of the password - * @param salt input salt - * @param salt_len length of salt - * @param key output key - * @param key_len size of the key - * @param complexity empiric number of complexity (rounds for pbkdf2 and garlic for catena) - * @return TRUE in case of success and FALSE if failed - */ +* Derive key from password using the specified algorithm +* @param pass input password +* @param pass_len length of the password +* @param salt input salt +* @param salt_len length of salt +* @param key output key +* @param key_len size of the key +* @param complexity empiric number of complexity (rounds for pbkdf2 and garlic for catena) +* @return TRUE in case of success and FALSE if failed +*/ gboolean rspamd_cryptobox_pbkdf(const char *pass, gsize pass_len, const uint8_t *salt, gsize salt_len, uint8_t *key, gsize key_len, @@ -274,71 +293,31 @@ gboolean rspamd_cryptobox_pbkdf(const char *pass, gsize pass_len, enum rspamd_cryptobox_pbkdf_type type); -/** - * Real size of rspamd cryptobox public key - */ -unsigned int rspamd_cryptobox_pk_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox signing public key - */ -unsigned int rspamd_cryptobox_pk_sig_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of crypto nonce - */ -unsigned int rspamd_cryptobox_nonce_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox secret key - */ -unsigned int rspamd_cryptobox_sk_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox signing secret key - */ -unsigned int rspamd_cryptobox_sk_sig_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox shared key - */ -unsigned int rspamd_cryptobox_nm_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox MAC signature - */ -unsigned int rspamd_cryptobox_mac_bytes(enum rspamd_cryptobox_mode mode); - -/** - * Real size of rspamd cryptobox digital signature - */ -unsigned int rspamd_cryptobox_signature_bytes(enum rspamd_cryptobox_mode mode); - /* Hash IUF interface */ typedef crypto_generichash_blake2b_state rspamd_cryptobox_hash_state_t; /** - * Init cryptobox hash state using key if needed, `st` must point to the buffer - * with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then - * non-keyed hash is generated - */ +* Init cryptobox hash state using key if needed, `st` must point to the buffer +* with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then +* non-keyed hash is generated +*/ void rspamd_cryptobox_hash_init(rspamd_cryptobox_hash_state_t *st, const unsigned char *key, gsize keylen); /** - * Update hash with data portion - */ +* Update hash with data portion +*/ void rspamd_cryptobox_hash_update(rspamd_cryptobox_hash_state_t *st, const unsigned char *data, gsize len); /** - * Output hash to the buffer of rspamd_cryptobox_HASHBYTES length - */ +* Output hash to the buffer of rspamd_cryptobox_HASHBYTES length +*/ void rspamd_cryptobox_hash_final(rspamd_cryptobox_hash_state_t *st, unsigned char *out); /** - * One in all function - */ +* One in all function +*/ void rspamd_cryptobox_hash(unsigned char *out, const unsigned char *data, gsize len, @@ -363,71 +342,71 @@ typedef struct CRYPTO_ALIGN(64) rspamd_cryptobox_fast_hash_state_s { /** - * Creates a new cryptobox state properly aligned - * @return - */ +* Creates a new cryptobox state properly aligned +* @return +*/ rspamd_cryptobox_fast_hash_state_t *rspamd_cryptobox_fast_hash_new(void); void rspamd_cryptobox_fast_hash_free(rspamd_cryptobox_fast_hash_state_t *st); /** - * Init cryptobox hash state using key if needed, `st` must point to the buffer - * with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then - * non-keyed hash is generated - */ +* Init cryptobox hash state using key if needed, `st` must point to the buffer +* with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then +* non-keyed hash is generated +*/ void rspamd_cryptobox_fast_hash_init(rspamd_cryptobox_fast_hash_state_t *st, uint64_t seed); /** - * Init cryptobox hash state using key if needed, `st` must point to the buffer - * with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then - * non-keyed hash is generated - */ +* Init cryptobox hash state using key if needed, `st` must point to the buffer +* with at least rspamd_cryptobox_HASHSTATEBYTES bytes length. If keylen == 0, then +* non-keyed hash is generated +*/ void rspamd_cryptobox_fast_hash_init_specific(rspamd_cryptobox_fast_hash_state_t *st, enum rspamd_cryptobox_fast_hash_type type, uint64_t seed); /** - * Update hash with data portion - */ +* Update hash with data portion +*/ void rspamd_cryptobox_fast_hash_update(rspamd_cryptobox_fast_hash_state_t *st, const void *data, gsize len); /** - * Output hash to the buffer of rspamd_cryptobox_HASHBYTES length - */ +* Output hash to the buffer of rspamd_cryptobox_HASHBYTES length +*/ uint64_t rspamd_cryptobox_fast_hash_final(rspamd_cryptobox_fast_hash_state_t *st); /** - * One in all function - */ +* One in all function +*/ uint64_t rspamd_cryptobox_fast_hash(const void *data, gsize len, uint64_t seed); /** - * Platform independent version - */ +* Platform independent version +*/ uint64_t rspamd_cryptobox_fast_hash_specific( enum rspamd_cryptobox_fast_hash_type type, const void *data, gsize len, uint64_t seed); /** - * Decode base64 using platform optimized code - * @param in - * @param inlen - * @param out - * @param outlen - * @return - */ +* Decode base64 using platform optimized code +* @param in +* @param inlen +* @param out +* @param outlen +* @return +*/ gboolean rspamd_cryptobox_base64_decode(const char *in, gsize inlen, unsigned char *out, gsize *outlen); /** - * Returns TRUE if data looks like a valid base64 string - * @param in - * @param inlen - * @return - */ +* Returns TRUE if data looks like a valid base64 string +* @param in +* @param inlen +* @return +*/ gboolean rspamd_cryptobox_base64_is_valid(const char *in, gsize inlen); #ifdef __cplusplus diff --git a/src/libcryptobox/keypair.c b/src/libcryptobox/keypair.c index 02070bb46..d3f81ee2d 100644 --- a/src/libcryptobox/keypair.c +++ b/src/libcryptobox/keypair.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -38,29 +38,14 @@ rspamd_cryptobox_keypair_sk(struct rspamd_cryptobox_keypair *kp, { g_assert(kp != NULL); - if (kp->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 32; - return RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp)->sk; - } - else { - *len = 64; - return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_25519(kp)->sk; - } + if (kp->type == RSPAMD_KEYPAIR_KEX) { + *len = 32; + return RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp)->sk; } else { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 32; - return RSPAMD_CRYPTOBOX_KEYPAIR_NIST(kp)->sk; - } - else { - *len = 32; - return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_NIST(kp)->sk; - } + *len = 64; + return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_25519(kp)->sk; } - - /* Not reached */ - return NULL; } static void * @@ -69,29 +54,14 @@ rspamd_cryptobox_keypair_pk(struct rspamd_cryptobox_keypair *kp, { g_assert(kp != NULL); - if (kp->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 32; - return RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp)->pk; - } - else { - *len = 32; - return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_25519(kp)->pk; - } + if (kp->type == RSPAMD_KEYPAIR_KEX) { + *len = 32; + return RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp)->pk; } else { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 65; - return RSPAMD_CRYPTOBOX_KEYPAIR_NIST(kp)->pk; - } - else { - *len = 65; - return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_NIST(kp)->pk; - } + *len = 32; + return RSPAMD_CRYPTOBOX_KEYPAIR_SIG_25519(kp)->pk; } - - /* Not reached */ - return NULL; } static void * @@ -100,53 +70,27 @@ rspamd_cryptobox_pubkey_pk(const struct rspamd_cryptobox_pubkey *kp, { g_assert(kp != NULL); - if (kp->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 32; - return RSPAMD_CRYPTOBOX_PUBKEY_25519(kp)->pk; - } - else { - *len = 32; - return RSPAMD_CRYPTOBOX_PUBKEY_SIG_25519(kp)->pk; - } + if (kp->type == RSPAMD_KEYPAIR_KEX) { + *len = 32; + return RSPAMD_CRYPTOBOX_PUBKEY_25519(kp)->pk; } else { - if (kp->type == RSPAMD_KEYPAIR_KEX) { - *len = 65; - return RSPAMD_CRYPTOBOX_PUBKEY_NIST(kp)->pk; - } - else { - *len = 65; - return RSPAMD_CRYPTOBOX_PUBKEY_SIG_NIST(kp)->pk; - } + *len = 32; + return RSPAMD_CRYPTOBOX_PUBKEY_SIG_25519(kp)->pk; } - - /* Not reached */ - return NULL; } static struct rspamd_cryptobox_keypair * -rspamd_cryptobox_keypair_alloc(enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) +rspamd_cryptobox_keypair_alloc(enum rspamd_cryptobox_keypair_type type) { struct rspamd_cryptobox_keypair *kp; unsigned int size = 0; - if (alg == RSPAMD_CRYPTOBOX_MODE_25519) { - if (type == RSPAMD_KEYPAIR_KEX) { - size = sizeof(struct rspamd_cryptobox_keypair_25519); - } - else { - size = sizeof(struct rspamd_cryptobox_keypair_sig_25519); - } + if (type == RSPAMD_KEYPAIR_KEX) { + size = sizeof(struct rspamd_cryptobox_keypair_25519); } else { - if (type == RSPAMD_KEYPAIR_KEX) { - size = sizeof(struct rspamd_cryptobox_keypair_nist); - } - else { - size = sizeof(struct rspamd_cryptobox_keypair_sig_nist); - } + size = sizeof(struct rspamd_cryptobox_keypair_sig_25519); } g_assert(size >= sizeof(*kp)); @@ -161,27 +105,17 @@ rspamd_cryptobox_keypair_alloc(enum rspamd_cryptobox_keypair_type type, } static struct rspamd_cryptobox_pubkey * -rspamd_cryptobox_pubkey_alloc(enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) +rspamd_cryptobox_pubkey_alloc(enum rspamd_cryptobox_keypair_type type) { struct rspamd_cryptobox_pubkey *pk; unsigned int size = 0; - if (alg == RSPAMD_CRYPTOBOX_MODE_25519) { - if (type == RSPAMD_KEYPAIR_KEX) { - size = sizeof(struct rspamd_cryptobox_pubkey_25519); - } - else { - size = sizeof(struct rspamd_cryptobox_pubkey_sig_25519); - } + + if (type == RSPAMD_KEYPAIR_KEX) { + size = sizeof(struct rspamd_cryptobox_pubkey_25519); } else { - if (type == RSPAMD_KEYPAIR_KEX) { - size = sizeof(struct rspamd_cryptobox_pubkey_nist); - } - else { - size = sizeof(struct rspamd_cryptobox_pubkey_sig_nist); - } + size = sizeof(struct rspamd_cryptobox_pubkey_sig_25519); } g_assert(size >= sizeof(*pk)); @@ -230,25 +164,23 @@ void rspamd_cryptobox_pubkey_dtor(struct rspamd_cryptobox_pubkey *p) } struct rspamd_cryptobox_keypair * -rspamd_keypair_new(enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) +rspamd_keypair_new(enum rspamd_cryptobox_keypair_type type) { struct rspamd_cryptobox_keypair *kp; void *pk, *sk; unsigned int size; - kp = rspamd_cryptobox_keypair_alloc(type, alg); - kp->alg = alg; + kp = rspamd_cryptobox_keypair_alloc(type); kp->type = type; sk = rspamd_cryptobox_keypair_sk(kp, &size); pk = rspamd_cryptobox_keypair_pk(kp, &size); if (type == RSPAMD_KEYPAIR_KEX) { - rspamd_cryptobox_keypair(pk, sk, alg); + rspamd_cryptobox_keypair(pk, sk); } else { - rspamd_cryptobox_keypair_sig(pk, sk, alg); + rspamd_cryptobox_keypair_sig(pk, sk); } rspamd_cryptobox_hash(kp->id, pk, size, NULL, 0); @@ -302,27 +234,10 @@ rspamd_pubkey_type(struct rspamd_cryptobox_pubkey *p) } -enum rspamd_cryptobox_mode -rspamd_keypair_alg(struct rspamd_cryptobox_keypair *kp) -{ - g_assert(kp != NULL); - - return kp->alg; -} - -enum rspamd_cryptobox_mode -rspamd_pubkey_alg(struct rspamd_cryptobox_pubkey *p) -{ - g_assert(p != NULL); - - return p->alg; -} - struct rspamd_cryptobox_pubkey * rspamd_pubkey_from_base32(const char *b32, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) + enum rspamd_cryptobox_keypair_type type) { unsigned char *decoded; gsize dlen, expected_len; @@ -342,16 +257,15 @@ rspamd_pubkey_from_base32(const char *b32, return NULL; } - expected_len = (type == RSPAMD_KEYPAIR_KEX) ? rspamd_cryptobox_pk_bytes(alg) : rspamd_cryptobox_pk_sig_bytes(alg); + expected_len = (type == RSPAMD_KEYPAIR_KEX) ? crypto_box_PUBLICKEYBYTES : crypto_sign_PUBLICKEYBYTES; if (dlen != expected_len) { g_free(decoded); return NULL; } - pk = rspamd_cryptobox_pubkey_alloc(type, alg); + pk = rspamd_cryptobox_pubkey_alloc(type); REF_INIT_RETAIN(pk, rspamd_cryptobox_pubkey_dtor); - pk->alg = alg; pk->type = type; pk_data = rspamd_cryptobox_pubkey_pk(pk, &pklen); @@ -365,8 +279,7 @@ rspamd_pubkey_from_base32(const char *b32, struct rspamd_cryptobox_pubkey * rspamd_pubkey_from_hex(const char *hex, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) + enum rspamd_cryptobox_keypair_type type) { unsigned char *decoded; gsize dlen, expected_len; @@ -388,16 +301,15 @@ rspamd_pubkey_from_hex(const char *hex, return NULL; } - expected_len = (type == RSPAMD_KEYPAIR_KEX) ? rspamd_cryptobox_pk_bytes(alg) : rspamd_cryptobox_pk_sig_bytes(alg); + expected_len = (type == RSPAMD_KEYPAIR_KEX) ? crypto_box_PUBLICKEYBYTES : crypto_sign_PUBLICKEYBYTES; if (dlen != expected_len) { g_free(decoded); return NULL; } - pk = rspamd_cryptobox_pubkey_alloc(type, alg); + pk = rspamd_cryptobox_pubkey_alloc(type); REF_INIT_RETAIN(pk, rspamd_cryptobox_pubkey_dtor); - pk->alg = alg; pk->type = type; pk_data = rspamd_cryptobox_pubkey_pk(pk, &pklen); @@ -411,25 +323,20 @@ rspamd_pubkey_from_hex(const char *hex, struct rspamd_cryptobox_pubkey * rspamd_pubkey_from_bin(const unsigned char *raw, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg) + enum rspamd_cryptobox_keypair_type type) { - gsize expected_len; unsigned int pklen; struct rspamd_cryptobox_pubkey *pk; unsigned char *pk_data; g_assert(raw != NULL && len > 0); - expected_len = (type == RSPAMD_KEYPAIR_KEX) ? rspamd_cryptobox_pk_bytes(alg) : rspamd_cryptobox_pk_sig_bytes(alg); - - if (len != expected_len) { + if (len != crypto_box_PUBLICKEYBYTES) { return NULL; } - pk = rspamd_cryptobox_pubkey_alloc(type, alg); + pk = rspamd_cryptobox_pubkey_alloc(type); REF_INIT_RETAIN(pk, rspamd_cryptobox_pubkey_dtor); - pk->alg = alg; pk->type = type; pk_data = rspamd_cryptobox_pubkey_pk(pk, &pklen); @@ -463,7 +370,6 @@ const unsigned char * rspamd_pubkey_calculate_nm(struct rspamd_cryptobox_pubkey *p, struct rspamd_cryptobox_keypair *kp) { - g_assert(kp->alg == p->alg); g_assert(kp->type == p->type); g_assert(p->type == RSPAMD_KEYPAIR_KEX); @@ -476,22 +382,12 @@ rspamd_pubkey_calculate_nm(struct rspamd_cryptobox_pubkey *p, REF_INIT_RETAIN(p->nm, rspamd_cryptobox_nm_dtor); } - if (kp->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - struct rspamd_cryptobox_pubkey_25519 *rk_25519 = - RSPAMD_CRYPTOBOX_PUBKEY_25519(p); - struct rspamd_cryptobox_keypair_25519 *sk_25519 = - RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp); + struct rspamd_cryptobox_pubkey_25519 *rk_25519 = + RSPAMD_CRYPTOBOX_PUBKEY_25519(p); + struct rspamd_cryptobox_keypair_25519 *sk_25519 = + RSPAMD_CRYPTOBOX_KEYPAIR_25519(kp); - rspamd_cryptobox_nm(p->nm->nm, rk_25519->pk, sk_25519->sk, p->alg); - } - else { - struct rspamd_cryptobox_pubkey_nist *rk_nist = - RSPAMD_CRYPTOBOX_PUBKEY_NIST(p); - struct rspamd_cryptobox_keypair_nist *sk_nist = - RSPAMD_CRYPTOBOX_KEYPAIR_NIST(kp); - - rspamd_cryptobox_nm(p->nm->nm, rk_nist->pk, sk_nist->sk, p->alg); - } + rspamd_cryptobox_nm(p->nm->nm, rk_25519->pk, sk_25519->sk); return p->nm->nm; } @@ -662,7 +558,6 @@ rspamd_keypair_from_ucl(const ucl_object_t *obj) const ucl_object_t *privkey, *pubkey, *elt; const char *str; enum rspamd_cryptobox_keypair_type type = RSPAMD_KEYPAIR_KEX; - enum rspamd_cryptobox_mode mode = RSPAMD_CRYPTOBOX_MODE_25519; gboolean is_hex = FALSE; struct rspamd_cryptobox_keypair *kp; unsigned int len; @@ -705,19 +600,6 @@ rspamd_keypair_from_ucl(const ucl_object_t *obj) /* TODO: handle errors */ } - elt = ucl_object_lookup(obj, "algorithm"); - if (elt && ucl_object_type(elt) == UCL_STRING) { - str = ucl_object_tostring(elt); - - if (g_ascii_strcasecmp(str, "curve25519") == 0) { - mode = RSPAMD_CRYPTOBOX_MODE_25519; - } - else if (g_ascii_strcasecmp(str, "nistp256") == 0) { - mode = RSPAMD_CRYPTOBOX_MODE_NIST; - } - /* TODO: handle errors */ - } - elt = ucl_object_lookup(obj, "encoding"); if (elt && ucl_object_type(elt) == UCL_STRING) { str = ucl_object_tostring(elt); @@ -728,9 +610,8 @@ rspamd_keypair_from_ucl(const ucl_object_t *obj) /* TODO: handle errors */ } - kp = rspamd_cryptobox_keypair_alloc(type, mode); + kp = rspamd_cryptobox_keypair_alloc(type); kp->type = type; - kp->alg = mode; REF_INIT_RETAIN(kp, rspamd_cryptobox_keypair_dtor); g_assert(kp != NULL); @@ -838,8 +719,7 @@ rspamd_keypair_to_ucl(struct rspamd_cryptobox_keypair *kp, "encoding", 0, false); ucl_object_insert_key(elt, - ucl_object_fromstring( - kp->alg == RSPAMD_CRYPTOBOX_MODE_NIST ? "nistp256" : "curve25519"), + ucl_object_fromstring("curve25519"), "algorithm", 0, false); ucl_object_insert_key(elt, @@ -873,9 +753,9 @@ rspamd_keypair_decrypt(struct rspamd_cryptobox_keypair *kp, return FALSE; } - if (inlen < sizeof(encrypted_magic) + rspamd_cryptobox_pk_bytes(kp->alg) + - rspamd_cryptobox_mac_bytes(kp->alg) + - rspamd_cryptobox_nonce_bytes(kp->alg)) { + if (inlen < sizeof(encrypted_magic) + crypto_box_publickeybytes() + + crypto_box_macbytes() + + crypto_box_noncebytes()) { g_set_error(err, rspamd_keypair_quark(), E2BIG, "invalid size: too small"); return FALSE; @@ -890,9 +770,9 @@ rspamd_keypair_decrypt(struct rspamd_cryptobox_keypair *kp, /* Set pointers */ pubkey = in + sizeof(encrypted_magic); - mac = pubkey + rspamd_cryptobox_pk_bytes(kp->alg); - nonce = mac + rspamd_cryptobox_mac_bytes(kp->alg); - data = nonce + rspamd_cryptobox_nonce_bytes(kp->alg); + mac = pubkey + crypto_box_publickeybytes(); + nonce = mac + crypto_box_macbytes(); + data = nonce + crypto_box_noncebytes(); if (data - in >= inlen) { g_set_error(err, rspamd_keypair_quark(), E2BIG, "invalid size: too small"); @@ -908,7 +788,7 @@ rspamd_keypair_decrypt(struct rspamd_cryptobox_keypair *kp, if (!rspamd_cryptobox_decrypt_inplace(*out, inlen, nonce, pubkey, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), - mac, kp->alg)) { + mac)) { g_set_error(err, rspamd_keypair_quark(), EPERM, "verification failed"); g_free(*out); @@ -942,26 +822,26 @@ rspamd_keypair_encrypt(struct rspamd_cryptobox_keypair *kp, return FALSE; } - local = rspamd_keypair_new(kp->type, kp->alg); + local = rspamd_keypair_new(kp->type); olen = inlen + sizeof(encrypted_magic) + - rspamd_cryptobox_pk_bytes(kp->alg) + - rspamd_cryptobox_mac_bytes(kp->alg) + - rspamd_cryptobox_nonce_bytes(kp->alg); + crypto_box_publickeybytes() + + crypto_box_macbytes() + + crypto_box_noncebytes(); *out = g_malloc(olen); memcpy(*out, encrypted_magic, sizeof(encrypted_magic)); pubkey = *out + sizeof(encrypted_magic); - mac = pubkey + rspamd_cryptobox_pk_bytes(kp->alg); - nonce = mac + rspamd_cryptobox_mac_bytes(kp->alg); - data = nonce + rspamd_cryptobox_nonce_bytes(kp->alg); + mac = pubkey + crypto_box_publickeybytes(); + nonce = mac + crypto_box_macbytes(); + data = nonce + crypto_box_noncebytes(); - ottery_rand_bytes(nonce, rspamd_cryptobox_nonce_bytes(kp->alg)); + ottery_rand_bytes(nonce, crypto_box_noncebytes()); memcpy(data, in, inlen); - memcpy(pubkey, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_PK, NULL), - rspamd_cryptobox_pk_bytes(kp->alg)); - rspamd_cryptobox_encrypt_inplace(data, inlen, nonce, pubkey, + memcpy(pubkey, rspamd_keypair_component(local, RSPAMD_KEYPAIR_COMPONENT_PK, NULL), + crypto_box_publickeybytes()); + rspamd_cryptobox_encrypt_inplace(data, inlen, nonce, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_PK, NULL), rspamd_keypair_component(local, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), - mac, kp->alg); + mac); rspamd_keypair_unref(local); if (outlen) { @@ -991,26 +871,26 @@ rspamd_pubkey_encrypt(struct rspamd_cryptobox_pubkey *pk, return FALSE; } - local = rspamd_keypair_new(pk->type, pk->alg); + local = rspamd_keypair_new(pk->type); olen = inlen + sizeof(encrypted_magic) + - rspamd_cryptobox_pk_bytes(pk->alg) + - rspamd_cryptobox_mac_bytes(pk->alg) + - rspamd_cryptobox_nonce_bytes(pk->alg); + crypto_box_publickeybytes() + + crypto_box_macbytes() + + crypto_box_noncebytes(); *out = g_malloc(olen); memcpy(*out, encrypted_magic, sizeof(encrypted_magic)); pubkey = *out + sizeof(encrypted_magic); - mac = pubkey + rspamd_cryptobox_pk_bytes(pk->alg); - nonce = mac + rspamd_cryptobox_mac_bytes(pk->alg); - data = nonce + rspamd_cryptobox_nonce_bytes(pk->alg); + mac = pubkey + crypto_box_publickeybytes(); + nonce = mac + crypto_box_macbytes(); + data = nonce + crypto_box_noncebytes(); - ottery_rand_bytes(nonce, rspamd_cryptobox_nonce_bytes(pk->alg)); + ottery_rand_bytes(nonce, crypto_box_noncebytes()); memcpy(data, in, inlen); - memcpy(pubkey, rspamd_pubkey_get_pk(pk, NULL), - rspamd_cryptobox_pk_bytes(pk->alg)); - rspamd_cryptobox_encrypt_inplace(data, inlen, nonce, pubkey, + memcpy(pubkey, rspamd_keypair_component(local, RSPAMD_KEYPAIR_COMPONENT_PK, NULL), + crypto_box_publickeybytes()); + rspamd_cryptobox_encrypt_inplace(data, inlen, nonce, rspamd_pubkey_get_pk(pk, NULL), rspamd_keypair_component(local, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), - mac, pk->alg); + mac); rspamd_keypair_unref(local); if (outlen) { diff --git a/src/libcryptobox/keypair.h b/src/libcryptobox/keypair.h index 849246255..97b46cbf5 100644 --- a/src/libcryptobox/keypair.h +++ b/src/libcryptobox/keypair.h @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -50,8 +50,7 @@ struct rspamd_cryptobox_pubkey; * @return fresh keypair generated */ struct rspamd_cryptobox_keypair *rspamd_keypair_new( - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg); + enum rspamd_cryptobox_keypair_type type); /** * Increase refcount for the specific keypair @@ -84,8 +83,7 @@ struct rspamd_cryptobox_pubkey *rspamd_pubkey_ref( */ struct rspamd_cryptobox_pubkey *rspamd_pubkey_from_base32(const char *b32, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg); + enum rspamd_cryptobox_keypair_type type); /** * Load pubkey from hex string @@ -96,8 +94,7 @@ struct rspamd_cryptobox_pubkey *rspamd_pubkey_from_base32(const char *b32, */ struct rspamd_cryptobox_pubkey *rspamd_pubkey_from_hex(const char *hex, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg); + enum rspamd_cryptobox_keypair_type type); /** * Load pubkey from raw chunk string @@ -108,8 +105,7 @@ struct rspamd_cryptobox_pubkey *rspamd_pubkey_from_hex(const char *hex, */ struct rspamd_cryptobox_pubkey *rspamd_pubkey_from_bin(const unsigned char *raw, gsize len, - enum rspamd_cryptobox_keypair_type type, - enum rspamd_cryptobox_mode alg); + enum rspamd_cryptobox_keypair_type type); /** @@ -127,18 +123,7 @@ enum rspamd_cryptobox_keypair_type rspamd_keypair_type( /** * Get type of pubkey */ -enum rspamd_cryptobox_keypair_type rspamd_pubkey_type( - struct rspamd_cryptobox_pubkey *p); - -/** - * Get algorithm of keypair - */ -enum rspamd_cryptobox_mode rspamd_keypair_alg(struct rspamd_cryptobox_keypair *kp); - -/** - * Get algorithm of pubkey - */ -enum rspamd_cryptobox_mode rspamd_pubkey_alg(struct rspamd_cryptobox_pubkey *p); +enum rspamd_cryptobox_keypair_type rspamd_pubkey_type(struct rspamd_cryptobox_pubkey *p); /** * Get cached NM for this specific pubkey diff --git a/src/libcryptobox/keypair_private.h b/src/libcryptobox/keypair_private.h index 793231701..2e372777c 100644 --- a/src/libcryptobox/keypair_private.h +++ b/src/libcryptobox/keypair_private.h @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -38,22 +38,11 @@ struct rspamd_cryptobox_nm { struct rspamd_cryptobox_keypair { unsigned char id[rspamd_cryptobox_HASHBYTES]; enum rspamd_cryptobox_keypair_type type; - enum rspamd_cryptobox_mode alg; ucl_object_t *extensions; ref_entry_t ref; }; /* - * NIST p256 ecdh keypair - */ -#define RSPAMD_CRYPTOBOX_KEYPAIR_NIST(x) ((struct rspamd_cryptobox_keypair_nist *) (x)) -struct rspamd_cryptobox_keypair_nist { - struct rspamd_cryptobox_keypair parent; - unsigned char sk[32]; - unsigned char pk[65]; -}; - -/* * Curve25519 ecdh keypair */ #define RSPAMD_CRYPTOBOX_KEYPAIR_25519(x) ((struct rspamd_cryptobox_keypair_25519 *) (x)) @@ -64,16 +53,6 @@ struct rspamd_cryptobox_keypair_25519 { }; /* - * NIST p256 ecdsa keypair - */ -#define RSPAMD_CRYPTOBOX_KEYPAIR_SIG_NIST(x) ((struct rspamd_cryptobox_keypair_sig_nist *) (x)) -struct rspamd_cryptobox_keypair_sig_nist { - struct rspamd_cryptobox_keypair parent; - unsigned char sk[32]; - unsigned char pk[65]; -}; - -/* * Ed25519 keypair */ #define RSPAMD_CRYPTOBOX_KEYPAIR_SIG_25519(x) ((struct rspamd_cryptobox_keypair_sig_25519 *) (x)) @@ -90,20 +69,10 @@ struct rspamd_cryptobox_pubkey { unsigned char id[rspamd_cryptobox_HASHBYTES]; struct rspamd_cryptobox_nm *nm; enum rspamd_cryptobox_keypair_type type; - enum rspamd_cryptobox_mode alg; ref_entry_t ref; }; /* - * Public p256 ecdh - */ -#define RSPAMD_CRYPTOBOX_PUBKEY_NIST(x) ((struct rspamd_cryptobox_pubkey_nist *) (x)) -struct rspamd_cryptobox_pubkey_nist { - struct rspamd_cryptobox_pubkey parent; - unsigned char pk[65]; -}; - -/* * Public curve25519 ecdh */ #define RSPAMD_CRYPTOBOX_PUBKEY_25519(x) ((struct rspamd_cryptobox_pubkey_25519 *) (x)) @@ -113,15 +82,6 @@ struct rspamd_cryptobox_pubkey_25519 { }; /* - * Public p256 ecdsa - */ -#define RSPAMD_CRYPTOBOX_PUBKEY_SIG_NIST(x) ((struct rspamd_cryptobox_pubkey_sig_nist *) (x)) -struct rspamd_cryptobox_pubkey_sig_nist { - struct rspamd_cryptobox_pubkey parent; - unsigned char pk[65]; -}; - -/* * Public ed25519 */ #define RSPAMD_CRYPTOBOX_PUBKEY_SIG_25519(x) ((struct rspamd_cryptobox_pubkey_sig_25519 *) (x)) diff --git a/src/libcryptobox/keypairs_cache.c b/src/libcryptobox/keypairs_cache.c index 6003d9923..0b069a64b 100644 --- a/src/libcryptobox/keypairs_cache.c +++ b/src/libcryptobox/keypairs_cache.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -77,7 +77,6 @@ void rspamd_keypair_cache_process(struct rspamd_keypair_cache *c, g_assert(lk != NULL); g_assert(rk != NULL); - g_assert(rk->alg == lk->alg); g_assert(rk->type == lk->type); g_assert(rk->type == RSPAMD_KEYPAIR_KEX); @@ -106,22 +105,12 @@ void rspamd_keypair_cache_process(struct rspamd_keypair_cache *c, rspamd_cryptobox_HASHBYTES); memcpy(&new->nm->sk_id, lk->id, sizeof(uint64_t)); - if (rk->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - struct rspamd_cryptobox_pubkey_25519 *rk_25519 = - RSPAMD_CRYPTOBOX_PUBKEY_25519(rk); - struct rspamd_cryptobox_keypair_25519 *sk_25519 = - RSPAMD_CRYPTOBOX_KEYPAIR_25519(lk); + struct rspamd_cryptobox_pubkey_25519 *rk_25519 = + RSPAMD_CRYPTOBOX_PUBKEY_25519(rk); + struct rspamd_cryptobox_keypair_25519 *sk_25519 = + RSPAMD_CRYPTOBOX_KEYPAIR_25519(lk); - rspamd_cryptobox_nm(new->nm->nm, rk_25519->pk, sk_25519->sk, rk->alg); - } - else { - struct rspamd_cryptobox_pubkey_nist *rk_nist = - RSPAMD_CRYPTOBOX_PUBKEY_NIST(rk); - struct rspamd_cryptobox_keypair_nist *sk_nist = - RSPAMD_CRYPTOBOX_KEYPAIR_NIST(lk); - - rspamd_cryptobox_nm(new->nm->nm, rk_nist->pk, sk_nist->sk, rk->alg); - } + rspamd_cryptobox_nm(new->nm->nm, rk_25519->pk, sk_25519->sk); rspamd_lru_hash_insert(c->hash, new, new, time(NULL), -1); } diff --git a/src/libmime/lang_detection.c b/src/libmime/lang_detection.c index 4796e4834..6e180ea66 100644 --- a/src/libmime/lang_detection.c +++ b/src/libmime/lang_detection.c @@ -1828,7 +1828,7 @@ rspamd_language_detector_detect(struct rspamd_task *task, unsigned int cand_len; enum rspamd_language_category cat; struct rspamd_lang_detector_res *cand; - enum rspamd_language_detected_type r; + enum rspamd_language_detected_type r = rs_detect_none; struct rspamd_frequency_sort_cbdata cbd; /* Check if we have sorted candidates based on frequency */ gboolean frequency_heuristic_applied = FALSE, ret = FALSE, internal_heuristic_applied = FALSE; diff --git a/src/libmime/scan_result.c b/src/libmime/scan_result.c index f15290b95..894ae4f9e 100644 --- a/src/libmime/scan_result.c +++ b/src/libmime/scan_result.c @@ -201,16 +201,34 @@ rspamd_check_group_score(struct rspamd_task *task, double *group_score, double w) { - if (gr != NULL && group_score && gr->max_score > 0.0 && w > 0.0) { - if (*group_score >= gr->max_score && w > 0) { + double group_limit = NAN; + + if (gr != NULL && group_score) { + if ((*group_score + w) >= 0 && !isnan(gr->max_score) && gr->max_score > 0) { + group_limit = gr->max_score; + } + else if ((*group_score + w) < 0 && !isnan(gr->min_score) && gr->min_score < 0) { + group_limit = -gr->min_score; + } + } + + if (gr != NULL && group_limit && !isnan(group_limit)) { + if (fabs(*group_score) >= group_limit && signbit(*group_score) == signbit(w)) { + /* Cannot add more to the group */ msg_info_task("maximum group score %.2f for group %s has been reached," " ignoring symbol %s with weight %.2f", - gr->max_score, + group_limit, gr->name, symbol, w); return NAN; } - else if (*group_score + w > gr->max_score) { - w = gr->max_score - *group_score; + else if (fabs(*group_score + w) > group_limit) { + /* Reduce weight */ + double new_w = signbit(w) ? -group_limit - *group_score : group_limit - *group_score; + msg_info_task("maximum group score %.2f for group %s has been reached," + " reduce weight of symbol %s from %.2f to %.2f", + group_limit, + gr->name, symbol, w, new_w); + w = new_w; } } @@ -393,15 +411,7 @@ insert_metric_result(struct rspamd_task *task, } else if (gr_score) { *gr_score += cur_diff; - - if (cur_diff < diff) { - /* Reduce */ - msg_debug_metric( - "group limit %.2f is reached for %s when inserting symbol %s;" - " reduce score %.2f - %.2f", - *gr_score, gr->name, symbol, diff, cur_diff); - diff = cur_diff; - } + diff = cur_diff; } } } @@ -461,15 +471,7 @@ insert_metric_result(struct rspamd_task *task, } else if (gr_score) { *gr_score += cur_score; - - if (cur_score < final_score) { - /* Reduce */ - msg_debug_metric( - "group limit %.2f is reached for %s when inserting symbol %s;" - " reduce score %.2f - %.2f", - *gr_score, gr->name, symbol, final_score, cur_score); - final_score = cur_score; - } + final_score = cur_score; } } } diff --git a/src/libserver/cfg_file.h b/src/libserver/cfg_file.h index a963f952f..f59c6ff89 100644 --- a/src/libserver/cfg_file.h +++ b/src/libserver/cfg_file.h @@ -102,6 +102,7 @@ struct rspamd_symbols_group { char *description; GHashTable *symbols; double max_score; + double min_score; unsigned int flags; }; diff --git a/src/libserver/cfg_rcl.cxx b/src/libserver/cfg_rcl.cxx index 2fe37f18e..79509e12e 100644 --- a/src/libserver/cfg_rcl.cxx +++ b/src/libserver/cfg_rcl.cxx @@ -420,6 +420,18 @@ rspamd_rcl_group_handler(rspamd_mempool_t *pool, const ucl_object_t *obj, return FALSE; } + if (!std::isnan(gr->max_score) && gr->max_score < 0) { + msg_err_config("group %s has negative max_score which is broken, use min_score if required", gr->name); + + return FALSE; + } + if (!std::isnan(gr->min_score) && gr->min_score > 0) { + msg_err_config("group %s has positive min_score which is broken, use max_score if required", gr->name); + + return FALSE; + } + + if (const auto *elt = ucl_object_lookup(obj, "one_shot"); elt != nullptr) { if (ucl_object_type(elt) != UCL_BOOLEAN) { g_set_error(err, @@ -2355,6 +2367,12 @@ rspamd_rcl_config_init(struct rspamd_config *cfg, GHashTable *skip_sections) G_STRUCT_OFFSET(struct rspamd_symbols_group, max_score), 0, "Maximum score that could be reached by this symbols group"); + rspamd_rcl_add_default_handler(sub, + "min_score", + rspamd_rcl_parse_struct_double, + G_STRUCT_OFFSET(struct rspamd_symbols_group, min_score), + 0, + "Maximum negative score that could be reached by this symbols group"); } if (!(skip_sections && g_hash_table_lookup(skip_sections, "worker"))) { @@ -3039,21 +3057,16 @@ rspamd_rcl_parse_struct_pubkey(rspamd_mempool_t *pool, gsize len; const char *str; rspamd_cryptobox_keypair_type keypair_type = RSPAMD_KEYPAIR_KEX; - rspamd_cryptobox_mode keypair_mode = RSPAMD_CRYPTOBOX_MODE_25519; if (pd->flags & RSPAMD_CL_FLAG_SIGNKEY) { keypair_type = RSPAMD_KEYPAIR_SIGN; } - if (pd->flags & RSPAMD_CL_FLAG_NISTKEY) { - keypair_mode = RSPAMD_CRYPTOBOX_MODE_NIST; - } target = (struct rspamd_cryptobox_pubkey **) (((char *) pd->user_struct) + pd->offset); if (obj->type == UCL_STRING) { str = ucl_object_tolstring(obj, &len); - pk = rspamd_pubkey_from_base32(str, len, keypair_type, - keypair_mode); + pk = rspamd_pubkey_from_base32(str, len, keypair_type); if (pk != nullptr) { *target = pk; @@ -3482,7 +3495,7 @@ void rspamd_rcl_maybe_apply_lua_transform(struct rspamd_config *cfg) lua_pushvalue(L, -2); /* Push the existing config */ - ucl_object_push_lua(L, cfg->cfg_ucl_obj, true); + ucl_object_push_lua_unwrapped(L, cfg->cfg_ucl_obj); if (auto ret = lua_pcall(L, 1, 2, err_idx); ret != 0) { msg_err("call to rspamadm lua script failed (%d): %s", ret, @@ -3492,12 +3505,8 @@ void rspamd_rcl_maybe_apply_lua_transform(struct rspamd_config *cfg) return; } - if (lua_toboolean(L, -2) && lua_type(L, -1) == LUA_TTABLE) { - ucl_object_t *old_cfg = cfg->cfg_ucl_obj; - + if (lua_toboolean(L, -2) && lua_type(L, -1) == LUA_TUSERDATA) { msg_info_config("configuration has been transformed in Lua"); - cfg->cfg_ucl_obj = ucl_object_lua_import(L, -1); - ucl_object_unref(old_cfg); } /* error function */ @@ -3629,7 +3638,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg, auto &cfg_file = cfg_file_maybe.value(); /* Try to load keyfile if available */ - rspamd::util::raii_file::open(fmt::format("{}.key", filename), O_RDONLY).map([&](const auto &keyfile) { + auto keyfile_name = fmt::format("{}.key", filename); + rspamd::util::raii_file::open(keyfile_name, O_RDONLY).map([&](const auto &keyfile) { auto *kp_parser = ucl_parser_new(0); if (ucl_parser_add_fd(kp_parser, keyfile.get_fd())) { auto *kp_obj = ucl_parser_get_object(kp_parser); @@ -3638,8 +3648,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg, decrypt_keypair = rspamd_keypair_from_ucl(kp_obj); if (decrypt_keypair == nullptr) { - msg_err_config_forced("cannot load keypair from %s.key: invalid keypair", - filename); + msg_err_config_forced("cannot load keypair from %s: invalid keypair", + keyfile_name.c_str()); } else { /* Add decryption support to UCL */ @@ -3651,8 +3661,8 @@ rspamd_config_parse_ucl(struct rspamd_config *cfg, ucl_object_unref(kp_obj); } else { - msg_err_config_forced("cannot load keypair from %s.key: %s", - filename, ucl_parser_get_error(kp_parser)); + msg_err_config_forced("cannot load keypair from %s: %s", + keyfile_name.c_str(), ucl_parser_get_error(kp_parser)); } ucl_parser_free(kp_parser); }); diff --git a/src/libserver/cfg_rcl.h b/src/libserver/cfg_rcl.h index e33656b72..35b9b931f 100644 --- a/src/libserver/cfg_rcl.h +++ b/src/libserver/cfg_rcl.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,6 @@ enum rspamd_rcl_flag { RSPAMD_CL_FLAG_STRING_LIST_HASH = 0x1 << 12, RSPAMD_CL_FLAG_MULTIPLE = 0x1 << 13, RSPAMD_CL_FLAG_SIGNKEY = 0x1 << 14, - RSPAMD_CL_FLAG_NISTKEY = 0x1 << 15, }; struct rspamd_rcl_struct_parser { diff --git a/src/libserver/cfg_utils.cxx b/src/libserver/cfg_utils.cxx index f17caa8a5..38adf8390 100644 --- a/src/libserver/cfg_utils.cxx +++ b/src/libserver/cfg_utils.cxx @@ -1053,6 +1053,8 @@ rspamd_config_new_group(struct rspamd_config *cfg, const char *name) rspamd_mempool_add_destructor(cfg->cfg_pool, (rspamd_mempool_destruct_t) g_hash_table_unref, gr->symbols); gr->name = rspamd_mempool_strdup(cfg->cfg_pool, name); + gr->max_score = NAN; + gr->min_score = NAN; if (strcmp(gr->name, "ungrouped") == 0) { gr->flags |= RSPAMD_SYMBOL_GROUP_UNGROUPED; diff --git a/src/libserver/dkim.c b/src/libserver/dkim.c index 742e4db8b..a76ed31ab 100644 --- a/src/libserver/dkim.c +++ b/src/libserver/dkim.c @@ -155,12 +155,12 @@ struct rspamd_dkim_key_s { gsize decoded_len; char key_id[RSPAMD_DKIM_KEY_ID_LEN]; union { - RSA *key_rsa; - EC_KEY *key_ecdsa; unsigned char *key_eddsa; - } key; - BIO *key_bio; - EVP_PKEY *key_evp; + struct { + BIO *key_bio; + EVP_PKEY *key_evp; + } key_ssl; + } specific; time_t mtime; unsigned int ttl; enum rspamd_dkim_key_type type; @@ -790,12 +790,12 @@ rspamd_dkim_add_arc_seal_headers(rspamd_mempool_t *pool, } /** - * Create new dkim context from signature - * @param sig message's signature - * @param pool pool to allocate memory from - * @param err pointer to error object - * @return new context or NULL - */ +* Create new dkim context from signature +* @param sig message's signature +* @param pool pool to allocate memory from +* @param err pointer to error object +* @return new context or NULL +*/ rspamd_dkim_context_t * rspamd_create_dkim_context(const char *sig, rspamd_mempool_t *pool, @@ -1097,9 +1097,9 @@ rspamd_create_dkim_context(const char *sig, if (state == DKIM_STATE_ERROR) { /* - * We need to return from here as state machine won't - * do any more steps after p == end - */ + * We need to return from here as state machine won't + * do any more steps after p == end + */ if (err) { msg_info_dkim("dkim parse failed: %e", *err); } @@ -1391,25 +1391,24 @@ rspamd_dkim_make_key(const char *keydata, EVP_MD_CTX_destroy(mdctx); if (key->type == RSPAMD_DKIM_KEY_EDDSA) { - key->key.key_eddsa = key->keydata; + key->specific.key_eddsa = key->keydata; - if (key->decoded_len != rspamd_cryptobox_pk_sig_bytes( - RSPAMD_CRYPTOBOX_MODE_25519)) { + if (key->decoded_len != crypto_sign_publickeybytes()) { g_set_error(err, DKIM_ERROR, DKIM_SIGERROR_KEYFAIL, - "DKIM key is has invalid length %d for eddsa; expected %d", + "DKIM key is has invalid length %d for eddsa; expected %zd", (int) key->decoded_len, - rspamd_cryptobox_pk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519)); + crypto_sign_publickeybytes()); REF_RELEASE(key); return NULL; } } else { - key->key_bio = BIO_new_mem_buf(key->keydata, key->decoded_len); + key->specific.key_ssl.key_bio = BIO_new_mem_buf(key->keydata, key->decoded_len); - if (key->key_bio == NULL) { + if (key->specific.key_ssl.key_bio == NULL) { g_set_error(err, DKIM_ERROR, DKIM_SIGERROR_KEYFAIL, @@ -1419,9 +1418,9 @@ rspamd_dkim_make_key(const char *keydata, return NULL; } - key->key_evp = d2i_PUBKEY_bio(key->key_bio, NULL); + key->specific.key_ssl.key_evp = d2i_PUBKEY_bio(key->specific.key_ssl.key_bio, NULL); - if (key->key_evp == NULL) { + if (key->specific.key_ssl.key_evp == NULL) { g_set_error(err, DKIM_ERROR, DKIM_SIGERROR_KEYFAIL, @@ -1430,33 +1429,6 @@ rspamd_dkim_make_key(const char *keydata, return NULL; } - - if (type == RSPAMD_DKIM_KEY_RSA) { - key->key.key_rsa = EVP_PKEY_get1_RSA(key->key_evp); - - if (key->key.key_rsa == NULL) { - g_set_error(err, - DKIM_ERROR, - DKIM_SIGERROR_KEYFAIL, - "cannot extract rsa key from evp key"); - REF_RELEASE(key); - - return NULL; - } - } - else { - key->key.key_ecdsa = EVP_PKEY_get1_EC_KEY(key->key_evp); - - if (key->key.key_ecdsa == NULL) { - g_set_error(err, - DKIM_ERROR, - DKIM_SIGERROR_KEYFAIL, - "cannot extract ecdsa key from evp key"); - REF_RELEASE(key); - - return NULL; - } - } } return key; @@ -1473,29 +1445,19 @@ rspamd_dkim_key_id(rspamd_dkim_key_t *key) } /** - * Free DKIM key - * @param key - */ +* Free DKIM key +* @param key +*/ void rspamd_dkim_key_free(rspamd_dkim_key_t *key) { - if (key->key_evp) { - EVP_PKEY_free(key->key_evp); - } - - if (key->type == RSPAMD_DKIM_KEY_RSA) { - if (key->key.key_rsa) { - RSA_free(key->key.key_rsa); + if (key->type != RSPAMD_DKIM_KEY_EDDSA) { + if (key->specific.key_ssl.key_evp) { + EVP_PKEY_free(key->specific.key_ssl.key_evp); } - } - else if (key->type == RSPAMD_DKIM_KEY_ECDSA) { - if (key->key.key_ecdsa) { - EC_KEY_free(key->key.key_ecdsa); + if (key->specific.key_ssl.key_bio) { + BIO_free(key->specific.key_ssl.key_bio); } } - /* Nothing in case of eddsa key */ - if (key->key_bio) { - BIO_free(key->key_bio); - } g_free(key->raw_key); g_free(key->keydata); @@ -1504,20 +1466,16 @@ void rspamd_dkim_key_free(rspamd_dkim_key_t *key) void rspamd_dkim_sign_key_free(rspamd_dkim_sign_key_t *key) { - if (key->key_evp) { - EVP_PKEY_free(key->key_evp); - } - if (key->type == RSPAMD_DKIM_KEY_RSA) { - if (key->key.key_rsa) { - RSA_free(key->key.key_rsa); + if (key->type != RSPAMD_DKIM_KEY_EDDSA) { + if (key->specific.key_ssl.key_evp) { + EVP_PKEY_free(key->specific.key_ssl.key_evp); + } + if (key->specific.key_ssl.key_bio) { + BIO_free(key->specific.key_ssl.key_bio); } } - if (key->key_bio) { - BIO_free(key->key_bio); - } - - if (key->type == RSPAMD_DKIM_KEY_EDDSA) { - rspamd_explicit_memzero(key->key.key_eddsa, key->keylen); + else { + rspamd_explicit_memzero(key->specific.key_eddsa, key->keylen); g_free(key->keydata); } @@ -1570,9 +1528,9 @@ rspamd_dkim_parse_key(const char *txt, gsize *keylen, GError **err) break; case read_tag_before_eqsign: /* Input: spaces before eqsign - * Output: either read a next tag (previous had no value), or read value - * p is moved forward - */ + * Output: either read a next tag (previous had no value), or read value + * p is moved forward + */ if (*p == '=') { state = read_eqsign; } @@ -1759,12 +1717,12 @@ rspamd_dkim_dns_cb(struct rdns_reply *reply, gpointer arg) } /** - * Make DNS request for specified context and obtain and parse key - * @param ctx dkim context from signature - * @param resolver dns resolver object - * @param s async session to make request - * @return - */ +* Make DNS request for specified context and obtain and parse key +* @param ctx dkim context from signature +* @param resolver dns resolver object +* @param s async session to make request +* @return +*/ gboolean rspamd_get_dkim_key(rspamd_dkim_context_t *ctx, struct rspamd_task *task, @@ -2187,8 +2145,8 @@ rspamd_dkim_canonize_body(struct rspamd_task *task, ; /* - * If we have l= tag then we cannot add crlf... - */ + * If we have l= tag then we cannot add crlf... + */ if (need_crlf) { /* l is evil... */ if (ctx->len == 0) { @@ -2241,7 +2199,6 @@ rspamd_dkim_canonize_body(struct rspamd_task *task, return TRUE; } - /* TODO: Implement relaxed algorithm */ return FALSE; } @@ -2461,9 +2418,9 @@ rspamd_dkim_canonize_header(struct rspamd_dkim_common_ctx *ctx, bool use_idx = false, is_sign = ctx->is_sign; /* - * TODO: - * Temporary hack to prevent linked list being misused until refactored - */ + * TODO: + * Temporary hack to prevent linked list being misused until refactored + */ const unsigned int max_list_iters = 1000; if (count < 0) { @@ -2509,17 +2466,17 @@ rspamd_dkim_canonize_header(struct rspamd_dkim_common_ctx *ctx, if (hdr_cnt <= count) { /* - * If DKIM has less headers requested than there are in a - * message, then it's fine, it allows adding extra headers - */ + * If DKIM has less headers requested than there are in a + * message, then it's fine, it allows adding extra headers + */ return TRUE; } } else { /* - * This branch is used for ARC headers, and it orders them based on - * i=<number> string and not their real order in the list of headers - */ + * This branch is used for ARC headers, and it orders them based on + * i=<number> string and not their real order in the list of headers + */ char idx_buf[16]; int id_len, i; @@ -2692,12 +2649,12 @@ rspamd_dkim_type_to_string(enum rspamd_dkim_type t) } /** - * Check task for dkim context using dkim key - * @param ctx dkim verify context - * @param key dkim key (from cache or from dns request) - * @param task task to check - * @return - */ +* Check task for dkim context using dkim key +* @param ctx dkim verify context +* @param key dkim key (from cache or from dns request) +* @param task task to check +* @return +*/ struct rspamd_dkim_check_result * rspamd_dkim_check(rspamd_dkim_context_t *ctx, rspamd_dkim_key_t *key, @@ -2913,11 +2870,10 @@ rspamd_dkim_check(rspamd_dkim_context_t *ctx, /* Not reached */ nid = NID_sha1; } - switch (key->type) { case RSPAMD_DKIM_KEY_RSA: - if (RSA_verify(nid, raw_digest, dlen, ctx->b, ctx->blen, - key->key.key_rsa) != 1) { + if (!rspamd_cryptobox_verify_evp_rsa(nid, ctx->b, ctx->blen, raw_digest, dlen, + key->specific.key_ssl.key_evp)) { msg_debug_dkim("headers rsa verify failed"); ERR_clear_error(); res->rcode = DKIM_REJECT; @@ -2935,8 +2891,8 @@ rspamd_dkim_check(rspamd_dkim_context_t *ctx, } break; case RSPAMD_DKIM_KEY_ECDSA: - if (ECDSA_verify(nid, raw_digest, dlen, ctx->b, ctx->blen, - key->key.key_ecdsa) != 1) { + if (rspamd_cryptobox_verify_evp_ecdsa(nid, ctx->b, ctx->blen, raw_digest, dlen, + key->specific.key_ssl.key_evp) != 1) { msg_info_dkim( "%s: headers ECDSA verification failure; " "body length %d->%d; headers length %d; d=%s; s=%s; key_md5=%*xs; orig header: %s", @@ -2952,9 +2908,10 @@ rspamd_dkim_check(rspamd_dkim_context_t *ctx, res->fail_reason = "headers ecdsa verify failed"; } break; + case RSPAMD_DKIM_KEY_EDDSA: if (!rspamd_cryptobox_verify(ctx->b, ctx->blen, raw_digest, dlen, - key->key.key_eddsa, RSPAMD_CRYPTOBOX_MODE_25519)) { + key->specific.key_eddsa)) { msg_info_dkim( "%s: headers EDDSA verification failure; " "body length %d->%d; headers length %d; d=%s; s=%s; key_md5=%*xs; orig header: %s", @@ -3155,30 +3112,29 @@ rspamd_dkim_sign_key_load(const char *key, gsize len, } if (type == RSPAMD_DKIM_KEY_RAW && (len == 32 || - len == rspamd_cryptobox_sk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519))) { + len == crypto_sign_secretkeybytes())) { if (len == 32) { /* Seeded key, need scalarmult */ unsigned char pk[32]; nkey->type = RSPAMD_DKIM_KEY_EDDSA; - nkey->key.key_eddsa = g_malloc( - rspamd_cryptobox_sk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519)); - crypto_sign_ed25519_seed_keypair(pk, nkey->key.key_eddsa, key); - nkey->keylen = rspamd_cryptobox_sk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519); + nkey->specific.key_eddsa = g_malloc(crypto_sign_secretkeybytes()); + crypto_sign_ed25519_seed_keypair(pk, nkey->specific.key_eddsa, key); + nkey->keylen = crypto_sign_secretkeybytes(); } else { /* Full ed25519 key */ - unsigned klen = rspamd_cryptobox_sk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519); + unsigned klen = crypto_sign_secretkeybytes(); nkey->type = RSPAMD_DKIM_KEY_EDDSA; - nkey->key.key_eddsa = g_malloc(klen); - memcpy(nkey->key.key_eddsa, key, klen); + nkey->specific.key_eddsa = g_malloc(klen); + memcpy(nkey->specific.key_eddsa, key, klen); nkey->keylen = klen; } } else { - nkey->key_bio = BIO_new_mem_buf(key, len); + nkey->specific.key_ssl.key_bio = BIO_new_mem_buf(key, len); if (type == RSPAMD_DKIM_KEY_RAW) { - if (d2i_PrivateKey_bio(nkey->key_bio, &nkey->key_evp) == NULL) { + if (d2i_PrivateKey_bio(nkey->specific.key_ssl.key_bio, &nkey->specific.key_ssl.key_evp) == NULL) { g_set_error(err, dkim_error_quark(), DKIM_SIGERROR_KEYFAIL, "cannot parse raw private key: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -3190,7 +3146,7 @@ rspamd_dkim_sign_key_load(const char *key, gsize len, } } else { - if (!PEM_read_bio_PrivateKey(nkey->key_bio, &nkey->key_evp, NULL, NULL)) { + if (!PEM_read_bio_PrivateKey(nkey->specific.key_ssl.key_bio, &nkey->specific.key_ssl.key_evp, NULL, NULL)) { g_set_error(err, dkim_error_quark(), DKIM_SIGERROR_KEYFAIL, "cannot parse pem private key: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -3200,18 +3156,6 @@ rspamd_dkim_sign_key_load(const char *key, gsize len, goto end; } } - nkey->key.key_rsa = EVP_PKEY_get1_RSA(nkey->key_evp); - if (nkey->key.key_rsa == NULL) { - g_set_error(err, - DKIM_ERROR, - DKIM_SIGERROR_KEYFAIL, - "cannot extract rsa key from evp key"); - rspamd_dkim_sign_key_free(nkey); - nkey = NULL; - - goto end; - } - nkey->type = RSPAMD_DKIM_KEY_RSA; } REF_INIT_RETAIN(nkey, rspamd_dkim_sign_key_free); @@ -3269,7 +3213,7 @@ rspamd_create_dkim_sign_context(struct rspamd_task *task, return NULL; } - if (!priv_key || (!priv_key->key.key_rsa && !priv_key->key.key_eddsa)) { + if (!priv_key) { g_set_error(err, DKIM_ERROR, DKIM_SIGERROR_KEYFAIL, @@ -3536,12 +3480,34 @@ rspamd_dkim_sign(struct rspamd_task *task, const char *selector, dlen = EVP_MD_CTX_size(ctx->common.headers_hash); EVP_DigestFinal_ex(ctx->common.headers_hash, raw_digest, NULL); + if (ctx->key->type == RSPAMD_DKIM_KEY_RSA) { - sig_len = RSA_size(ctx->key->key.key_rsa); + sig_len = EVP_PKEY_size(ctx->key->specific.key_ssl.key_evp); sig_buf = g_alloca(sig_len); + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(ctx->key->specific.key_ssl.key_evp, NULL); + if (EVP_PKEY_sign_init(pctx) <= 0) { + g_string_free(hdr, TRUE); + msg_err_task("rsa sign error: %s", + ERR_error_string(ERR_get_error(), NULL)); + + return NULL; + } + if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0) { + g_string_free(hdr, TRUE); + msg_err_task("rsa sign error: %s", + ERR_error_string(ERR_get_error(), NULL)); - if (RSA_sign(NID_sha256, raw_digest, dlen, sig_buf, &sig_len, - ctx->key->key.key_rsa) != 1) { + return NULL; + } + if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha256()) <= 0) { + g_string_free(hdr, TRUE); + msg_err_task("rsa sign error: %s", + ERR_error_string(ERR_get_error(), NULL)); + + return NULL; + } + size_t sig_len_size_t = sig_len; + if (EVP_PKEY_sign(pctx, sig_buf, &sig_len_size_t, raw_digest, dlen) <= 0) { g_string_free(hdr, TRUE); msg_err_task("rsa sign error: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -3550,11 +3516,10 @@ rspamd_dkim_sign(struct rspamd_task *task, const char *selector, } } else if (ctx->key->type == RSPAMD_DKIM_KEY_EDDSA) { - sig_len = rspamd_cryptobox_signature_bytes(RSPAMD_CRYPTOBOX_MODE_25519); + sig_len = crypto_sign_bytes(); sig_buf = g_alloca(sig_len); - rspamd_cryptobox_sign(sig_buf, NULL, raw_digest, dlen, - ctx->key->key.key_eddsa, RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_sign(sig_buf, NULL, raw_digest, dlen, ctx->key->specific.key_eddsa); } else { g_string_free(hdr, TRUE); @@ -3595,17 +3560,25 @@ rspamd_dkim_match_keys(rspamd_dkim_key_t *pk, } if (pk->type == RSPAMD_DKIM_KEY_EDDSA) { - if (memcmp(sk->key.key_eddsa + 32, pk->key.key_eddsa, 32) != 0) { + if (memcmp(sk->specific.key_eddsa + 32, pk->specific.key_eddsa, 32) != 0) { g_set_error(err, dkim_error_quark(), DKIM_SIGERROR_KEYHASHMISMATCH, "pubkey does not match private key"); return FALSE; } } - else if (EVP_PKEY_cmp(pk->key_evp, sk->key_evp) != 1) { +#if OPENSSL_VERSION_MAJOR >= 3 + else if (EVP_PKEY_eq(pk->specific.key_ssl.key_evp, sk->specific.key_ssl.key_evp) != 1) { g_set_error(err, dkim_error_quark(), DKIM_SIGERROR_KEYHASHMISMATCH, "pubkey does not match private key"); return FALSE; } +#else + else if (EVP_PKEY_cmp(pk->specific.key_ssl.key_evp, sk->specific.key_ssl.key_evp) != 1) { + g_set_error(err, dkim_error_quark(), DKIM_SIGERROR_KEYHASHMISMATCH, + "pubkey does not match private key"); + return FALSE; + } +#endif return TRUE; } diff --git a/src/libserver/http/http_connection.c b/src/libserver/http/http_connection.c index ef39e11e7..1ae9bb034 100644 --- a/src/libserver/http/http_connection.c +++ b/src/libserver/http/http_connection.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -159,8 +159,7 @@ rspamd_http_parse_key(rspamd_ftok_t *data, struct rspamd_http_connection *conn, if (decoded_id != NULL && id_len >= RSPAMD_KEYPAIR_SHORT_ID_LEN) { pk = rspamd_pubkey_from_base32(eq_pos + 1, data->begin + data->len - eq_pos - 1, - RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + RSPAMD_KEYPAIR_KEX); if (pk != NULL) { if (memcmp(rspamd_keypair_get_id(priv->local_key), decoded_id, @@ -572,21 +571,18 @@ rspamd_http_decrypt_message(struct rspamd_http_connection *conn, struct rspamd_http_header *hdr, *hcur, *hcurtmp; struct http_parser decrypted_parser; struct http_parser_settings decrypted_cb; - enum rspamd_cryptobox_mode mode; - mode = rspamd_keypair_alg(priv->local_key); nonce = msg->body_buf.str; - m = msg->body_buf.str + rspamd_cryptobox_nonce_bytes(mode) + - rspamd_cryptobox_mac_bytes(mode); - dec_len = msg->body_buf.len - rspamd_cryptobox_nonce_bytes(mode) - - rspamd_cryptobox_mac_bytes(mode); + m = msg->body_buf.str + crypto_box_noncebytes() + + crypto_box_macbytes(); + dec_len = msg->body_buf.len - crypto_box_noncebytes() - crypto_box_macbytes(); if ((nm = rspamd_pubkey_get_nm(peer_key, priv->local_key)) == NULL) { nm = rspamd_pubkey_calculate_nm(peer_key, priv->local_key); } if (!rspamd_cryptobox_decrypt_nm_inplace(m, dec_len, nonce, - nm, m - rspamd_cryptobox_mac_bytes(mode), mode)) { + nm, m - crypto_box_macbytes())) { msg_err("cannot verify encrypted message, first bytes of the input: %*xs", (int) MIN(msg->body_buf.len, 64), msg->body_buf.begin); return -1; @@ -640,7 +636,6 @@ rspamd_http_on_message_complete(http_parser *parser) (struct rspamd_http_connection *) parser->data; struct rspamd_http_connection_private *priv; int ret = 0; - enum rspamd_cryptobox_mode mode; if (conn->finished) { return 0; @@ -655,11 +650,10 @@ rspamd_http_on_message_complete(http_parser *parser) } if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) == 0 && IS_CONN_ENCRYPTED(priv)) { - mode = rspamd_keypair_alg(priv->local_key); if (priv->local_key == NULL || priv->msg->peer_key == NULL || - priv->msg->body_buf.len < rspamd_cryptobox_nonce_bytes(mode) + - rspamd_cryptobox_mac_bytes(mode)) { + priv->msg->body_buf.len < crypto_box_noncebytes() + + crypto_box_macbytes()) { msg_err("cannot decrypt message"); return -1; } @@ -1576,10 +1570,8 @@ rspamd_http_connection_encrypt_message( int i, cnt; unsigned int outlen; struct rspamd_http_header *hdr, *hcur; - enum rspamd_cryptobox_mode mode; - mode = rspamd_keypair_alg(priv->local_key); - crlfp = mp + rspamd_cryptobox_mac_bytes(mode); + crlfp = mp + crypto_box_macbytes(); outlen = priv->out[0].iov_len + priv->out[1].iov_len; /* @@ -1632,7 +1624,7 @@ if ((nm = rspamd_pubkey_get_nm(peer_key, priv->local_key)) == NULL) { nm = rspamd_pubkey_calculate_nm(peer_key, priv->local_key); } -rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, np, nm, mp, mode); +rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, np, nm, mp); /* * iov[0] = base HTTP request @@ -1642,12 +1634,12 @@ rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, np, nm, mp, mode); * iov[4..i] = encrypted HTTP request/reply */ priv->out[2].iov_base = np; -priv->out[2].iov_len = rspamd_cryptobox_nonce_bytes(mode); +priv->out[2].iov_len = crypto_box_noncebytes(); priv->out[3].iov_base = mp; -priv->out[3].iov_len = rspamd_cryptobox_mac_bytes(mode); +priv->out[3].iov_len = crypto_box_macbytes(); -outlen += rspamd_cryptobox_nonce_bytes(mode) + - rspamd_cryptobox_mac_bytes(mode); +outlen += crypto_box_noncebytes() + + crypto_box_macbytes(); for (i = 0; i < cnt; i++) { priv->out[i + 4].iov_base = segments[i].data; @@ -2027,7 +2019,6 @@ rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, unsigned char nonce[rspamd_cryptobox_MAX_NONCEBYTES], mac[rspamd_cryptobox_MAX_MACBYTES]; unsigned char *np = NULL, *mp = NULL, *meth_pos = NULL; struct rspamd_cryptobox_pubkey *peer_key = NULL; - enum rspamd_cryptobox_mode mode; GError *err; conn->ud = ud; @@ -2059,8 +2050,7 @@ rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, if (msg->peer_key != NULL) { if (priv->local_key == NULL) { /* Automatically generate a temporary keypair */ - priv->local_key = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + priv->local_key = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); } encrypted = TRUE; @@ -2128,8 +2118,6 @@ rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, } if (encrypted) { - mode = rspamd_keypair_alg(priv->local_key); - if (msg->body_buf.len == 0) { pbody = NULL; bodylen = 0; @@ -2154,8 +2142,8 @@ rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, * [iov[n + 2] = encrypted body] */ priv->outlen = 7; - enclen = rspamd_cryptobox_nonce_bytes(mode) + - rspamd_cryptobox_mac_bytes(mode) + + enclen = crypto_box_noncebytes() + + crypto_box_macbytes() + 4 + /* 2 * CRLF */ bodylen; } @@ -2197,8 +2185,8 @@ rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, ENCRYPTED_VERSION); } - enclen = rspamd_cryptobox_nonce_bytes(mode) + - rspamd_cryptobox_mac_bytes(mode) + + enclen = crypto_box_noncebytes() + + crypto_box_macbytes() + preludelen + /* version [content-length] + 2 * CRLF */ bodylen; } @@ -2275,10 +2263,9 @@ priv->out[0].iov_len = buf->len; /* Buf will be used eventually for encryption */ if (encrypted) { int meth_offset, nonce_offset, mac_offset; - mode = rspamd_keypair_alg(priv->local_key); - ottery_rand_bytes(nonce, rspamd_cryptobox_nonce_bytes(mode)); - memset(mac, 0, rspamd_cryptobox_mac_bytes(mode)); + ottery_rand_bytes(nonce, crypto_box_noncebytes()); + memset(mac, 0, crypto_box_macbytes()); meth_offset = buf->len; if (conn->type == RSPAMD_HTTP_SERVER) { @@ -2292,11 +2279,9 @@ if (encrypted) { } nonce_offset = buf->len; - buf = rspamd_fstring_append(buf, nonce, - rspamd_cryptobox_nonce_bytes(mode)); + buf = rspamd_fstring_append(buf, nonce, crypto_box_noncebytes()); mac_offset = buf->len; - buf = rspamd_fstring_append(buf, mac, - rspamd_cryptobox_mac_bytes(mode)); + buf = rspamd_fstring_append(buf, mac, crypto_box_macbytes()); /* Need to be encrypted */ if (conn->type == RSPAMD_HTTP_SERVER) { @@ -2365,44 +2350,44 @@ if (conn->opts & RSPAMD_HTTP_CLIENT_SSL) { gpointer ssl_ctx = (msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY) ? priv->ctx->ssl_ctx_noverify : priv->ctx->ssl_ctx; if (!ssl_ctx) { - err = g_error_new(HTTP_ERROR, 400, "ssl message requested " - "with no ssl ctx"); - rspamd_http_connection_ref(conn); - conn->error_handler(conn, err); - rspamd_http_connection_unref(conn); - g_error_free(err); - return FALSE; + err = g_error_new(HTTP_ERROR, 400, "ssl message requested " + "with no ssl ctx"); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; } else { - if (!priv->ssl) { - priv->ssl = rspamd_ssl_connection_new(ssl_ctx, priv->ctx->event_loop, - !(msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY), - conn->log_tag); - g_assert(priv->ssl != NULL); - - if (!rspamd_ssl_connect_fd(priv->ssl, conn->fd, host, &priv->ev, - priv->timeout, rspamd_http_event_handler, - rspamd_http_ssl_err_handler, conn)) { - - err = g_error_new(HTTP_ERROR, 400, - "ssl connection error: ssl error=%s, errno=%s", - ERR_error_string(ERR_get_error(), NULL), - strerror(errno)); - rspamd_http_connection_ref(conn); - conn->error_handler(conn, err); - rspamd_http_connection_unref(conn); - g_error_free(err); - return FALSE; - } - } - else { - /* Just restore SSL handlers */ - rspamd_ssl_connection_restore_handlers(priv->ssl, - rspamd_http_event_handler, - rspamd_http_ssl_err_handler, - conn, - EV_WRITE); + if (!priv->ssl) { + priv->ssl = rspamd_ssl_connection_new(ssl_ctx, priv->ctx->event_loop, + !(msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY), + conn->log_tag); + g_assert(priv->ssl != NULL); + + if (!rspamd_ssl_connect_fd(priv->ssl, conn->fd, host, &priv->ev, + priv->timeout, rspamd_http_event_handler, + rspamd_http_ssl_err_handler, conn)) { + + err = g_error_new(HTTP_ERROR, 400, + "ssl connection error: ssl error=%s, errno=%s", + ERR_error_string(ERR_get_error(), NULL), + strerror(errno)); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; } + } + else { + /* Just restore SSL handlers */ + rspamd_ssl_connection_restore_handlers(priv->ssl, + rspamd_http_event_handler, + rspamd_http_ssl_err_handler, + conn, + EV_WRITE); + } } } else { @@ -2467,10 +2452,10 @@ rspamd_http_connection_get_peer_key(struct rspamd_http_connection *conn) struct rspamd_http_connection_private *priv = conn->priv; if (priv->peer_key) { - return priv->peer_key; + return priv->peer_key; } else if (priv->msg) { - return priv->msg->peer_key; + return priv->msg->peer_key; } return NULL; @@ -2482,10 +2467,10 @@ rspamd_http_connection_is_encrypted(struct rspamd_http_connection *conn) struct rspamd_http_connection_private *priv = conn->priv; if (priv->peer_key != NULL) { - return TRUE; + return TRUE; } else if (priv->msg) { - return priv->msg->peer_key != NULL; + return priv->msg->peer_key != NULL; } return FALSE; @@ -2512,103 +2497,103 @@ rspamd_http_message_parse_query(struct rspamd_http_message *msg) rspamd_fstring_mapped_ftok_free); if (msg->url && msg->url->len > 0) { - http_parser_parse_url(msg->url->str, msg->url->len, TRUE, &u); - - if (u.field_set & (1 << UF_QUERY)) { - p = msg->url->str + u.field_data[UF_QUERY].off; - c = p; - end = p + u.field_data[UF_QUERY].len; - - while (p <= end) { - switch (state) { - case parse_key: - if ((p == end || *p == '&') && p > c) { - /* We have a single parameter without a value */ - key = rspamd_fstring_new_init(c, p - c); - key_tok = rspamd_ftok_map(key); - key_tok->len = rspamd_url_decode(key->str, key->str, - key->len); - - value = rspamd_fstring_new_init("", 0); - value_tok = rspamd_ftok_map(value); + http_parser_parse_url(msg->url->str, msg->url->len, TRUE, &u); + + if (u.field_set & (1 << UF_QUERY)) { + p = msg->url->str + u.field_data[UF_QUERY].off; + c = p; + end = p + u.field_data[UF_QUERY].len; + + while (p <= end) { + switch (state) { + case parse_key: + if ((p == end || *p == '&') && p > c) { + /* We have a single parameter without a value */ + key = rspamd_fstring_new_init(c, p - c); + key_tok = rspamd_ftok_map(key); + key_tok->len = rspamd_url_decode(key->str, key->str, + key->len); + + value = rspamd_fstring_new_init("", 0); + value_tok = rspamd_ftok_map(value); + + g_hash_table_replace(res, key_tok, value_tok); + state = parse_ampersand; + } + else if (*p == '=' && p > c) { + /* We have something like key=value */ + key = rspamd_fstring_new_init(c, p - c); + key_tok = rspamd_ftok_map(key); + key_tok->len = rspamd_url_decode(key->str, key->str, + key->len); + + state = parse_eqsign; + } + else { + p++; + } + break; - g_hash_table_replace(res, key_tok, value_tok); - state = parse_ampersand; - } - else if (*p == '=' && p > c) { - /* We have something like key=value */ - key = rspamd_fstring_new_init(c, p - c); - key_tok = rspamd_ftok_map(key); - key_tok->len = rspamd_url_decode(key->str, key->str, - key->len); - - state = parse_eqsign; - } - else { - p++; - } - break; + case parse_eqsign: + if (*p != '=') { + c = p; + state = parse_value; + } + else { + p++; + } + break; - case parse_eqsign: - if (*p != '=') { - c = p; - state = parse_value; - } - else { - p++; - } - break; - - case parse_value: - if ((p == end || *p == '&') && p >= c) { - g_assert(key != NULL); - if (p > c) { - value = rspamd_fstring_new_init(c, p - c); - value_tok = rspamd_ftok_map(value); - value_tok->len = rspamd_url_decode(value->str, - value->str, - value->len); - /* Detect quotes for value */ - if (value_tok->begin[0] == '"') { - memmove(value->str, value->str + 1, - value_tok->len - 1); - value_tok->len--; - } - if (value_tok->begin[value_tok->len - 1] == '"') { - value_tok->len--; - } + case parse_value: + if ((p == end || *p == '&') && p >= c) { + g_assert(key != NULL); + if (p > c) { + value = rspamd_fstring_new_init(c, p - c); + value_tok = rspamd_ftok_map(value); + value_tok->len = rspamd_url_decode(value->str, + value->str, + value->len); + /* Detect quotes for value */ + if (value_tok->begin[0] == '"') { + memmove(value->str, value->str + 1, + value_tok->len - 1); + value_tok->len--; } - else { - value = rspamd_fstring_new_init("", 0); - value_tok = rspamd_ftok_map(value); + if (value_tok->begin[value_tok->len - 1] == '"') { + value_tok->len--; } - - g_hash_table_replace(res, key_tok, value_tok); - key = value = NULL; - key_tok = value_tok = NULL; - state = parse_ampersand; } else { - p++; + value = rspamd_fstring_new_init("", 0); + value_tok = rspamd_ftok_map(value); } - break; - case parse_ampersand: - if (p != end && *p != '&') { - c = p; - state = parse_key; - } - else { - p++; - } - break; + g_hash_table_replace(res, key_tok, value_tok); + key = value = NULL; + key_tok = value_tok = NULL; + state = parse_ampersand; + } + else { + p++; + } + break; + + case parse_ampersand: + if (p != end && *p != '&') { + c = p; + state = parse_key; + } + else { + p++; } + break; } } + } - if (state != parse_ampersand && key != NULL) { - rspamd_fstring_free(key); - } + if (state != parse_ampersand && key != NULL) { + rspamd_fstring_free(key); + } } return res; @@ -2635,15 +2620,15 @@ void rspamd_http_connection_disable_encryption(struct rspamd_http_connection *co priv = conn->priv; if (priv) { - if (priv->local_key) { - rspamd_keypair_unref(priv->local_key); - } - if (priv->peer_key) { - rspamd_pubkey_unref(priv->peer_key); - } + if (priv->local_key) { + rspamd_keypair_unref(priv->local_key); + } + if (priv->peer_key) { + rspamd_pubkey_unref(priv->peer_key); + } - priv->local_key = NULL; - priv->peer_key = NULL; - priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + priv->local_key = NULL; + priv->peer_key = NULL; + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; } }
\ No newline at end of file diff --git a/src/libserver/http/http_context.c b/src/libserver/http/http_context.c index fe9412fe2..df32a2258 100644 --- a/src/libserver/http/http_context.c +++ b/src/libserver/http/http_context.c @@ -77,8 +77,7 @@ rspamd_http_context_client_rotate_ev(struct ev_loop *loop, ev_timer *w, int reve ev_timer_again(loop, w); kp = ctx->client_kp; - ctx->client_kp = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + ctx->client_kp = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); rspamd_keypair_unref(kp); } diff --git a/src/libserver/logger/logger.c b/src/libserver/logger/logger.c index 13bac5cbe..25818e7a5 100644 --- a/src/libserver/logger/logger.c +++ b/src/libserver/logger/logger.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -277,8 +277,7 @@ rspamd_log_open_specific(rspamd_mempool_t *pool, if (cfg->log_encryption_key) { logger->pk = rspamd_pubkey_ref(cfg->log_encryption_key); - logger->keypair = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + logger->keypair = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); rspamd_pubkey_calculate_nm(logger->pk, logger->keypair); } } @@ -342,9 +341,9 @@ rspamd_log_encrypt_message(const char *begin, const char *end, gsize *enc_len, g_assert(end > begin); /* base64 (pubkey | nonce | message) */ - inlen = rspamd_cryptobox_nonce_bytes(RSPAMD_CRYPTOBOX_MODE_25519) + - rspamd_cryptobox_pk_bytes(RSPAMD_CRYPTOBOX_MODE_25519) + - rspamd_cryptobox_mac_bytes(RSPAMD_CRYPTOBOX_MODE_25519) + + inlen = crypto_box_noncebytes() + + crypto_box_publickeybytes() + + crypto_box_macbytes() + (end - begin); out = g_malloc(inlen); @@ -352,16 +351,15 @@ rspamd_log_encrypt_message(const char *begin, const char *end, gsize *enc_len, comp = rspamd_pubkey_get_pk(rspamd_log->pk, &len); memcpy(p, comp, len); p += len; - ottery_rand_bytes(p, rspamd_cryptobox_nonce_bytes(RSPAMD_CRYPTOBOX_MODE_25519)); + ottery_rand_bytes(p, crypto_box_noncebytes()); nonce = p; - p += rspamd_cryptobox_nonce_bytes(RSPAMD_CRYPTOBOX_MODE_25519); + p += crypto_box_noncebytes(); mac = p; - p += rspamd_cryptobox_mac_bytes(RSPAMD_CRYPTOBOX_MODE_25519); + p += crypto_box_macbytes(); memcpy(p, begin, end - begin); comp = rspamd_pubkey_get_nm(rspamd_log->pk, rspamd_log->keypair); g_assert(comp != NULL); - rspamd_cryptobox_encrypt_nm_inplace(p, end - begin, nonce, comp, mac, - RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_encrypt_nm_inplace(p, end - begin, nonce, comp, mac); b64 = rspamd_encode_base64(out, inlen, 0, enc_len); g_free(out); diff --git a/src/libserver/maps/map.c b/src/libserver/maps/map.c index 15fce7e9d..631455755 100644 --- a/src/libserver/maps/map.c +++ b/src/libserver/maps/map.c @@ -670,14 +670,14 @@ rspamd_map_check_sig_pk_mem(const unsigned char *sig, GString *b32_key; gboolean ret = TRUE; - if (siglen != rspamd_cryptobox_signature_bytes(RSPAMD_CRYPTOBOX_MODE_25519)) { + if (siglen != crypto_sign_bytes()) { msg_err_map("can't open signature for %s: invalid size: %z", map->name, siglen); ret = FALSE; } if (ret && !rspamd_cryptobox_verify(sig, siglen, input, inlen, - rspamd_pubkey_get_pk(pk, NULL), RSPAMD_CRYPTOBOX_MODE_25519)) { + rspamd_pubkey_get_pk(pk, NULL))) { msg_err_map("can't verify signature for %s: incorrect signature", map->name); ret = FALSE; @@ -718,8 +718,7 @@ rspamd_map_check_file_sig(const char *fname, return FALSE; } - pk = rspamd_pubkey_from_base32(data, len, RSPAMD_KEYPAIR_SIGN, - RSPAMD_CRYPTOBOX_MODE_25519); + pk = rspamd_pubkey_from_base32(data, len, RSPAMD_KEYPAIR_SIGN); munmap(data, len); if (pk == NULL) { @@ -2414,8 +2413,7 @@ rspamd_map_check_proto(struct rspamd_config *cfg, end_key = memchr(pos, '+', end - pos); if (end_key != NULL) { - bk->trusted_pubkey = rspamd_pubkey_from_base32(pos, end_key - pos, - RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); + bk->trusted_pubkey = rspamd_pubkey_from_base32(pos, end_key - pos, RSPAMD_KEYPAIR_SIGN); if (bk->trusted_pubkey == NULL) { msg_err_config("cannot read pubkey from map: %s", @@ -2426,8 +2424,7 @@ rspamd_map_check_proto(struct rspamd_config *cfg, } else if (end - pos > 64) { /* Try hex encoding */ - bk->trusted_pubkey = rspamd_pubkey_from_hex(pos, 64, - RSPAMD_KEYPAIR_SIGN, RSPAMD_CRYPTOBOX_MODE_25519); + bk->trusted_pubkey = rspamd_pubkey_from_hex(pos, 64, RSPAMD_KEYPAIR_SIGN); if (bk->trusted_pubkey == NULL) { msg_err_config("cannot read pubkey from map: %s", diff --git a/src/libserver/milter.c b/src/libserver/milter.c index f35278a0e..94b0d6cc1 100644 --- a/src/libserver/milter.c +++ b/src/libserver/milter.c @@ -1465,10 +1465,16 @@ rspamd_milter_macro_http(struct rspamd_milter_session *session, return; } + /* + * When we get a queue-id we try to pass it to the backend, where possible + * We also need that for logging consistency + */ IF_MACRO("{i}") { rspamd_http_message_add_header_len(msg, QUEUE_ID_HEADER, found->begin, found->len); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, + found->begin, found->len); } else { @@ -1476,6 +1482,8 @@ rspamd_milter_macro_http(struct rspamd_milter_session *session, { rspamd_http_message_add_header_len(msg, QUEUE_ID_HEADER, found->begin, found->len); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, + found->begin, found->len); } } diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c index db83b0bfb..ee2192913 100644 --- a/src/libserver/protocol.c +++ b/src/libserver/protocol.c @@ -660,9 +660,9 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, IF_HEADER(USER_HEADER) { /* - * We must ignore User header in case of spamc, as SA has - * different meaning of this header - */ + * We must ignore User header in case of spamc, as SA has + * different meaning of this header + */ msg_debug_protocol("read user header, value: %T", hv_tok); if (!RSPAMD_TASK_IS_SPAMC(task)) { task->auth_user = rspamd_mempool_ftokdup(task->task_pool, @@ -708,6 +708,15 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, task->flags |= RSPAMD_TASK_FLAG_NO_LOG; } } + IF_HEADER(LOG_TAG_HEADER) + { + msg_debug_protocol("read log-tag header, value: %T", hv_tok); + /* Ensure that a tag is valid */ + if (rspamd_fast_utf8_validate(hv_tok->begin, hv_tok->len) == 0) { + memcpy(task->task_pool->tag.uid, hv_tok->begin, + MIN(hv_tok->len, sizeof(task->task_pool->tag.uid))); + } + } break; case 'm': case 'M': @@ -752,9 +761,9 @@ rspamd_protocol_handle_headers(struct rspamd_task *task, default: msg_debug_protocol("generic header: %T", hn_tok); break; - } + } - rspamd_task_add_request_header (task, hn_tok, hv_tok); + rspamd_task_add_request_header (task, hn_tok, hv_tok); } }); /* End of kh_foreach_value */ diff --git a/src/libserver/protocol_internal.h b/src/libserver/protocol_internal.h index 7a70ccef0..e55e54851 100644 --- a/src/libserver/protocol_internal.h +++ b/src/libserver/protocol_internal.h @@ -78,6 +78,7 @@ extern "C" { #define HOSTNAME_HEADER "Hostname" #define DELIVER_TO_HEADER "Deliver-To" #define NO_LOG_HEADER "Log" +#define LOG_TAG_HEADER "Log-Tag" #define MLEN_HEADER "Message-Length" #define USER_AGENT_HEADER "User-Agent" #define MTA_TAG_HEADER "MTA-Tag" diff --git a/src/libserver/spf.c b/src/libserver/spf.c index 32c020bf3..562222042 100644 --- a/src/libserver/spf.c +++ b/src/libserver/spf.c @@ -451,6 +451,9 @@ rspamd_spf_process_reference(struct spf_resolved *target, target->flags |= RSPAMD_SPF_RESOLVED_NA; continue; } + if (cur->flags & RSPAMD_SPF_FLAG_PLUSALL) { + target->flags |= RSPAMD_SPF_RESOLVED_PLUSALL; + } if (cur->flags & RSPAMD_SPF_FLAG_INVALID) { /* Ignore invalid elements */ continue; @@ -1418,7 +1421,7 @@ parse_spf_all(struct spf_record *rec, struct spf_addr *addr) /* Disallow +all */ if (addr->mech == SPF_PASS) { - addr->flags |= RSPAMD_SPF_FLAG_INVALID; + addr->flags |= RSPAMD_SPF_FLAG_PLUSALL; msg_notice_spf("domain %s allows any SPF (+all), ignore SPF record completely", rec->sender_domain); } diff --git a/src/libserver/spf.h b/src/libserver/spf.h index cc0ee4c05..b89dc4d0e 100644 --- a/src/libserver/spf.h +++ b/src/libserver/spf.h @@ -77,6 +77,7 @@ typedef enum spf_action_e { #define RSPAMD_SPF_FLAG_PERMFAIL (1u << 10u) #define RSPAMD_SPF_FLAG_RESOLVED (1u << 11u) #define RSPAMD_SPF_FLAG_CACHED (1u << 12u) +#define RSPAMD_SPF_FLAG_PLUSALL (1u << 13u) /** Default SPF limits for avoiding abuse **/ #define SPF_MAX_NESTING 10 @@ -104,6 +105,7 @@ enum rspamd_spf_resolved_flags { RSPAMD_SPF_RESOLVED_TEMP_FAILED = (1u << 0u), RSPAMD_SPF_RESOLVED_PERM_FAILED = (1u << 1u), RSPAMD_SPF_RESOLVED_NA = (1u << 2u), + RSPAMD_SPF_RESOLVED_PLUSALL = (1u << 3u), }; struct spf_resolved { diff --git a/src/libserver/symcache/symcache_runtime.cxx b/src/libserver/symcache/symcache_runtime.cxx index dc7066b32..ddfbdf1ae 100644 --- a/src/libserver/symcache/symcache_runtime.cxx +++ b/src/libserver/symcache/symcache_runtime.cxx @@ -333,8 +333,8 @@ auto symcache_runtime::process_pre_postfilters(struct rspamd_task *task, */ if (stage != RSPAMD_TASK_STAGE_IDEMPOTENT && !(item->flags & SYMBOL_TYPE_IGNORE_PASSTHROUGH)) { - if (check_metric_limit(task)) { - msg_debug_cache_task_lambda("task has already the result being set, ignore further checks"); + if (check_process_status(task) == check_status::passthrough) { + msg_debug_cache_task_lambda("task has already the passthrough result being set, ignore further checks"); return true; } @@ -407,13 +407,20 @@ auto symcache_runtime::process_filters(struct rspamd_task *task, symcache &cache break; } + auto check_result = check_process_status(task); + if (!(item->flags & (SYMBOL_TYPE_FINE | SYMBOL_TYPE_IGNORE_PASSTHROUGH))) { - if (has_passtrough || check_metric_limit(task)) { - msg_debug_cache_task_lambda("task has already the result being set, ignore further checks"); + if (has_passtrough || check_result == check_status::passthrough) { + msg_debug_cache_task_lambda("task has already the passthrough result being set, ignore further checks"); has_passtrough = true; /* Skip this item */ continue; } + else if (check_result == check_status::limit_reached) { + msg_debug_cache_task_lambda("task has already the limit reached result being set, ignore further checks"); + /* Skip this item */ + continue; + } } auto dyn_item = &dynamic_items[idx]; @@ -531,19 +538,8 @@ auto symcache_runtime::process_symbol(struct rspamd_task *task, symcache &cache, return true; } -auto symcache_runtime::check_metric_limit(struct rspamd_task *task) -> bool +auto symcache_runtime::check_process_status(struct rspamd_task *task) -> symcache_runtime::check_status { - if (task->flags & RSPAMD_TASK_FLAG_PASS_ALL) { - return false; - } - - /* Check score limit */ - if (!std::isnan(lim)) { - if (task->result->score > lim) { - return true; - } - } - if (task->result->passthrough_result != nullptr) { /* We also need to check passthrough results */ auto *pr = task->result->passthrough_result; @@ -563,11 +559,22 @@ auto symcache_runtime::check_metric_limit(struct rspamd_task *task) -> bool } /* Immediately stop on non least passthrough action */ - return true; + return check_status::passthrough; } } - return false; + if (task->flags & RSPAMD_TASK_FLAG_PASS_ALL) { + return check_status::allow; + } + + /* Check score limit */ + if (!std::isnan(lim)) { + if (task->result->score > lim) { + return check_status::limit_reached; + } + } + + return check_status::allow; } auto symcache_runtime::check_item_deps(struct rspamd_task *task, symcache &cache, cache_item *item, diff --git a/src/libserver/symcache/symcache_runtime.hxx b/src/libserver/symcache/symcache_runtime.hxx index 7e4a41269..d1dc7ac24 100644 --- a/src/libserver/symcache/symcache_runtime.hxx +++ b/src/libserver/symcache/symcache_runtime.hxx @@ -60,6 +60,11 @@ class symcache_runtime { enabled = 1, disabled = 2, } slow_status; + enum class check_status { + allow, + limit_reached, + passthrough, + }; bool profile; double profile_start; @@ -77,7 +82,7 @@ class symcache_runtime { /* Specific stages of the processing */ auto process_pre_postfilters(struct rspamd_task *task, symcache &cache, int start_events, unsigned int stage) -> bool; auto process_filters(struct rspamd_task *task, symcache &cache, int start_events) -> bool; - auto check_metric_limit(struct rspamd_task *task) -> bool; + auto check_process_status(struct rspamd_task *task) -> check_status; auto check_item_deps(struct rspamd_task *task, symcache &cache, cache_item *item, cache_dynamic_item *dyn_item, bool check_only) -> bool; diff --git a/src/libstat/stat_process.c b/src/libstat/stat_process.c index ad976e713..5db3af6ce 100644 --- a/src/libstat/stat_process.c +++ b/src/libstat/stat_process.c @@ -509,6 +509,14 @@ rspamd_stat_classify(struct rspamd_task *task, lua_State *L, unsigned int stage, return ret; } + if (task->message == NULL) { + ret = RSPAMD_STAT_PROCESS_ERROR; + msg_err_task("trying to classify empty message"); + + task->processed_stages |= stage; + return ret; + } + if (stage == RSPAMD_TASK_STAGE_CLASSIFIERS_PRE) { /* Preprocess tokens */ rspamd_stat_preprocess(st_ctx, task, FALSE, FALSE); @@ -892,6 +900,18 @@ rspamd_stat_learn(struct rspamd_task *task, return ret; } + + if (task->message == NULL) { + ret = RSPAMD_STAT_PROCESS_ERROR; + if (err && *err == NULL) { + g_set_error(err, rspamd_stat_quark(), 500, + "Trying to learn an empty message"); + } + + task->processed_stages |= stage; + return ret; + } + if (stage == RSPAMD_TASK_STAGE_LEARN_PRE) { /* Process classifiers */ rspamd_stat_preprocess(st_ctx, task, TRUE, spam); diff --git a/src/lua/lua_config.c b/src/lua/lua_config.c index f5405f76d..e3f8b2e57 100644 --- a/src/lua/lua_config.c +++ b/src/lua/lua_config.c @@ -34,7 +34,11 @@ local function foo(task) -- do something end -rspamd_config:register_symbol('SYMBOL', 1.0, foo) +rspamd_config:register_symbol{ + name = 'SYMBOL', + score = 1.0, + callback = foo +} -- Get configuration local tab = rspamd_config:get_all_opt('module') -- get table for module's options @@ -1965,210 +1969,454 @@ lua_config_get_symbol_flags(lua_State *L) return 1; } -static int -lua_config_register_symbol(lua_State *L) +static bool +lua_config_register_symbol_from_table(lua_State *L, struct rspamd_config *cfg, + const char *name, int tbl_idx, int *id_out) { - LUA_TRACE_POINT; - struct rspamd_config *cfg = lua_check_config(L, 1); - const char *name = NULL, *type_str = NULL, - *description = NULL, *group = NULL; - double weight = 0, score = NAN, parent_float = NAN; - gboolean one_shot = FALSE; - int ret = -1, cbref = -1; - unsigned int type = 0, flags = 0; - int64_t parent = 0, priority = 0, nshots = 0; + unsigned int type = SYMBOL_TYPE_NORMAL, priority = 0; + double weight = 1.0, score = NAN; + const char *type_str, *group = NULL, *description = NULL; GArray *allowed_ids = NULL, *forbidden_ids = NULL; - GError *err = NULL; - int prev_top = lua_gettop(L); + int id, nshots, cb_ref, parent = -1; + unsigned int flags = 0; + gboolean optional = FALSE; - if (cfg) { - if (!rspamd_lua_parse_table_arguments(L, 2, &err, - RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, - "name=S;weight=N;callback=F;type=S;priority=I;parent=D;" - "score=D;description=S;group=S;one_shot=B;nshots=I", - &name, &weight, &cbref, &type_str, - &priority, &parent_float, - &score, &description, &group, &one_shot, &nshots)) { - msg_err_config("bad arguments: %e", err); - g_error_free(err); - lua_settop(L, prev_top); + /* + * Table can have the following attributes: + * "callback" - should be a callback function + * "weight" - optional weight + * "priority" - optional priority + * "type" - optional type (normal, virtual, callback) + * "flags" - optional flags + * -- Metric options + * "score" - optional default score (overridden by metric) + * "group" - optional default group + * "one_shot" - optional one shot mode + * "description" - optional description + */ + lua_pushvalue(L, tbl_idx); /* Push table on top of the stack */ - return luaL_error(L, "invalid arguments"); - } + if (name == NULL) { + /* Try to resolve name */ + lua_pushstring(L, "name"); + lua_gettable(L, -2); - /* Deal with flags and ids */ - lua_pushstring(L, "flags"); - lua_gettable(L, 2); - if (lua_type(L, -1) == LUA_TSTRING) { - flags = lua_parse_symbol_flags(lua_tostring(L, -1)); - } - else if (lua_type(L, -1) == LUA_TTABLE) { - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - flags |= lua_parse_symbol_flags(lua_tostring(L, -1)); - } - } - lua_pop(L, 1); /* Clean flags */ + if (lua_type(L, -1) != LUA_TSTRING) { + lua_pop(L, 2); + luaL_error(L, "name is not specified"); - lua_pushstring(L, "allowed_ids"); - lua_gettable(L, 2); - if (lua_type(L, -1) == LUA_TSTRING) { - allowed_ids = rspamd_process_id_list(lua_tostring(L, -1)); + return false; } - else if (lua_type(L, -1) == LUA_TTABLE) { - allowed_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), - rspamd_lua_table_size(L, -1)); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - uint32_t v = lua_tointeger(L, -1); - g_array_append_val(allowed_ids, v); - } + else { + name = lua_tostring(L, -1); } + lua_pop(L, 1); + } - lua_pushstring(L, "forbidden_ids"); - lua_gettable(L, 2); - if (lua_type(L, -1) == LUA_TSTRING) { - forbidden_ids = rspamd_process_id_list(lua_tostring(L, -1)); + lua_pushstring(L, "callback"); + lua_gettable(L, -2); + + if (lua_type(L, -1) != LUA_TFUNCTION) { + cb_ref = -1; + } + else { + cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + lua_pop(L, 1); + + /* Optional fields */ + lua_pushstring(L, "weight"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TNUMBER) { + weight = lua_tonumber(L, -1); + } + lua_pop(L, 1); + + lua_pushstring(L, "priority"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TNUMBER) { + priority = lua_tointeger(L, -1); + } + lua_pop(L, 1); + + lua_pushstring(L, "optional"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TBOOLEAN) { + optional = lua_toboolean(L, -1); + } + lua_pop(L, 1); + + lua_pushstring(L, "type"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TSTRING) { + type_str = lua_tostring(L, -1); + } + else { + type_str = "normal"; + } + lua_pop(L, 1); + + type = lua_parse_symbol_type(type_str); + + if (!name && !(type & SYMBOL_TYPE_CALLBACK)) { + luaL_error(L, "no symbol name but type is not callback"); + + return false; + } + else if (!(type & SYMBOL_TYPE_VIRTUAL) && cb_ref == -1) { + luaL_error(L, "no callback for symbol %s", name); + + return false; + } + + lua_pushstring(L, "parent"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TNUMBER) { + parent = lua_tointeger(L, -1); + } + lua_pop(L, 1); + + + /* Deal with flags and ids */ + lua_pushstring(L, "flags"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + flags = lua_parse_symbol_flags(lua_tostring(L, -1)); + } + else if (lua_type(L, -1) == LUA_TTABLE) { + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + flags |= lua_parse_symbol_flags(lua_tostring(L, -1)); } - else if (lua_type(L, -1) == LUA_TTABLE) { - forbidden_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), - rspamd_lua_table_size(L, -1)); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - uint32_t v = lua_tointeger(L, -1); - g_array_append_val(forbidden_ids, v); - } + } + lua_pop(L, 1); /* Clean flags */ + + lua_pushstring(L, "allowed_ids"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + allowed_ids = rspamd_process_id_list(lua_tostring(L, -1)); + } + else if (lua_type(L, -1) == LUA_TTABLE) { + allowed_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), + rspamd_lua_table_size(L, -1)); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + uint32_t v = lua_tointeger(L, -1); + g_array_append_val(allowed_ids, v); } - lua_pop(L, 1); + } + lua_pop(L, 1); - if (nshots == 0) { - nshots = cfg->default_max_shots; + lua_pushstring(L, "forbidden_ids"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + forbidden_ids = rspamd_process_id_list(lua_tostring(L, -1)); + } + else if (lua_type(L, -1) == LUA_TTABLE) { + forbidden_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), + rspamd_lua_table_size(L, -1)); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + uint32_t v = lua_tointeger(L, -1); + g_array_append_val(forbidden_ids, v); } + } + lua_pop(L, 1); - type = lua_parse_symbol_type(type_str); + id = rspamd_register_symbol_fromlua(L, + cfg, + name, + cb_ref, + weight, + priority, + type | flags, + parent, + allowed_ids, forbidden_ids, + optional); - if (!name && !(type & SYMBOL_TYPE_CALLBACK)) { - lua_settop(L, prev_top); - return luaL_error(L, "no symbol name but type is not callback"); - } - else if (!(type & SYMBOL_TYPE_VIRTUAL) && cbref == -1) { - lua_settop(L, prev_top); - return luaL_error(L, "no callback for symbol %s", name); - } + if (allowed_ids) { + g_array_free(allowed_ids, TRUE); + } + + if (forbidden_ids) { + g_array_free(forbidden_ids, TRUE); + } + + if (id != -1) { + if (cb_ref != -1) { + /* Check for condition */ + lua_pushstring(L, "condition"); + lua_gettable(L, -2); - if (isnan(parent_float)) { - parent = -1; + if (lua_type(L, -1) == LUA_TFUNCTION) { + int condref; + + /* Here we pop function from the stack, so no lua_pop is required */ + condref = luaL_ref(L, LUA_REGISTRYINDEX); + g_assert(name != NULL); + rspamd_symcache_add_condition_delayed(cfg->cache, + name, L, condref); + } + else { + lua_pop(L, 1); + } } - else { - parent = parent_float; + + /* Check for augmentations */ + lua_pushstring(L, "augmentations"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TTABLE) { + + int aug_tbl_idx = lua_gettop(L); + for (lua_pushnil(L); lua_next(L, aug_tbl_idx); lua_pop(L, 1)) { + rspamd_symcache_add_symbol_augmentation(cfg->cache, id, + lua_tostring(L, -1), NULL); + } } - ret = rspamd_register_symbol_fromlua(L, - cfg, - name, - cbref, - weight == 0 ? 1.0 : weight, - priority, - type | flags, - parent, - allowed_ids, forbidden_ids, - FALSE); + lua_pop(L, 1); + } - if (allowed_ids) { - g_array_free(allowed_ids, TRUE); + /* + * Now check if a symbol has not been registered in any metric and + * insert default value if applicable + */ + struct rspamd_symbol *sym = g_hash_table_lookup(cfg->symbols, name); + if (sym == NULL || (sym->flags & RSPAMD_SYMBOL_FLAG_UNSCORED)) { + nshots = cfg->default_max_shots; + + lua_pushstring(L, "score"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TNUMBER) { + score = lua_tonumber(L, -1); + + if (sym) { + /* Reset unscored flag */ + sym->flags &= ~RSPAMD_SYMBOL_FLAG_UNSCORED; + } } + lua_pop(L, 1); - if (forbidden_ids) { - g_array_free(forbidden_ids, TRUE); + lua_pushstring(L, "group"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + group = lua_tostring(L, -1); } + lua_pop(L, 1); + + if (!isnan(score) || group != NULL) { + lua_pushstring(L, "description"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TSTRING) { + description = lua_tostring(L, -1); + } + lua_pop(L, 1); + + lua_pushstring(L, "one_shot"); + lua_gettable(L, -2); - if (ret != -1) { - if (!isnan(score) || group) { - if (one_shot) { + if (lua_type(L, -1) == LUA_TBOOLEAN) { + if (lua_toboolean(L, -1)) { nshots = 1; } + } + lua_pop(L, 1); - rspamd_config_add_symbol(cfg, name, - score, description, group, flags, - 0, nshots); + lua_pushstring(L, "one_param"); + lua_gettable(L, -2); - lua_pushstring(L, "groups"); - lua_gettable(L, 2); + if (lua_type(L, -1) == LUA_TBOOLEAN) { + if (lua_toboolean(L, -1)) { + flags |= RSPAMD_SYMBOL_FLAG_ONEPARAM; + } + } + lua_pop(L, 1); - if (lua_istable(L, -1)) { - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - if (lua_isstring(L, -1)) { - rspamd_config_add_symbol_group(cfg, name, - lua_tostring(L, -1)); - } - else { - lua_settop(L, prev_top); - return luaL_error(L, "invalid groups element"); - } + /* + * Do not override the existing symbols (using zero priority), + * since we are defining default values here + */ + if (!isnan(score)) { + rspamd_config_add_symbol(cfg, name, score, + description, group, flags, 0, nshots); + } + else if (group) { + /* Add with zero score */ + rspamd_config_add_symbol(cfg, name, NAN, + description, group, flags, 0, nshots); + } + + lua_pushstring(L, "groups"); + lua_gettable(L, -2); + + if (lua_istable(L, -1)) { + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + if (lua_isstring(L, -1)) { + rspamd_config_add_symbol_group(cfg, name, + lua_tostring(L, -1)); + } + else { + lua_pop(L, 2); + luaL_error(L, "invalid groups element"); + + return false; } } + } - lua_pop(L, 1); + lua_pop(L, 1); + } + } + else { + /* Fill in missing fields from lua definition if they are not set */ + if (sym->description == NULL) { + lua_pushstring(L, "description"); + lua_gettable(L, -2); + + if (lua_type(L, -1) == LUA_TSTRING) { + description = lua_tostring(L, -1); } + lua_pop(L, 1); - lua_pushstring(L, "augmentations"); - lua_gettable(L, 2); + if (description) { + sym->description = rspamd_mempool_strdup(cfg->cfg_pool, description); + } + } - if (lua_type(L, -1) == LUA_TTABLE) { - int tbl_idx = lua_gettop(L); - for (lua_pushnil(L); lua_next(L, tbl_idx); lua_pop(L, 1)) { - size_t len; - const char *augmentation = lua_tolstring(L, -1, &len), *eqsign_pos; + /* If ungrouped and there is a group defined in lua, change the primary group + * Otherwise, add to the list of groups for this symbol. */ + lua_pushstring(L, "group"); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + group = lua_tostring(L, -1); + } + lua_pop(L, 1); + if (group) { + if (sym->flags & RSPAMD_SYMBOL_FLAG_UNGROUPED) { + /* Unset the "ungrouped" group */ + sym->gr = NULL; + } + /* Add the group. If the symbol was ungrouped, this will + * clear RSPAMD_SYMBOL_FLAG_UNGROUPED from the flags. */ + rspamd_config_add_symbol_group(cfg, name, group); + } + } - /* Find `=` symbol and use it as a separator */ - eqsign_pos = memchr(augmentation, '=', len); - if (eqsign_pos != NULL && eqsign_pos + 1 < augmentation + len) { - rspamd_ftok_t tok; + /* Remove table from stack */ + lua_pop(L, 1); - tok.begin = augmentation; - tok.len = eqsign_pos - augmentation; - char *augentation_name = rspamd_ftokdup(&tok); + *id_out = id; - tok.begin = eqsign_pos + 1; - tok.len = (augmentation + len) - tok.begin; + return true; +} - char *augmentation_value = rspamd_ftokdup(&tok); +/* Legacy symbol registration */ +static bool +lua_config_register_symbol_legacy(lua_State *L, struct rspamd_config *cfg, int pos, int *id_out) +{ + const char *name = NULL, *type_str = NULL, + *description = NULL, *group = NULL; + double weight = 0, score = NAN, parent_float = NAN; + gboolean one_shot = FALSE; + int ret, cbref = -1; + unsigned int type = 0, flags = 0; + int64_t parent = 0, priority = 0, nshots = 0; - if (!rspamd_symcache_add_symbol_augmentation(cfg->cache, ret, - augentation_name, augmentation_value)) { - lua_settop(L, prev_top); - g_free(augmentation_value); - g_free(augentation_name); + GError *err = NULL; + if (!rspamd_lua_parse_table_arguments(L, pos, &err, + RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, + "name=S;weight=N;callback=F;type=S;priority=I;parent=D;" + "score=D;description=S;group=S;one_shot=B;nshots=I", + &name, &weight, &cbref, &type_str, + &priority, &parent_float, + &score, &description, &group, &one_shot, &nshots)) { + msg_err_config("bad arguments: %e", err); + g_error_free(err); - return luaL_error(L, "unknown or invalid augmentation %s in symbol %s", - augmentation, name); - } + return false; + } - g_free(augmentation_value); - g_free(augentation_name); - } - else { - /* Just a value */ - if (!rspamd_symcache_add_symbol_augmentation(cfg->cache, ret, - augmentation, NULL)) { - lua_settop(L, prev_top); + type = lua_parse_symbol_type(type_str); - return luaL_error(L, "unknown augmentation %s in symbol %s", - augmentation, name); - } - } - } + if (!name && !(type & SYMBOL_TYPE_CALLBACK)) { + luaL_error(L, "no symbol name but type is not callback"); + + return false; + } + else if (!(type & SYMBOL_TYPE_VIRTUAL) && cbref == -1) { + luaL_error(L, "no callback for symbol %s", name); + + return false; + } + + if (isnan(parent_float)) { + parent = -1; + } + else { + parent = parent_float; + } + + ret = rspamd_register_symbol_fromlua(L, + cfg, + name, + cbref, + weight == 0 ? 1.0 : weight, + priority, + type | flags, + parent, + NULL, NULL, + FALSE); + + if (ret != -1) { + if (!isnan(score) || group) { + if (one_shot) { + nshots = 1; + } + if (nshots == 0) { + nshots = cfg->default_max_shots; } + + rspamd_config_add_symbol(cfg, name, score, + description, group, flags, 0, nshots); } + + *id_out = ret; + + return true; } - else { - lua_settop(L, prev_top); - return luaL_error(L, "invalid arguments"); + return false; +} + +static int +lua_config_register_symbol(lua_State *L) +{ + LUA_TRACE_POINT; + struct rspamd_config *cfg = lua_check_config(L, 1); + int id = -1; + + if (lua_type(L, 2) == LUA_TSTRING) { + if (lua_config_register_symbol_legacy(L, cfg, 2, &id)) { + lua_pushinteger(L, id); + + return 1; + } + else { + return luaL_error(L, "bad arguments"); + } } + else if (lua_config_register_symbol_from_table(L, cfg, NULL, 2, &id)) { + lua_pushinteger(L, id); - lua_settop(L, prev_top); - lua_pushinteger(L, ret); + return 1; + } - return 1; + return 0; } static int @@ -2655,10 +2903,7 @@ lua_config_newindex(lua_State *L) LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name; - GArray *allowed_ids = NULL, *forbidden_ids = NULL; - int id, nshots; - unsigned int flags = 0; - gboolean optional = FALSE; + int id = -1; name = luaL_checkstring(L, 2); @@ -2679,291 +2924,14 @@ lua_config_newindex(lua_State *L) FALSE); } else if (lua_type(L, 3) == LUA_TTABLE) { - unsigned int type = SYMBOL_TYPE_NORMAL, priority = 0; - int idx; - double weight = 1.0, score = NAN; - const char *type_str, *group = NULL, *description = NULL; - - /* - * Table can have the following attributes: - * "callback" - should be a callback function - * "weight" - optional weight - * "priority" - optional priority - * "type" - optional type (normal, virtual, callback) - * "flags" - optional flags - * -- Metric options - * "score" - optional default score (overridden by metric) - * "group" - optional default group - * "one_shot" - optional one shot mode - * "description" - optional description - */ - lua_pushvalue(L, 3); - lua_pushstring(L, "callback"); - lua_gettable(L, -2); - - if (lua_type(L, -1) != LUA_TFUNCTION) { - lua_pop(L, 2); - msg_info_config("cannot find callback definition for %s", - name); - return 0; - } - idx = luaL_ref(L, LUA_REGISTRYINDEX); - - /* Optional fields */ - lua_pushstring(L, "weight"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TNUMBER) { - weight = lua_tonumber(L, -1); - } - lua_pop(L, 1); - - lua_pushstring(L, "priority"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TNUMBER) { - priority = lua_tointeger(L, -1); - } - lua_pop(L, 1); - - lua_pushstring(L, "optional"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TBOOLEAN) { - optional = lua_toboolean(L, -1); - } - lua_pop(L, 1); - - lua_pushstring(L, "type"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TSTRING) { - type_str = lua_tostring(L, -1); - type = lua_parse_symbol_type(type_str); - } - lua_pop(L, 1); - - /* Deal with flags and ids */ - lua_pushstring(L, "flags"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TSTRING) { - flags = lua_parse_symbol_flags(lua_tostring(L, -1)); - } - else if (lua_type(L, -1) == LUA_TTABLE) { - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - flags |= lua_parse_symbol_flags(lua_tostring(L, -1)); - } - } - lua_pop(L, 1); /* Clean flags */ - - lua_pushstring(L, "allowed_ids"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TSTRING) { - allowed_ids = rspamd_process_id_list(lua_tostring(L, -1)); - } - else if (lua_type(L, -1) == LUA_TTABLE) { - allowed_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), - rspamd_lua_table_size(L, -1)); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - uint32_t v = lua_tointeger(L, -1); - g_array_append_val(allowed_ids, v); - } - } - lua_pop(L, 1); - - lua_pushstring(L, "forbidden_ids"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TSTRING) { - forbidden_ids = rspamd_process_id_list(lua_tostring(L, -1)); - } - else if (lua_type(L, -1) == LUA_TTABLE) { - forbidden_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), - rspamd_lua_table_size(L, -1)); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - uint32_t v = lua_tointeger(L, -1); - g_array_append_val(forbidden_ids, v); - } - } - lua_pop(L, 1); - - id = rspamd_register_symbol_fromlua(L, - cfg, - name, - idx, - weight, - priority, - type | flags, - -1, - allowed_ids, forbidden_ids, - optional); - - if (allowed_ids) { - g_array_free(allowed_ids, TRUE); - } - - if (forbidden_ids) { - g_array_free(forbidden_ids, TRUE); - } - - if (id != -1) { - /* Check for condition */ - lua_pushstring(L, "condition"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TFUNCTION) { - int condref; - - /* Here we pop function from the stack, so no lua_pop is required */ - condref = luaL_ref(L, LUA_REGISTRYINDEX); - g_assert(name != NULL); - rspamd_symcache_add_condition_delayed(cfg->cache, - name, L, condref); - } - else { - lua_pop(L, 1); - } - - /* Check for augmentations */ - lua_pushstring(L, "augmentations"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TTABLE) { - - int tbl_idx = lua_gettop(L); - for (lua_pushnil(L); lua_next(L, tbl_idx); lua_pop(L, 1)) { - rspamd_symcache_add_symbol_augmentation(cfg->cache, id, - lua_tostring(L, -1), NULL); - } - } - - lua_pop(L, 1); - } - - /* - * Now check if a symbol has not been registered in any metric and - * insert default value if applicable - */ - struct rspamd_symbol *sym = g_hash_table_lookup(cfg->symbols, name); - if (sym == NULL || (sym->flags & RSPAMD_SYMBOL_FLAG_UNSCORED)) { - nshots = cfg->default_max_shots; - - lua_pushstring(L, "score"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TNUMBER) { - score = lua_tonumber(L, -1); - - if (sym) { - /* Reset unscored flag */ - sym->flags &= ~RSPAMD_SYMBOL_FLAG_UNSCORED; - } - } - lua_pop(L, 1); - - lua_pushstring(L, "group"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TSTRING) { - group = lua_tostring(L, -1); - } - lua_pop(L, 1); - - if (!isnan(score) || group != NULL) { - lua_pushstring(L, "description"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TSTRING) { - description = lua_tostring(L, -1); - } - lua_pop(L, 1); - - lua_pushstring(L, "one_shot"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TBOOLEAN) { - if (lua_toboolean(L, -1)) { - nshots = 1; - } - } - lua_pop(L, 1); - - lua_pushstring(L, "one_param"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TBOOLEAN) { - if (lua_toboolean(L, -1)) { - flags |= RSPAMD_SYMBOL_FLAG_ONEPARAM; - } - } - lua_pop(L, 1); - - /* - * Do not override the existing symbols (using zero priority), - * since we are defining default values here - */ - if (!isnan(score)) { - rspamd_config_add_symbol(cfg, name, score, - description, group, flags, 0, nshots); - } - else if (group) { - /* Add with zero score */ - rspamd_config_add_symbol(cfg, name, NAN, - description, group, flags, 0, nshots); - } - - lua_pushstring(L, "groups"); - lua_gettable(L, -2); - - if (lua_istable(L, -1)) { - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - if (lua_isstring(L, -1)) { - rspamd_config_add_symbol_group(cfg, name, - lua_tostring(L, -1)); - } - else { - return luaL_error(L, "invalid groups element"); - } - } - } - - lua_pop(L, 1); - } - } - else { - /* Fill in missing fields from lua definition if they are not set */ - if (sym->description == NULL) { - lua_pushstring(L, "description"); - lua_gettable(L, -2); - - if (lua_type(L, -1) == LUA_TSTRING) { - description = lua_tostring(L, -1); - } - lua_pop(L, 1); - - if (description) { - sym->description = rspamd_mempool_strdup(cfg->cfg_pool, description); - } - } - - /* If ungrouped and there is a group defined in lua, change the primary group - * Otherwise, add to the list of groups for this symbol. */ - lua_pushstring(L, "group"); - lua_gettable(L, -2); - if (lua_type(L, -1) == LUA_TSTRING) { - group = lua_tostring(L, -1); - } - lua_pop(L, 1); - if (group) { - if (sym->flags & RSPAMD_SYMBOL_FLAG_UNGROUPED) { - /* Unset the "ungrouped" group */ - sym->gr = NULL; - } - /* Add the group. If the symbol was ungrouped, this will - * clear RSPAMD_SYMBOL_FLAG_UNGROUPED from the flags. */ - rspamd_config_add_symbol_group(cfg, name, group); - } + /* Table symbol */ + if (lua_config_register_symbol_from_table(L, cfg, name, 3, &id)) { + lua_pushinteger(L, id); + return 1; } - - /* Remove table from stack */ - lua_pop(L, 1); + } + else { + return luaL_error(L, "invalid value for symbol"); } } else { @@ -3941,6 +3909,8 @@ lua_config_get_groups(lua_State *L) lua_setfield(L, -2, "description"); lua_pushnumber(L, gr->max_score); lua_setfield(L, -2, "max_score"); + lua_pushnumber(L, gr->min_score); + lua_setfield(L, -2, "min_score"); lua_pushboolean(L, (gr->flags & RSPAMD_SYMBOL_GROUP_PUBLIC) != 0); lua_setfield(L, -2, "is_public"); /* TODO: maybe push symbols as well */ diff --git a/src/lua/lua_cryptobox.c b/src/lua/lua_cryptobox.c index bad7d7024..1b9074f58 100644 --- a/src/lua/lua_cryptobox.c +++ b/src/lua/lua_cryptobox.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -257,7 +257,6 @@ lua_check_cryptobox_secretbox(lua_State *L, int pos) * Loads public key from base32 encoded file * @param {string} file filename to load * @param {string} type optional 'sign' or 'kex' for signing and encryption - * @param {string} alg optional 'default' or 'nist' for curve25519/nistp256 keys * @return {cryptobox_pubkey} new public key */ static int @@ -267,7 +266,6 @@ lua_cryptobox_pubkey_load(lua_State *L) struct rspamd_cryptobox_pubkey *pkey = NULL, **ppkey; const char *filename, *arg; int type = RSPAMD_KEYPAIR_SIGN; - int alg = RSPAMD_CRYPTOBOX_MODE_25519; unsigned char *map; gsize len; @@ -293,19 +291,8 @@ lua_cryptobox_pubkey_load(lua_State *L) type = RSPAMD_KEYPAIR_KEX; } } - if (lua_type(L, 3) == LUA_TSTRING) { - /* algorithm */ - arg = lua_tostring(L, 3); - - if (strcmp(arg, "default") == 0 || strcmp(arg, "curve25519") == 0) { - type = RSPAMD_CRYPTOBOX_MODE_25519; - } - else if (strcmp(arg, "nist") == 0) { - type = RSPAMD_CRYPTOBOX_MODE_NIST; - } - } - pkey = rspamd_pubkey_from_base32(map, len, type, alg); + pkey = rspamd_pubkey_from_base32(map, len, type); if (pkey == NULL) { msg_err("cannot open pubkey from file: %s", filename); @@ -333,7 +320,6 @@ lua_cryptobox_pubkey_load(lua_State *L) * Loads public key from base32 encoded string * @param {base32 string} base32 string with the key * @param {string} type optional 'sign' or 'kex' for signing and encryption - * @param {string} alg optional 'default' or 'nist' for curve25519/nistp256 keys * @return {cryptobox_pubkey} new public key */ static int @@ -344,7 +330,6 @@ lua_cryptobox_pubkey_create(lua_State *L) const char *buf, *arg; gsize len; int type = RSPAMD_KEYPAIR_SIGN; - int alg = RSPAMD_CRYPTOBOX_MODE_25519; buf = luaL_checklstring(L, 1, &len); if (buf != NULL) { @@ -359,19 +344,8 @@ lua_cryptobox_pubkey_create(lua_State *L) type = RSPAMD_KEYPAIR_KEX; } } - if (lua_type(L, 3) == LUA_TSTRING) { - /* algorithm */ - arg = lua_tostring(L, 3); - - if (strcmp(arg, "default") == 0 || strcmp(arg, "curve25519") == 0) { - type = RSPAMD_CRYPTOBOX_MODE_25519; - } - else if (strcmp(arg, "nist") == 0) { - type = RSPAMD_CRYPTOBOX_MODE_NIST; - } - } - pkey = rspamd_pubkey_from_base32(buf, len, type, alg); + pkey = rspamd_pubkey_from_base32(buf, len, type); if (pkey == NULL) { msg_err("cannot load pubkey from string"); @@ -477,7 +451,6 @@ lua_cryptobox_keypair_load(lua_State *L) * @function rspamd_cryptobox_keypair.create([type='encryption'[, alg='curve25519']]) * Generates new keypair * @param {string} type type of keypair: 'encryption' (default) or 'sign' - * @param {string} alg algorithm of keypair: 'curve25519' (default) or 'nist' * @return {cryptobox_keypair} new keypair */ static int @@ -486,7 +459,6 @@ lua_cryptobox_keypair_create(lua_State *L) LUA_TRACE_POINT; struct rspamd_cryptobox_keypair *kp, **pkp; enum rspamd_cryptobox_keypair_type type = RSPAMD_KEYPAIR_KEX; - enum rspamd_cryptobox_mode alg = RSPAMD_CRYPTOBOX_MODE_25519; if (lua_isstring(L, 1)) { const char *str = lua_tostring(L, 1); @@ -502,21 +474,7 @@ lua_cryptobox_keypair_create(lua_State *L) } } - if (lua_isstring(L, 2)) { - const char *str = lua_tostring(L, 2); - - if (strcmp(str, "nist") == 0 || strcmp(str, "openssl") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_NIST; - } - else if (strcmp(str, "curve25519") == 0 || strcmp(str, "default") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_25519; - } - else { - return luaL_error(L, "invalid keypair algorithm: %s", str); - } - } - - kp = rspamd_keypair_new(type, alg); + kp = rspamd_keypair_new(type); pkp = lua_newuserdata(L, sizeof(gpointer)); *pkp = kp; @@ -606,12 +564,7 @@ lua_cryptobox_keypair_get_alg(lua_State *L) struct rspamd_cryptobox_keypair *kp = lua_check_cryptobox_keypair(L, 1); if (kp) { - if (kp->alg == RSPAMD_CRYPTOBOX_MODE_25519) { - lua_pushstring(L, "curve25519"); - } - else { - lua_pushstring(L, "nist"); - } + lua_pushstring(L, "curve25519"); } else { return luaL_error(L, "invalid arguments"); @@ -636,7 +589,7 @@ lua_cryptobox_keypair_get_pk(lua_State *L) if (kp) { data = rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_PK, &dlen); - pk = rspamd_pubkey_from_bin(data, dlen, kp->type, kp->alg); + pk = rspamd_pubkey_from_bin(data, dlen, kp->type); if (pk == NULL) { return luaL_error(L, "invalid keypair"); @@ -654,7 +607,7 @@ lua_cryptobox_keypair_get_pk(lua_State *L) } /*** - * @function rspamd_cryptobox_signature.load(file, [alg = 'curve25519']) + * @function rspamd_cryptobox_signature.load(file) * Loads signature from raw file * @param {string} file filename to load * @return {cryptobox_signature} new signature @@ -668,7 +621,6 @@ lua_cryptobox_signature_load(lua_State *L) gpointer data; int fd; struct stat st; - enum rspamd_cryptobox_mode alg = RSPAMD_CRYPTOBOX_MODE_25519; filename = luaL_checkstring(L, 1); if (filename != NULL) { @@ -686,22 +638,6 @@ lua_cryptobox_signature_load(lua_State *L) lua_pushnil(L); } else { - if (lua_isstring(L, 2)) { - const char *str = lua_tostring(L, 2); - - if (strcmp(str, "nist") == 0 || strcmp(str, "openssl") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_NIST; - } - else if (strcmp(str, "curve25519") == 0 || strcmp(str, "default") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_25519; - } - else { - munmap(data, st.st_size); - close(fd); - - return luaL_error(L, "invalid keypair algorithm: %s", str); - } - } if (st.st_size > 0) { sig = rspamd_fstring_new_init(data, st.st_size); psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); @@ -711,7 +647,7 @@ lua_cryptobox_signature_load(lua_State *L) else { msg_err("size of %s mismatches: %d while %d is expected", filename, (int) st.st_size, - rspamd_cryptobox_signature_bytes(alg)); + crypto_sign_bytes()); lua_pushnil(L); } @@ -821,7 +757,7 @@ lua_cryptobox_signature_create(lua_State *L) } if (data != NULL) { - if (dlen == rspamd_cryptobox_signature_bytes(RSPAMD_CRYPTOBOX_MODE_25519)) { + if (dlen == crypto_sign_bytes()) { sig = rspamd_fstring_new_init(data, dlen); psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); rspamd_lua_setclass(L, rspamd_cryptobox_signature_classname, -1); @@ -1723,7 +1659,7 @@ lua_cryptobox_hash_gc(lua_State *L) } /*** - * @function rspamd_cryptobox.verify_memory(pk, sig, data, [alg = 'curve25519']) + * @function rspamd_cryptobox.verify_memory(pk, sig, data) * Check memory using specified cryptobox key and signature * @param {pubkey} pk public key to verify * @param {sig} signature to check @@ -1738,7 +1674,6 @@ lua_cryptobox_verify_memory(lua_State *L) rspamd_fstring_t *signature; struct rspamd_lua_text *t; const char *data; - enum rspamd_cryptobox_mode alg = RSPAMD_CRYPTOBOX_MODE_25519; gsize len; int ret; @@ -1759,23 +1694,9 @@ lua_cryptobox_verify_memory(lua_State *L) data = luaL_checklstring(L, 3, &len); } - if (lua_isstring(L, 4)) { - const char *str = lua_tostring(L, 4); - - if (strcmp(str, "nist") == 0 || strcmp(str, "openssl") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_NIST; - } - else if (strcmp(str, "curve25519") == 0 || strcmp(str, "default") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_25519; - } - else { - return luaL_error(L, "invalid algorithm: %s", str); - } - } - if (pk != NULL && signature != NULL && data != NULL) { ret = rspamd_cryptobox_verify(signature->str, signature->len, data, len, - rspamd_pubkey_get_pk(pk, NULL), alg); + rspamd_pubkey_get_pk(pk, NULL)); if (ret) { lua_pushboolean(L, 1); @@ -1792,7 +1713,7 @@ lua_cryptobox_verify_memory(lua_State *L) } /*** - * @function rspamd_cryptobox.verify_file(pk, sig, file, [alg = 'curve25519']) + * @function rspamd_cryptobox.verify_file(pk, sig, file) * Check file using specified cryptobox key and signature * @param {pubkey} pk public key to verify * @param {sig} signature to check @@ -1807,7 +1728,6 @@ lua_cryptobox_verify_file(lua_State *L) struct rspamd_cryptobox_pubkey *pk; rspamd_fstring_t *signature; unsigned char *map = NULL; - enum rspamd_cryptobox_mode alg = RSPAMD_CRYPTOBOX_MODE_25519; gsize len; int ret; @@ -1815,26 +1735,12 @@ lua_cryptobox_verify_file(lua_State *L) signature = lua_check_cryptobox_sign(L, 2); fname = luaL_checkstring(L, 3); - if (lua_isstring(L, 4)) { - const char *str = lua_tostring(L, 4); - - if (strcmp(str, "nist") == 0 || strcmp(str, "openssl") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_NIST; - } - else if (strcmp(str, "curve25519") == 0 || strcmp(str, "default") == 0) { - alg = RSPAMD_CRYPTOBOX_MODE_25519; - } - else { - return luaL_error(L, "invalid algorithm: %s", str); - } - } - map = rspamd_file_xmap(fname, PROT_READ, &len, TRUE); if (map != NULL && pk != NULL && signature != NULL) { ret = rspamd_cryptobox_verify(signature->str, signature->len, map, len, - rspamd_pubkey_get_pk(pk, NULL), alg); + rspamd_pubkey_get_pk(pk, NULL)); if (ret) { lua_pushboolean(L, 1); @@ -1896,12 +1802,11 @@ lua_cryptobox_sign_memory(lua_State *L) return luaL_error(L, "invalid arguments"); } - sig = rspamd_fstring_sized_new(rspamd_cryptobox_signature_bytes( - rspamd_keypair_alg(kp))); + sig = rspamd_fstring_sized_new(crypto_sign_bytes()); unsigned long long siglen = sig->len; rspamd_cryptobox_sign(sig->str, &siglen, data, - len, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), rspamd_keypair_alg(kp)); + len, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL)); sig->len = siglen; psig = lua_newuserdata(L, sizeof(void *)); @@ -1942,13 +1847,12 @@ lua_cryptobox_sign_file(lua_State *L) lua_pushnil(L); } else { - sig = rspamd_fstring_sized_new(rspamd_cryptobox_signature_bytes( - rspamd_keypair_alg(kp))); + sig = rspamd_fstring_sized_new(crypto_sign_bytes()); unsigned long long siglen = sig->len; rspamd_cryptobox_sign(sig->str, &siglen, data, - len, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL), rspamd_keypair_alg(kp)); + len, rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL)); sig->len = siglen; psig = lua_newuserdata(L, sizeof(void *)); @@ -1961,7 +1865,7 @@ lua_cryptobox_sign_file(lua_State *L) } /*** - * @function rspamd_cryptobox.encrypt_memory(kp, data[, nist=false]) + * @function rspamd_cryptobox.encrypt_memory(kp, data) * Encrypt data using specified keypair/pubkey * @param {keypair|string} kp keypair or pubkey in base32 to use * @param {string|text} data @@ -1993,8 +1897,7 @@ lua_cryptobox_encrypt_memory(lua_State *L) gsize blen; b32 = lua_tolstring(L, 1, &blen); - pk = rspamd_pubkey_from_base32(b32, blen, RSPAMD_KEYPAIR_KEX, - lua_toboolean(L, 3) ? RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); + pk = rspamd_pubkey_from_base32(b32, blen, RSPAMD_KEYPAIR_KEX); owned_pk = true; } @@ -2063,7 +1966,7 @@ err: } /*** - * @function rspamd_cryptobox.encrypt_file(kp|pk_string, filename[, nist=false]) + * @function rspamd_cryptobox.encrypt_file(kp|pk_string, filename) * Encrypt data using specified keypair/pubkey * @param {keypair|string} kp keypair or pubkey in base32 to use * @param {string} filename @@ -2096,8 +1999,7 @@ lua_cryptobox_encrypt_file(lua_State *L) gsize blen; b32 = lua_tolstring(L, 1, &blen); - pk = rspamd_pubkey_from_base32(b32, blen, RSPAMD_KEYPAIR_KEX, - lua_toboolean(L, 3) ? RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); + pk = rspamd_pubkey_from_base32(b32, blen, RSPAMD_KEYPAIR_KEX); own_pk = true; } @@ -2658,11 +2560,11 @@ lua_cryptobox_gen_dkim_keypair(lua_State *L) char *b64_data; gsize b64_len; - rspamd_cryptobox_keypair_sig(pk, sk, RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_keypair_sig(pk, sk); /* Process private key */ b64_data = rspamd_encode_base64(sk, - rspamd_cryptobox_sk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519), + crypto_sign_secretkeybytes(), -1, &b64_len); priv_out = lua_newuserdata(L, sizeof(*priv_out)); @@ -2673,7 +2575,7 @@ lua_cryptobox_gen_dkim_keypair(lua_State *L) /* Process public key */ b64_data = rspamd_encode_base64(pk, - rspamd_cryptobox_pk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519), + crypto_sign_publickeybytes(), -1, &b64_len); pub_out = lua_newuserdata(L, sizeof(*pub_out)); @@ -2691,7 +2593,7 @@ lua_cryptobox_gen_dkim_keypair(lua_State *L) char *b64_data; gsize b64_len; - rspamd_cryptobox_keypair_sig(pk, sk, RSPAMD_CRYPTOBOX_MODE_25519); + rspamd_cryptobox_keypair_sig(pk, sk); /* Process private key */ b64_data = rspamd_encode_base64(sk, @@ -2706,7 +2608,7 @@ lua_cryptobox_gen_dkim_keypair(lua_State *L) /* Process public key */ b64_data = rspamd_encode_base64(pk, - rspamd_cryptobox_pk_sig_bytes(RSPAMD_CRYPTOBOX_MODE_25519), + crypto_sign_publickeybytes(), -1, &b64_len); pub_out = lua_newuserdata(L, sizeof(*pub_out)); diff --git a/src/lua/lua_http.c b/src/lua/lua_http.c index 2032f7dc1..8ba612c1b 100644 --- a/src/lua/lua_http.c +++ b/src/lua/lua_http.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -907,8 +907,7 @@ lua_http_request(lua_State *L) gsize inlen; in = lua_tolstring(L, -1, &inlen); - peer_key = rspamd_pubkey_from_base32(in, inlen, - RSPAMD_KEYPAIR_KEX, RSPAMD_CRYPTOBOX_MODE_25519); + peer_key = rspamd_pubkey_from_base32(in, inlen, RSPAMD_KEYPAIR_KEX); } lua_pop(L, 1); diff --git a/src/lua/lua_map.c b/src/lua/lua_map.c index cce78ff2c..1cc2ce1bd 100644 --- a/src/lua/lua_map.c +++ b/src/lua/lua_map.c @@ -1293,8 +1293,7 @@ lua_map_set_sign_key(lua_State *L) pk_str = lua_tolstring(L, 2, &len); if (map && pk_str) { - pk = rspamd_pubkey_from_base32(pk_str, len, RSPAMD_KEYPAIR_SIGN, - RSPAMD_CRYPTOBOX_MODE_25519); + pk = rspamd_pubkey_from_base32(pk_str, len, RSPAMD_KEYPAIR_SIGN); if (!pk) { return luaL_error(L, "invalid pubkey string"); diff --git a/src/lua/lua_redis.c b/src/lua/lua_redis.c index f95abb577..d20c496ed 100644 --- a/src/lua/lua_redis.c +++ b/src/lua/lua_redis.c @@ -104,7 +104,7 @@ struct lua_redis_userdata { char *server; char log_tag[RSPAMD_LOG_ID_LEN + 1]; struct lua_redis_request_specific_userdata *specific; - double timeout; + ev_tstamp timeout; uint16_t port; uint16_t terminated; }; @@ -280,16 +280,23 @@ lua_redis_fin(void *arg) * @param code * @param ud */ +#ifdef __GNUC__ +__attribute__((format(printf, 1, 5))) +#endif static void lua_redis_push_error(const char *err, struct lua_redis_ctx *ctx, struct lua_redis_request_specific_userdata *sp_ud, - gboolean connected) + gboolean connected, + ...) { struct lua_redis_userdata *ud = sp_ud->c; struct lua_callback_state cbs; lua_State *L; + va_list ap; + va_start(ap, connected); + if (!(sp_ud->flags & (LUA_REDIS_SPECIFIC_REPLIED | LUA_REDIS_SPECIFIC_FINISHED))) { if (sp_ud->cbref != -1) { @@ -302,7 +309,7 @@ lua_redis_push_error(const char *err, lua_rawgeti(cbs.L, LUA_REGISTRYINDEX, sp_ud->cbref); /* String of error */ - lua_pushstring(cbs.L, err); + lua_pushvfstring(cbs.L, err, ap); /* Data is nil */ lua_pushnil(cbs.L); @@ -331,6 +338,8 @@ lua_redis_push_error(const char *err, lua_redis_fin(sp_ud); } } + + va_end(ap); } static void @@ -479,7 +488,7 @@ lua_redis_callback(redisAsyncContext *c, gpointer r, gpointer priv) lua_redis_push_data(reply, ctx, sp_ud); } else { - lua_redis_push_error(reply->str, ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, reply->str); } } else { @@ -488,10 +497,10 @@ lua_redis_callback(redisAsyncContext *c, gpointer r, gpointer priv) } else { if (c->err == REDIS_ERR_IO) { - lua_redis_push_error(strerror(errno), ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, strerror(errno)); } else { - lua_redis_push_error(c->errstr, ctx, sp_ud, TRUE); + lua_redis_push_error("%s", ctx, sp_ud, TRUE, c->errstr); } } } @@ -750,7 +759,7 @@ lua_redis_timeout(EV_P_ ev_timer *w, int revents) REDIS_RETAIN(ctx); msg_debug_lua_redis("timeout while querying redis server: %p, redis: %p", sp_ud, sp_ud->c->ctx); - lua_redis_push_error("timeout while connecting the server", ctx, sp_ud, TRUE); + lua_redis_push_error("timeout while connecting the server (%.2f sec)", ctx, sp_ud, TRUE, ud->timeout); if (sp_ud->c->ctx) { ac = sp_ud->c->ctx; diff --git a/src/lua/lua_rsa.c b/src/lua/lua_rsa.c index 0f67e91d1..0c56b223b 100644 --- a/src/lua/lua_rsa.c +++ b/src/lua/lua_rsa.c @@ -91,22 +91,22 @@ static const struct luaL_reg rsasignlib_m[] = { {"__gc", lua_rsa_signature_gc}, {NULL, NULL}}; -static RSA * +static EVP_PKEY * lua_check_rsa_pubkey(lua_State *L, int pos) { void *ud = rspamd_lua_check_udata(L, pos, rspamd_rsa_pubkey_classname); luaL_argcheck(L, ud != NULL, 1, "'rsa_pubkey' expected"); - return ud ? *((RSA **) ud) : NULL; + return ud ? *((EVP_PKEY **) ud) : NULL; } -static RSA * +static EVP_PKEY * lua_check_rsa_privkey(lua_State *L, int pos) { void *ud = rspamd_lua_check_udata(L, pos, rspamd_rsa_privkey_classname); luaL_argcheck(L, ud != NULL, 1, "'rsa_privkey' expected"); - return ud ? *((RSA **) ud) : NULL; + return ud ? *((EVP_PKEY **) ud) : NULL; } static rspamd_fstring_t * @@ -121,7 +121,7 @@ lua_check_rsa_sign(lua_State *L, int pos) static int lua_rsa_pubkey_load(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; const char *filename; FILE *f; @@ -135,15 +135,15 @@ lua_rsa_pubkey_load(lua_State *L) lua_pushnil(L); } else { - if (!PEM_read_RSA_PUBKEY(f, &rsa, NULL, NULL)) { + if (!PEM_read_PUBKEY(f, &pkey, NULL, NULL)) { msg_err("cannot open pubkey from file: %s, %s", filename, ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_pubkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } fclose(f); } @@ -161,15 +161,14 @@ lua_rsa_privkey_save(lua_State *L) const char *type = "pem"; FILE *f; int ret; - - RSA *rsa = lua_check_rsa_privkey(L, 1); + EVP_PKEY *pkey = lua_check_rsa_privkey(L, 1); filename = luaL_checkstring(L, 2); if (lua_gettop(L) > 2) { type = luaL_checkstring(L, 3); } - if (rsa != NULL && filename != NULL) { + if (pkey != NULL && filename != NULL) { if (strcmp(filename, "-") == 0) { f = stdout; } @@ -189,10 +188,10 @@ lua_rsa_privkey_save(lua_State *L) } if (strcmp(type, "der") == 0) { - ret = i2d_RSAPrivateKey_fp(f, rsa); + ret = i2d_PrivateKey_fp(f, pkey); } else { - ret = PEM_write_RSAPrivateKey(f, rsa, NULL, NULL, 0, NULL, NULL); + ret = PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL); } if (!ret) { @@ -223,7 +222,7 @@ lua_rsa_privkey_save(lua_State *L) static int lua_rsa_pubkey_create(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey, **ppkey; const char *buf; BIO *bp; @@ -231,15 +230,15 @@ lua_rsa_pubkey_create(lua_State *L) if (buf != NULL) { bp = BIO_new_mem_buf((void *) buf, -1); - if (!PEM_read_bio_RSA_PUBKEY(bp, &rsa, NULL, NULL)) { + if (!PEM_read_bio_PUBKEY(bp, &pkey, NULL, NULL)) { msg_err("cannot parse pubkey: %s", ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_pubkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } BIO_free(bp); } @@ -252,10 +251,10 @@ lua_rsa_pubkey_create(lua_State *L) static int lua_rsa_pubkey_gc(lua_State *L) { - RSA *rsa = lua_check_rsa_pubkey(L, 1); + EVP_PKEY *pkey = lua_check_rsa_pubkey(L, 1); - if (rsa != NULL) { - RSA_free(rsa); + if (pkey != NULL) { + EVP_PKEY_free(pkey); } return 0; @@ -264,18 +263,18 @@ lua_rsa_pubkey_gc(lua_State *L) static int lua_rsa_pubkey_tostring(lua_State *L) { - RSA *rsa = lua_check_rsa_pubkey(L, 1); + EVP_PKEY *pkey = lua_check_rsa_pubkey(L, 1); - if (rsa != NULL) { + if (pkey != NULL) { BIO *pubout = BIO_new(BIO_s_mem()); const char *pubdata; gsize publen; - int rc = i2d_RSA_PUBKEY_bio(pubout, rsa); + int rc = i2d_PUBKEY_bio(pubout, pkey); if (rc != 1) { BIO_free(pubout); - return luaL_error(L, "i2d_RSA_PUBKEY_bio failed"); + return luaL_error(L, "i2d_PUBKEY_bio failed"); } publen = BIO_get_mem_data(pubout, &pubdata); @@ -292,7 +291,7 @@ lua_rsa_pubkey_tostring(lua_State *L) static int lua_rsa_privkey_load_file(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; const char *filename; FILE *f; @@ -306,15 +305,15 @@ lua_rsa_privkey_load_file(lua_State *L) lua_pushnil(L); } else { - if (!PEM_read_RSAPrivateKey(f, &rsa, NULL, NULL)) { + if (!PEM_read_PrivateKey(f, &pkey, NULL, NULL)) { msg_err("cannot open private key from file: %s, %s", filename, ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } fclose(f); } @@ -328,7 +327,7 @@ lua_rsa_privkey_load_file(lua_State *L) static int lua_rsa_privkey_load_pem(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; BIO *b; struct rspamd_lua_text *t; const char *data; @@ -351,15 +350,15 @@ lua_rsa_privkey_load_pem(lua_State *L) if (data != NULL) { b = BIO_new_mem_buf(data, len); - if (!PEM_read_bio_RSAPrivateKey(b, &rsa, NULL, NULL)) { + if (!PEM_read_bio_PrivateKey(b, &pkey, NULL, NULL)) { msg_err("cannot open private key from data, %s", ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } BIO_free(b); @@ -374,7 +373,7 @@ lua_rsa_privkey_load_pem(lua_State *L) static int lua_rsa_privkey_load_raw(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; BIO *b; struct rspamd_lua_text *t; const char *data; @@ -396,17 +395,17 @@ lua_rsa_privkey_load_raw(lua_State *L) if (data != NULL) { b = BIO_new_mem_buf(data, len); - rsa = d2i_RSAPrivateKey_bio(b, NULL); + pkey = d2i_PrivateKey_bio(b, NULL); - if (rsa == NULL) { + if (pkey == NULL) { msg_err("cannot open private key from data, %s", ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } BIO_free(b); @@ -421,9 +420,8 @@ lua_rsa_privkey_load_raw(lua_State *L) static int lua_rsa_privkey_load_base64(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; BIO *b; - EVP_PKEY *evp = NULL; struct rspamd_lua_text *t; const char *data; unsigned char *decoded; @@ -454,21 +452,18 @@ lua_rsa_privkey_load_base64(lua_State *L) b = BIO_new_mem_buf(decoded, dec_len); - if (d2i_PrivateKey_bio(b, &evp) != NULL) { - rsa = EVP_PKEY_get1_RSA(evp); - - if (rsa == NULL) { + if (d2i_PrivateKey_bio(b, &pkey) != NULL) { + if (pkey == NULL) { msg_err("cannot open RSA private key from data, %s", ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } - EVP_PKEY_free(evp); } else { msg_err("cannot open EVP private key from data, %s", @@ -489,7 +484,7 @@ lua_rsa_privkey_load_base64(lua_State *L) static int lua_rsa_privkey_create(lua_State *L) { - RSA *rsa = NULL, **prsa; + EVP_PKEY *pkey = NULL, **ppkey; const char *buf; BIO *bp; @@ -497,15 +492,15 @@ lua_rsa_privkey_create(lua_State *L) if (buf != NULL) { bp = BIO_new_mem_buf((void *) buf, -1); - if (!PEM_read_bio_RSAPrivateKey(bp, &rsa, NULL, NULL)) { + if (!PEM_read_bio_PrivateKey(bp, &pkey, NULL, NULL)) { msg_err("cannot parse private key: %s", ERR_error_string(ERR_get_error(), NULL)); lua_pushnil(L); } else { - prsa = lua_newuserdata(L, sizeof(RSA *)); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = rsa; + *ppkey = pkey; } BIO_free(bp); } @@ -518,10 +513,10 @@ lua_rsa_privkey_create(lua_State *L) static int lua_rsa_privkey_gc(lua_State *L) { - RSA *rsa = lua_check_rsa_privkey(L, 1); + EVP_PKEY *pkey = lua_check_rsa_privkey(L, 1); - if (rsa != NULL) { - RSA_free(rsa); + if (pkey != NULL) { + EVP_PKEY_free(pkey); } return 0; @@ -699,19 +694,22 @@ lua_rsa_signature_base64(lua_State *L) static int lua_rsa_verify_memory(lua_State *L) { - RSA *rsa; + EVP_PKEY *pkey; rspamd_fstring_t *signature; const char *data; gsize sz; int ret; - rsa = lua_check_rsa_pubkey(L, 1); + pkey = lua_check_rsa_pubkey(L, 1); signature = lua_check_rsa_sign(L, 2); data = luaL_checklstring(L, 3, &sz); - if (rsa != NULL && signature != NULL && data != NULL) { - ret = RSA_verify(NID_sha256, data, sz, - signature->str, signature->len, rsa); + if (pkey != NULL && signature != NULL && data != NULL) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pkey, NULL); + g_assert(pctx != NULL); + g_assert(EVP_PKEY_verify_init(pctx) == 1); + + ret = EVP_PKEY_verify(pctx, signature->str, signature->len, data, sz); if (ret == 0) { lua_pushboolean(L, FALSE); @@ -722,6 +720,7 @@ lua_rsa_verify_memory(lua_State *L) else { lua_pushboolean(L, TRUE); } + EVP_PKEY_CTX_free(pctx); } else { lua_pushnil(L); @@ -743,22 +742,26 @@ lua_rsa_verify_memory(lua_State *L) static int lua_rsa_sign_memory(lua_State *L) { - RSA *rsa; + EVP_PKEY *pkey; rspamd_fstring_t *signature, **psig; const char *data; gsize sz; int ret; - rsa = lua_check_rsa_privkey(L, 1); + pkey = lua_check_rsa_privkey(L, 1); data = luaL_checklstring(L, 2, &sz); - if (rsa != NULL && data != NULL) { - signature = rspamd_fstring_sized_new(RSA_size(rsa)); + if (pkey != NULL && data != NULL) { + signature = rspamd_fstring_sized_new(EVP_PKEY_get_size(pkey)); + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pkey, NULL); + g_assert(pctx != NULL); - unsigned int siglen = signature->len; - ret = RSA_sign(NID_sha256, data, sz, - signature->str, &siglen, rsa); + g_assert(EVP_PKEY_sign_init(pctx) == 1); + size_t slen = signature->allocated; + ret = EVP_PKEY_sign(pctx, signature->str, &slen, data, sz); + EVP_PKEY_CTX_free(pctx); if (ret != 1) { rspamd_fstring_free(signature); @@ -766,7 +769,7 @@ lua_rsa_sign_memory(lua_State *L) ERR_error_string(ERR_get_error(), NULL)); } else { - signature->len = siglen; + signature->len = slen; psig = lua_newuserdata(L, sizeof(rspamd_fstring_t *)); rspamd_lua_setclass(L, rspamd_rsa_signature_classname, -1); *psig = signature; @@ -783,7 +786,7 @@ static int lua_rsa_keypair(lua_State *L) { BIGNUM *e; - RSA *rsa, *pub_rsa, *priv_rsa, **prsa; + EVP_PKEY *pkey = NULL, *pub_pkey, *priv_pkey, **ppkey; int bits = lua_gettop(L) > 0 ? lua_tointeger(L, 1) : 1024; if (bits > 4096 || bits < 512) { @@ -791,21 +794,30 @@ lua_rsa_keypair(lua_State *L) } e = BN_new(); - rsa = RSA_new(); + g_assert(BN_set_word(e, RSA_F4) == 1); - g_assert(RSA_generate_key_ex(rsa, bits, e, NULL) == 1); + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + g_assert(pctx != NULL); + g_assert(EVP_PKEY_keygen_init(pctx) == 1); + + g_assert(EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, bits) == 1); + g_assert(EVP_PKEY_CTX_set1_rsa_keygen_pubexp(pctx, e) == 1); + + g_assert(EVP_PKEY_keygen(pctx, &pkey) == 1); + g_assert(pkey != NULL); - priv_rsa = RSAPrivateKey_dup(rsa); - prsa = lua_newuserdata(L, sizeof(RSA *)); + priv_pkey = EVP_PKEY_dup(pkey); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_privkey_classname, -1); - *prsa = priv_rsa; + *ppkey = priv_pkey; - pub_rsa = RSAPublicKey_dup(rsa); - prsa = lua_newuserdata(L, sizeof(RSA *)); + pub_pkey = EVP_PKEY_dup(pkey); + ppkey = lua_newuserdata(L, sizeof(EVP_PKEY *)); rspamd_lua_setclass(L, rspamd_rsa_pubkey_classname, -1); - *prsa = pub_rsa; + *ppkey = pub_pkey; - RSA_free(rsa); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(pctx); BN_free(e); return 2; diff --git a/src/lua/lua_spf.c b/src/lua/lua_spf.c index 46e72202f..850ce2120 100644 --- a/src/lua/lua_spf.c +++ b/src/lua/lua_spf.c @@ -89,6 +89,8 @@ lua_load_spf(lua_State *L) lua_setfield(L, -2, "perm_fail"); lua_pushinteger(L, RSPAMD_SPF_FLAG_CACHED); lua_setfield(L, -2, "cached"); + lua_pushinteger(L, RSPAMD_SPF_RESOLVED_PLUSALL); + lua_setfield(L, -2, "plusall"); lua_setfield(L, -2, "flags"); @@ -368,6 +370,11 @@ spf_check_element(lua_State *L, struct spf_resolved *rec, struct spf_addr *addr, lua_pushinteger(L, RSPAMD_SPF_RESOLVED_TEMP_FAILED); lua_pushfstring(L, "%cany", spf_mech_char(addr->mech)); } + else if (rec->flags & RSPAMD_SPF_RESOLVED_PLUSALL) { + lua_pushboolean(L, false); + lua_pushinteger(L, RSPAMD_SPF_RESOLVED_PLUSALL); + lua_pushfstring(L, "%cany", spf_mech_char(addr->mech)); + } else { lua_pushboolean(L, true); lua_pushinteger(L, addr->mech); diff --git a/src/lua/lua_task.c b/src/lua/lua_task.c index 21e24fc45..3968c01eb 100644 --- a/src/lua/lua_task.c +++ b/src/lua/lua_task.c @@ -1318,6 +1318,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF(task, get_metric_threshold), LUA_INTERFACE_DEF(task, set_metric_score), LUA_INTERFACE_DEF(task, set_metric_subject), + {"set_subject", lua_task_set_metric_subject}, LUA_INTERFACE_DEF(task, learn), LUA_INTERFACE_DEF(task, set_settings), LUA_INTERFACE_DEF(task, get_settings), diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c index a035eeaae..91b77c702 100644 --- a/src/plugins/fuzzy_check.c +++ b/src/plugins/fuzzy_check.c @@ -543,15 +543,13 @@ fuzzy_parse_rule(struct rspamd_config *cfg, const ucl_object_t *obj, k = ucl_object_tostring(value); if (k == NULL || (rule->peer_key = - rspamd_pubkey_from_base32(k, 0, RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519)) == NULL) { + rspamd_pubkey_from_base32(k, 0, RSPAMD_KEYPAIR_KEX)) == NULL) { msg_err_config("bad encryption key value: %s", k); return -1; } - rule->local_key = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, - RSPAMD_CRYPTOBOX_MODE_25519); + rule->local_key = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); } if ((value = ucl_object_lookup(obj, "learn_condition")) != NULL) { @@ -1334,8 +1332,7 @@ fuzzy_encrypt_cmd(struct fuzzy_rule *rule, rule->local_key, rule->peer_key); rspamd_cryptobox_encrypt_nm_inplace(data, datalen, hdr->nonce, rspamd_pubkey_get_nm(rule->peer_key, rule->local_key), - hdr->mac, - rspamd_pubkey_alg(rule->peer_key)); + hdr->mac); } static struct fuzzy_cmd_io * @@ -2209,8 +2206,7 @@ fuzzy_process_reply(unsigned char **pos, int *r, GPtrArray *req, sizeof(encrep.rep), encrep.hdr.nonce, rspamd_pubkey_get_nm(rule->peer_key, rule->local_key), - encrep.hdr.mac, - rspamd_pubkey_alg(rule->peer_key))) { + encrep.hdr.mac)) { msg_info("cannot decrypt reply"); return NULL; } @@ -2299,6 +2295,8 @@ fuzzy_insert_result(struct fuzzy_client_session *session, * Otherwise `value` means error code */ + msg_debug_fuzzy_check("got reply with probability %.2f and value %.2f", + (double) rep->v1.prob, (double) rep->v1.value); nval = fuzzy_normalize(rep->v1.value, weight); if (io) { diff --git a/src/plugins/lua/arc.lua b/src/plugins/lua/arc.lua index ff19aef4c..90e254e78 100644 --- a/src/plugins/lua/arc.lua +++ b/src/plugins/lua/arc.lua @@ -635,11 +635,21 @@ local function prepare_arc_selector(task, sel) end end + local function arc_result_from_ar(ar_header) + ar_header = ar_header or "" + for k, v in string.gmatch(ar_header, "(%w+)=(%w+)") do + if k == 'arc' then + return v + end + end + return nil + end + if settings.reuse_auth_results then local ar_header = task:get_header('Authentication-Results') if ar_header then - local arc_match = string.match(ar_header, 'arc=(%w+)') + local arc_match = arc_result_from_ar(ar_header) if arc_match then if arc_match == 'none' or arc_match == 'pass' then diff --git a/src/plugins/lua/gpt.lua b/src/plugins/lua/gpt.lua index 6adbce3bf..823dbd045 100644 --- a/src/plugins/lua/gpt.lua +++ b/src/plugins/lua/gpt.lua @@ -27,13 +27,11 @@ gpt { # Your key to access the API api_key = "xxx"; # Model name - model = "gpt-3.5-turbo"; + model = "gpt-4o-mini"; # Maximum tokens to generate max_tokens = 1000; # Temperature for sampling - temperature = 0.7; - # Top p for sampling - top_p = 0.9; + temperature = 0.0; # Timeout for requests timeout = 10s; # Prompt for the model (use default if not set) @@ -71,10 +69,9 @@ local default_symbols_to_except = { local settings = { type = 'openai', api_key = nil, - model = 'gpt-3.5-turbo', + model = 'gpt-4o-mini', max_tokens = 1000, - temperature = 0.7, - top_p = 0.9, + temperature = 0.0, timeout = 10, prompt = nil, condition = nil, @@ -97,11 +94,11 @@ local function default_condition(task) local action = result.action if action == 'reject' and result.npositive > 1 then - return true, 'already decided as spam' + return false, 'already decided as spam' end if action == 'no action' and score < 0 then - return true, 'negative score, already decided as ham' + return false, 'negative score, already decided as ham' end end -- We also exclude some symbols @@ -109,10 +106,12 @@ local function default_condition(task) if task:has_symbol(s) then if required_weight > 0 then -- Also check score - local sym = task:get_symbol(s) + local sym = task:get_symbol(s) or E -- Must exist as we checked it before with `has_symbol` - if math.abs(sym.weight) >= required_weight then - return false, 'skip as "' .. s .. '" is found (weight: ' .. sym.weight .. ')' + if sym.weight then + if math.abs(sym.weight) >= required_weight then + return false, 'skip as "' .. s .. '" is found (weight: ' .. sym.weight .. ')' + end end lua_util.debugm(N, task, 'symbol %s has weight %s, but required %s', s, sym.weight, required_weight) @@ -195,6 +194,18 @@ local function default_conversion(task, input) if type(reply) == 'table' and reply.probability then local spam_score = tonumber(reply.probability) + + if not spam_score then + -- Maybe we need GPT to convert GPT reply here? + if reply.probability == "high" then + spam_score = 0.9 + elseif reply.probability == "low" then + spam_score = 0.1 + else + rspamd_logger.infox("cannot convert to spam probability: %s", reply.probability) + end + end + if type(reply.usage) == 'table' then rspamd_logger.infox(task, 'usage: %s tokens', reply.usage.total_tokens) end @@ -276,7 +287,7 @@ local function openai_gpt_check(task) model = settings.model, max_tokens = settings.max_tokens, temperature = settings.temperature, - top_p = settings.top_p, + response_format = { type = "json_object" }, messages = { { role = 'system', @@ -348,8 +359,8 @@ if opts then end if not settings.prompt then - settings.prompt = "You will be provided with the email message, " .. - "and your task is to classify its probability to be spam, " .. + settings.prompt = "You will be provided with the email message, subject, from and url domains, " .. + "and your task is to evaluate the probability to be spam as number from 0 to 1, " .. "output result as JSON with 'probability' field." end diff --git a/src/plugins/lua/history_redis.lua b/src/plugins/lua/history_redis.lua index 3365b30cd..fff9f46b3 100644 --- a/src/plugins/lua/history_redis.lua +++ b/src/plugins/lua/history_redis.lua @@ -21,8 +21,8 @@ if confighelp then redis_history { # History key name key_prefix = 'rs_history{{HOSTNAME}}{{COMPRESS}}'; - # History expire in seconds - expire = 0; + # Expire in seconds for inactive keys, default to 5 days + expire = 432000; # History rows limit nrows = 200; # Use zstd compression when storing data in redis diff --git a/src/plugins/lua/known_senders.lua b/src/plugins/lua/known_senders.lua index 6d57acea3..5cb2ddcf5 100644 --- a/src/plugins/lua/known_senders.lua +++ b/src/plugins/lua/known_senders.lua @@ -18,6 +18,7 @@ limitations under the License. local rspamd_logger = require "rspamd_logger" local N = 'known_senders' +local E = {} local lua_util = require "lua_util" local lua_redis = require "lua_redis" local lua_maps = require "lua_maps" @@ -258,7 +259,7 @@ local function verify_local_replies_set(task) return nil end - local replies_recipients = task:get_recipients('mime') + local replies_recipients = task:get_recipients('mime') or E local replies_sender_string = lua_util.maybe_obfuscate_string(tostring(replies_sender), settings, settings.sender_prefix) diff --git a/src/plugins/lua/once_received.lua b/src/plugins/lua/once_received.lua index 2a5552ab9..5c5ff7986 100644 --- a/src/plugins/lua/once_received.lua +++ b/src/plugins/lua/once_received.lua @@ -19,10 +19,7 @@ if confighelp then end -- 0 or 1 received: = spam - local symbol = 'ONCE_RECEIVED' -local symbol_rdns = 'RDNS_NONE' -local symbol_rdns_dnsfail = 'RDNS_DNSFAIL' local symbol_mx = 'DIRECT_TO_MX' -- Symbol for strict checks local symbol_strict = nil @@ -47,54 +44,6 @@ local function check_quantity_received (task) return not h['flags']['artificial'] end, recvh)) - local function recv_dns_cb(_, to_resolve, results, err) - if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then - rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) - task:insert_result(symbol_rdns_dnsfail, 1.0) - end - - if not results then - if nreceived <= 1 then - task:insert_result(symbol, 1) - -- Avoid strict symbol inserting as the remaining symbols have already - -- quote a significant weight, so a message could be rejected by just - -- this property. - --task:insert_result(symbol_strict, 1) - -- Check for MUAs - local ua = task:get_header('User-Agent') - local xm = task:get_header('X-Mailer') - if (ua or xm) then - task:insert_result(symbol_mx, 1, (ua or xm)) - end - end - task:insert_result(symbol_rdns, 1) - else - rspamd_logger.infox(task, 'source hostname has not been passed to Rspamd from MTA, ' .. - 'but we could resolve source IP address PTR %s as "%s"', - to_resolve, results[1]) - task:set_hostname(results[1]) - - if good_hosts then - for _, gh in ipairs(good_hosts) do - if string.find(results[1], gh) then - return - end - end - end - - if nreceived <= 1 then - task:insert_result(symbol, 1) - for _, h in ipairs(bad_hosts) do - if string.find(results[1], h) then - - task:insert_result(symbol_strict, 1, h) - return - end - end - end - end - end - local task_ip = task:get_ip() if ((not check_authed and task:get_user()) or @@ -110,13 +59,39 @@ local function check_quantity_received (task) local hn = task:get_hostname() -- Here we don't care about received - if (not hn) and task_ip and task_ip:is_valid() then - task:get_resolver():resolve_ptr({ task = task, - name = task_ip:to_string(), - callback = recv_dns_cb, - forced = true - }) + if not hn then + if nreceived <= 1 then + task:insert_result(symbol, 1) + -- Avoid strict symbol inserting as the remaining symbols have already + -- quote a significant weight, so a message could be rejected by just + -- this property. + --task:insert_result(symbol_strict, 1) + -- Check for MUAs + local ua = task:get_header('User-Agent') + local xm = task:get_header('X-Mailer') + if (ua or xm) then + task:insert_result(symbol_mx, 1, (ua or xm)) + end + end return + else + if good_hosts then + for _, gh in ipairs(good_hosts) do + if string.find(hn, gh) then + return + end + end + end + + if nreceived <= 1 then + task:insert_result(symbol, 1) + for _, h in ipairs(bad_hosts) do + if string.find(hn, h) then + task:insert_result(symbol_strict, 1, h) + break + end + end + end end if nreceived <= 1 then @@ -181,10 +156,6 @@ if opts then for n, v in pairs(opts) do if n == 'symbol_strict' then symbol_strict = v - elseif n == 'symbol_rdns' then - symbol_rdns = v - elseif n == 'symbol_rdns_dnsfail' then - symbol_rdns_dnsfail = v elseif n == 'bad_host' then if type(v) == 'string' then bad_hosts[1] = v @@ -207,16 +178,6 @@ if opts then end rspamd_config:register_symbol({ - name = symbol_rdns, - type = 'virtual', - parent = id - }) - rspamd_config:register_symbol({ - name = symbol_rdns_dnsfail, - type = 'virtual', - parent = id - }) - rspamd_config:register_symbol({ name = symbol_strict, type = 'virtual', parent = id diff --git a/src/plugins/lua/spf.lua b/src/plugins/lua/spf.lua index 48f3c17be..356507250 100644 --- a/src/plugins/lua/spf.lua +++ b/src/plugins/lua/spf.lua @@ -56,6 +56,7 @@ local symbols = { dnsfail = "R_SPF_DNSFAIL", permfail = "R_SPF_PERMFAIL", na = "R_SPF_NA", + plusall = "R_SPF_PLUSALL", } local default_config = { @@ -118,6 +119,8 @@ local function spf_check_callback(task) local function flag_to_symbol(fl) if bit.band(fl, rspamd_spf.flags.temp_fail) ~= 0 then return local_config.symbols.dnsfail + elseif bit.band(fl, rspamd_spf.flags.plusall) ~= 0 then + return local_config.symbols.plusall elseif bit.band(fl, rspamd_spf.flags.perm_fail) ~= 0 then return local_config.symbols.permfail elseif bit.band(fl, rspamd_spf.flags.na) ~= 0 then diff --git a/src/rspamadm/configdump.c b/src/rspamadm/configdump.c index 167b4c891..456875cf2 100644 --- a/src/rspamadm/configdump.c +++ b/src/rspamadm/configdump.c @@ -1,5 +1,5 @@ /* - * Copyright 2023 Vsevolod Stakhov + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -445,6 +445,9 @@ rspamadm_configdump(int argc, char **argv, const struct rspamadm_command *cmd) ucl_object_fromdouble(gr->max_score), "max_score", strlen("max_score"), false); ucl_object_insert_key(gr_ucl, + ucl_object_fromdouble(gr->min_score), + "min_score", strlen("min_score"), false); + ucl_object_insert_key(gr_ucl, ucl_object_fromstring(gr->description), "description", strlen("description"), false); diff --git a/src/rspamadm/signtool.c b/src/rspamadm/signtool.c index 612a67c83..ddc3d45df 100644 --- a/src/rspamadm/signtool.c +++ b/src/rspamadm/signtool.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -27,7 +27,6 @@ #include <sys/wait.h> #endif -static gboolean openssl = FALSE; static gboolean verify = FALSE; static gboolean quiet = FALSE; static char *suffix = NULL; @@ -37,7 +36,6 @@ static char *pubout = NULL; static char *keypair_file = NULL; static char *editor = NULL; static gboolean edit = FALSE; -enum rspamd_cryptobox_mode mode = RSPAMD_CRYPTOBOX_MODE_25519; static void rspamadm_signtool(int argc, char **argv, const struct rspamadm_command *cmd); @@ -53,8 +51,6 @@ struct rspamadm_command signtool_command = { }; static GOptionEntry entries[] = { - {"openssl", 'o', 0, G_OPTION_ARG_NONE, &openssl, - "Generate openssl nistp256 keypair not curve25519 one", NULL}, {"verify", 'v', 0, G_OPTION_ARG_NONE, &verify, "Verify signatures and not sign", NULL}, {"suffix", 'S', 0, G_OPTION_ARG_STRING, &suffix, @@ -327,11 +323,8 @@ rspamadm_sign_file(const char *fname, struct rspamd_cryptobox_keypair *kp) exit(EXIT_FAILURE); } - g_assert(rspamd_cryptobox_MAX_SIGBYTES >= - rspamd_cryptobox_signature_bytes(mode)); - sk = rspamd_keypair_component(kp, RSPAMD_KEYPAIR_COMPONENT_SK, NULL); - rspamd_cryptobox_sign(sig, NULL, map, st.st_size, sk, mode); + rspamd_cryptobox_sign(sig, NULL, map, st.st_size, sk); if (edit) { /* We also need to rename .new file */ @@ -348,7 +341,7 @@ rspamadm_sign_file(const char *fname, struct rspamd_cryptobox_keypair *kp) rspamd_snprintf(sigpath, sizeof(sigpath), "%s%s", fname, suffix); - if (write(fd_sig, sig, rspamd_cryptobox_signature_bytes(mode)) == -1) { + if (write(fd_sig, sig, crypto_sign_bytes()) == -1) { rspamd_fprintf(stderr, "cannot write signature to %s: %s\n", sigpath, strerror(errno)); exit(EXIT_FAILURE); @@ -400,9 +393,6 @@ rspamadm_verify_file(const char *fname, const unsigned char *pk) struct stat st, st_sig; bool ret; - g_assert(rspamd_cryptobox_MAX_SIGBYTES >= - rspamd_cryptobox_signature_bytes(mode)); - if (suffix == NULL) { suffix = ".sig"; } @@ -439,7 +429,7 @@ rspamadm_verify_file(const char *fname, const unsigned char *pk) g_assert(fstat(fd_sig, &st_sig) != -1); - if (st_sig.st_size != rspamd_cryptobox_signature_bytes(mode)) { + if (st_sig.st_size != crypto_sign_bytes()) { close(fd_sig); rspamd_fprintf(stderr, "invalid signature size %s: %ud\n", fname, (unsigned int) st_sig.st_size); @@ -458,7 +448,7 @@ rspamadm_verify_file(const char *fname, const unsigned char *pk) } ret = rspamd_cryptobox_verify(map_sig, st_sig.st_size, - map, st.st_size, pk, mode); + map, st.st_size, pk); munmap(map, st.st_size); munmap(map_sig, st_sig.st_size); @@ -503,10 +493,6 @@ rspamadm_signtool(int argc, char **argv, const struct rspamadm_command *cmd) g_option_context_free(context); - if (openssl) { - mode = RSPAMD_CRYPTOBOX_MODE_NIST; - } - if (verify && (!pubkey && !pubkey_file)) { rspamd_fprintf(stderr, "no pubkey for verification\n"); exit(EXIT_FAILURE); @@ -549,14 +535,13 @@ rspamadm_signtool(int argc, char **argv, const struct rspamadm_command *cmd) flen--; } - pk = rspamd_pubkey_from_base32(map, flen, - RSPAMD_KEYPAIR_SIGN, mode); + pk = rspamd_pubkey_from_base32(map, flen, RSPAMD_KEYPAIR_SIGN); if (pk == NULL) { rspamd_fprintf(stderr, "bad size %s: %ud, %ud expected\n", pubkey_file, (unsigned int) flen, - rspamd_cryptobox_pk_sig_bytes(mode)); + crypto_sign_publickeybytes()); exit(EXIT_FAILURE); } @@ -564,13 +549,13 @@ rspamadm_signtool(int argc, char **argv, const struct rspamadm_command *cmd) } else { pk = rspamd_pubkey_from_base32(pubkey, strlen(pubkey), - RSPAMD_KEYPAIR_SIGN, mode); + RSPAMD_KEYPAIR_SIGN); if (pk == NULL) { rspamd_fprintf(stderr, "bad size %s: %ud, %ud expected\n", pubkey_file, (unsigned int) strlen(pubkey), - rspamd_cryptobox_pk_sig_bytes(mode)); + crypto_sign_publickeybytes()); exit(EXIT_FAILURE); } } diff --git a/src/rspamd_proxy.c b/src/rspamd_proxy.c index 4f08e81b9..dbdd2e5a7 100644 --- a/src/rspamd_proxy.c +++ b/src/rspamd_proxy.c @@ -395,7 +395,7 @@ rspamd_proxy_parse_upstream(rspamd_mempool_t *pool, elt = ucl_object_lookup(obj, "key"); if (elt != NULL) { up->key = rspamd_pubkey_from_base32(ucl_object_tostring(elt), 0, - RSPAMD_KEYPAIR_KEX, RSPAMD_CRYPTOBOX_MODE_25519); + RSPAMD_KEYPAIR_KEX); if (up->key == NULL) { g_set_error(err, rspamd_proxy_quark(), 100, @@ -571,7 +571,7 @@ rspamd_proxy_parse_mirror(rspamd_mempool_t *pool, elt = ucl_object_lookup(obj, "key"); if (elt != NULL) { up->key = rspamd_pubkey_from_base32(ucl_object_tostring(elt), 0, - RSPAMD_KEYPAIR_KEX, RSPAMD_CRYPTOBOX_MODE_25519); + RSPAMD_KEYPAIR_KEX); if (up->key == NULL) { g_set_error(err, rspamd_proxy_quark(), 100, @@ -1398,7 +1398,8 @@ proxy_backend_mirror_finish_handler(struct rspamd_http_connection *conn, bk_conn->err = "cannot parse ucl"; } - msg_info_session("finished mirror connection to %s", bk_conn->name); + msg_info_session("finished mirror connection to %s; HTTP code: %d", + bk_conn->name, msg->code); rspamd_upstream_ok(bk_conn->up); proxy_backend_close_connection(bk_conn); @@ -2203,6 +2204,8 @@ proxy_client_finish_handler(struct rspamd_http_connection *conn, rspamd_http_message_remove_header(msg, "Keep-Alive"); rspamd_http_message_remove_header(msg, "Connection"); rspamd_http_message_remove_header(msg, "Key"); + rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, session->pool->tag.uid, + sizeof(session->pool->tag.uid)); proxy_open_mirror_connections(session); rspamd_http_connection_reset(session->client_conn); @@ -2210,7 +2213,10 @@ proxy_client_finish_handler(struct rspamd_http_connection *conn, proxy_send_master_message(session); } else { - msg_info_session("finished master connection"); + msg_info_session("finished master connection to %s; HTTP code: %d", + rspamd_inet_address_to_string_pretty( + rspamd_upstream_addr_cur(session->master_conn->up)), + msg->code); proxy_backend_close_connection(session->master_conn); REF_RELEASE(session); } diff --git a/test/functional/cases/001_merged/101_lua.robot b/test/functional/cases/001_merged/101_lua.robot index 2cfc03677..51d5b4b23 100644 --- a/test/functional/cases/001_merged/101_lua.robot +++ b/test/functional/cases/001_merged/101_lua.robot @@ -52,4 +52,48 @@ External Maps Simple Task Inject Url Scan File ${URL_ICS} Settings={symbols_enabled = [TEST_INJECT_URL]} - Expect Symbol TEST_INJECT_URL
\ No newline at end of file + Expect Symbol TEST_INJECT_URL + +Group Score Positive + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_POSITIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_POSITIVE16 0 + +Group Score Negative + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_NEGATIVE1, GR_NEGATIVE2, GR_NEGATIVE4, GR_NEGATIVE8]} + Expect Symbol With Score GR_NEGATIVE1 -1 + Expect Symbol With Score GR_NEGATIVE2 -2 + Expect Symbol With Score GR_NEGATIVE4 -4 + Expect Symbol With Score GR_NEGATIVE8 -3 + +Group Score Mix 1 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_NEGATIVE1, GR_NEGATIVE2, GR_NEGATIVE4, GR_NEGATIVE8]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_NEGATIVE1 -1 + Expect Symbol With Score GR_NEGATIVE2 -2 + Expect Symbol With Score GR_NEGATIVE4 -4 + Expect Symbol With Score GR_NEGATIVE8 -8 + +Group Score Mix 2 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_POSITIVE2, GR_POSITIVE4, GR_POSITIVE8, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_POSITIVE2 2 + Expect Symbol With Score GR_POSITIVE4 4 + Expect Symbol With Score GR_POSITIVE8 3 + Expect Symbol With Score GR_NEGATIVE16 -16 + +Group Score Mix 3 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE1, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE1 1 + Expect Symbol With Score GR_NEGATIVE16 -11 + +Group Score Mix 4 + Scan File ${MESSAGE} Settings={symbols_enabled = [GR_POSITIVE16, GR_NEGATIVE16]} + Expect Symbol With Score GR_POSITIVE16 10 + Expect Symbol With Score GR_NEGATIVE16 -16
\ No newline at end of file diff --git a/test/functional/cases/001_merged/117_spf.robot b/test/functional/cases/001_merged/117_spf.robot index dda35f671..a8b35a8c9 100644 --- a/test/functional/cases/001_merged/117_spf.robot +++ b/test/functional/cases/001_merged/117_spf.robot @@ -155,3 +155,9 @@ SPF UPPERCASE ... IP=8.8.8.8 From=x@fail11.org.org.za ... Settings=${SETTINGS_SPF} Expect Symbol R_SPF_ALLOW + +SPF PLUSALL + Scan File ${RSPAMD_TESTDIR}/messages/dmarc/bad_dkim1.eml + ... IP=8.8.8.8 From=x@plusall.com + ... Settings=${SETTINGS_SPF} + Expect Symbol R_SPF_PLUSALL diff --git a/test/functional/cases/450_grow_factor.robot b/test/functional/cases/450_grow_factor.robot index 518c3ed82..85ca08c68 100644 --- a/test/functional/cases/450_grow_factor.robot +++ b/test/functional/cases/450_grow_factor.robot @@ -1,6 +1,7 @@ *** Settings *** Suite Setup Rspamd Setup Suite Teardown Rspamd Teardown +Library ${RSPAMD_TESTDIR}/lib/grow_factor.py Library ${RSPAMD_TESTDIR}/lib/rspamd.py Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot Variables ${RSPAMD_TESTDIR}/lib/vars.py @@ -9,29 +10,17 @@ Variables ${RSPAMD_TESTDIR}/lib/vars.py ${CONFIG} ${RSPAMD_TESTDIR}/configs/grow_factor.conf ${HAM_MESSAGE} ${RSPAMD_TESTDIR}/messages/ham.eml ${RSPAMD_SCOPE} Suite -&{RESCORED_SYMBOLS} -... SIMPLE_TEST_001=0.013067 -... SIMPLE_TEST_002=14.374194 -... SIMPLE_TEST_003=6.533724 -... SIMPLE_TEST_004=13.067449 -... SIMPLE_TEST_005=0.013067 -... SIMPLE_TEST_006=0.130674 -... SIMPLE_TEST_007=0.143741 -... SIMPLE_TEST_008=0.156809 -... SIMPLE_TEST_009=0.169876 -... SIMPLE_TEST_010=0.182944 -... SIMPLE_TEST_011=-0.010000 -... SIMPLE_TEST_012=-0.100000 -... SIMPLE_TEST_013=-10.000000 *** Test Cases *** CHECK BASIC Scan File ${HAM_MESSAGE} ... Settings={groups_enabled = [simple_tests]} Expect Required Score 15 + &{RESCORED_SYMBOLS} = Apply Grow Factor 1.1 15 Expect Symbols With Scores &{RESCORED_SYMBOLS} CHECK NOREJECT Scan File ${HAM_MESSAGE} ... Settings={actions { reject = null, "add header" = 15 }, groups_enabled = [simple_tests]} + &{RESCORED_SYMBOLS} = Apply Grow Factor 1.1 15 Expect Symbols With Scores &{RESCORED_SYMBOLS} diff --git a/test/functional/cases/550_milter_headers.robot b/test/functional/cases/550_milter_headers.robot new file mode 100644 index 000000000..80471b83c --- /dev/null +++ b/test/functional/cases/550_milter_headers.robot @@ -0,0 +1,39 @@ +*** Settings *** +Suite Setup Rspamd Setup +Suite Teardown Rspamd Teardown +Library ${RSPAMD_TESTDIR}/lib/rspamd.py +Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot +Variables ${RSPAMD_TESTDIR}/lib/vars.py + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/milter_headers.conf +${MESSAGE} ${RSPAMD_TESTDIR}/messages/zip.eml +${RSPAMD_SCOPE} Suite +${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat +${SETTINGS_NOSYMBOLS} {symbols_enabled = []} +${SETTINGS_TEST} {SIMPLE_TEST = 2.0, symbols_enabled = [SIMPLE_TEST]} + +*** Test Cases *** +CHECK HEADERS WITH TEST SYMBOL + Scan File ${MESSAGE} Settings=${SETTINGS_TEST} + # Check X-Virus header + Expect Removed Header X-Virus + Expect Added Header X-Virus Fires always + # Check My-Spamd-Bar header + Expect Added Header My-Spamd-Bar ++ + Do Not Expect Removed Header My-Spamd-Bar + # Check X-Spam-Level header + Expect Added Header X-Spam-Level ** + Expect Removed Header X-Spam-Level + +CHECK HEADERS WITHOUT TEST SYMBOL + Scan File ${MESSAGE} Settings=${SETTINGS_NOSYMBOLS} + # Check X-Virus header + Expect Removed Header X-Virus + Do Not Expect Added Header X-Virus + # Check My-Spamd-Bar header + Expect Added Header My-Spamd-Bar / + Do Not Expect Removed Header My-Spamd-Bar + # Check X-Spam-Level header + Do Not Expect Added Header X-Spam-Level + Expect Removed Header X-Spam-Level diff --git a/test/functional/configs/grow_factor-local.conf b/test/functional/configs/grow_factor-local.conf index cdf1bf436..80b60b43b 100644 --- a/test/functional/configs/grow_factor-local.conf +++ b/test/functional/configs/grow_factor-local.conf @@ -6,3 +6,7 @@ metric { greylist = 4; } } + +options { + check_all_filters = true; +}
\ No newline at end of file diff --git a/test/functional/configs/merged-local.conf b/test/functional/configs/merged-local.conf index 75b9f0554..2aef274c2 100644 --- a/test/functional/configs/merged-local.conf +++ b/test/functional/configs/merged-local.conf @@ -229,6 +229,11 @@ options = { replies = ["v=spf1 -all"]; }, { + name = "plusall.com", + type = "txt"; + replies = ["v=spf1 +all"]; + }, + { name = "fail4.org.org.za", type = "txt"; replies = ["v=spf1 redirect=asdfsfewewrredfs"]; @@ -960,6 +965,45 @@ symbols { } } +group "test" { + max_score = 10; + min_score = -10; + + symbols = { + "GR_POSITIVE1" = { + score = 1.0; + }, + "GR_POSITIVE2" = { + score = 2.0; + }, + "GR_POSITIVE4" = { + score = 4.0; + }, + "GR_POSITIVE8" = { + score = 8.0; + }, + "GR_POSITIVE16" = { + score = 16.0; + }, + + "GR_NEGATIVE1" = { + score = -1.0; + }, + "GR_NEGATIVE2" = { + score = -2.0; + }, + "GR_NEGATIVE4" = { + score = -4.0; + }, + "GR_NEGATIVE8" = { + score = -8.0; + }, + "GR_NEGATIVE16" = { + score = -16.0; + }, + } +} + worker "controller" { bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_CONTROLLER =}"; keypair { diff --git a/test/functional/configs/merged.conf b/test/functional/configs/merged.conf index 0ee224ceb..06a34308d 100644 --- a/test/functional/configs/merged.conf +++ b/test/functional/configs/merged.conf @@ -11,6 +11,7 @@ lua = "{= env.TESTDIR =}/lua/recipients.lua" lua = "{= env.TESTDIR =}/lua/remove_result.lua" lua = "{= env.TESTDIR =}/lua/tlds.lua" lua = "{= env.TESTDIR =}/lua/inject_url.lua" +lua = "{= env.TESTDIR =}/lua/limits.lua" # 104_get_from lua = "{= env.TESTDIR =}/lua/get_from.lua" diff --git a/test/functional/configs/milter_headers.conf b/test/functional/configs/milter_headers.conf new file mode 100644 index 000000000..947bc28dd --- /dev/null +++ b/test/functional/configs/milter_headers.conf @@ -0,0 +1,24 @@ +lua = "{= env.TESTDIR =}/lua/simple.lua" + +.include(duplicate=append,priority=0) "{= env.TESTDIR =}/configs/plugins.conf" + +milter_headers { + + use = ["remove-headers", "x-spam-level", "x-spamd-bar", "x-virus"]; + + routines { + remove-headers { + headers { + "X-Spam-Level" = 0, + } + } + x-spamd-bar { + header = "My-Spamd-Bar"; + remove = null; + } + x-virus { + symbols = ["SIMPLE_TEST"]; + } + } + +} diff --git a/test/functional/lib/grow_factor.py b/test/functional/lib/grow_factor.py new file mode 100644 index 000000000..486a186e8 --- /dev/null +++ b/test/functional/lib/grow_factor.py @@ -0,0 +1,32 @@ +from robot.libraries.BuiltIn import BuiltIn + +def Apply_Grow_Factor(grow_factor, max_limit): + grow_factor = float(grow_factor) + max_limit = float(max_limit) + expected_result = {} + res = BuiltIn().get_variable_value("${SCAN_RESULT}") + + for sym, p in res["symbols"].items(): + expected_result[sym] = p["score"] + + if grow_factor <= 1.0: + return expected_result + + if max_limit <= 0: + return expected_result + + final_grow_factor = grow_factor + mult = grow_factor - 1.0 + for sym, p in res["symbols"].items(): + if p["score"] <= 0: + continue + mult *= round(p["score"] / max_limit, 2) + final_grow_factor *= round(1.0 + mult, 2) + + if final_grow_factor <= 1.0: + return expected_result + + for sym, p in res["symbols"].items(): + if p["score"] <= 0: + continue + expected_result[sym] = round(p["score"] * final_grow_factor, 2) diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot index 254a6f3ca..de4e5285f 100644 --- a/test/functional/lib/rspamd.robot +++ b/test/functional/lib/rspamd.robot @@ -60,6 +60,28 @@ Check Rspamc Match String Should Not Contain ${subject} ${str} END +Do Not Expect Added Header + [Arguments] ${header_name} + IF 'milter' not in ${SCAN_RESULT} + RETURN + END + IF 'add_headers' not in ${SCAN_RESULT}[milter] + RETURN + END + Dictionary Should Not Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name} + ... msg=${header_name} was added + +Do Not Expect Removed Header + [Arguments] ${header_name} + IF 'milter' not in ${SCAN_RESULT} + RETURN + END + IF 'remove_headers' not in ${SCAN_RESULT}[milter] + RETURN + END + Dictionary Should Not Contain Key ${SCAN_RESULT}[milter][remove_headers] ${header_name} + ... msg=${header_name} was removed + Do Not Expect Symbol [Arguments] ${symbol} Dictionary Should Not Contain Key ${SCAN_RESULT}[symbols] ${symbol} @@ -76,10 +98,31 @@ Expect Action [Arguments] ${action} Should Be Equal ${SCAN_RESULT}[action] ${action} +Expect Added Header + [Arguments] ${header_name} ${header_value} ${pos}=-1 + Dictionary Should Contain Key ${SCAN_RESULT} milter + ... msg=milter block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter] add_headers + ... msg=add_headers block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name} + ... msg=${header_name} was not added + Should Be Equal ${SCAN_RESULT}[milter][add_headers][${header_name}][value] ${header_value} + Should Be Equal as Numbers ${SCAN_RESULT}[milter][add_headers][${header_name}][order] ${pos} + Expect Email [Arguments] ${email} List Should Contain Value ${SCAN_RESULT}[emails] ${email} +Expect Removed Header + [Arguments] ${header_name} ${pos}=0 + Dictionary Should Contain Key ${SCAN_RESULT} milter + ... msg=milter block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter] remove_headers + ... msg=remove_headers block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter][remove_headers] ${header_name} + ... msg=${header_name} was not removed + Should Be Equal as Numbers ${SCAN_RESULT}[milter][remove_headers][${header_name}] ${pos} + Expect Required Score [Arguments] ${required_score} Should Be Equal As Numbers ${SCAN_RESULT}[required_score] ${required_score} @@ -302,7 +345,22 @@ Run Rspamd Export Scoped Variables ${RSPAMD_SCOPE} RSPAMD_PROCESS=${result} # Confirm worker is reachable - Wait Until Keyword Succeeds 15x 1 sec Ping Rspamd ${RSPAMD_LOCAL_ADDR} ${check_port} + FOR ${index} IN RANGE 37 + ${ok} = Rspamd Startup Check ${check_port} + IF ${ok} CONTINUE + Sleep 0.4s + END + +Rspamd Startup Check + [Arguments] ${check_port}=${RSPAMD_PORT_NORMAL} + ${handle} = Get Process Object + ${res} = Evaluate $handle.poll() + IF ${res} != None + ${stderr} = Get File ${RSPAMD_TMPDIR}/rspamd.stderr + Fail Process Is Gone, stderr: ${stderr} + END + ${ping} = Run Keyword And Return Status Ping Rspamd ${RSPAMD_LOCAL_ADDR} ${check_port} + [Return] ${ping} Rspamadm Setup ${RSPAMADM_TMPDIR} = Make Temporary Directory diff --git a/test/functional/lua/deps.lua b/test/functional/lua/deps.lua index 6171db699..b78d3abb7 100644 --- a/test/functional/lua/deps.lua +++ b/test/functional/lua/deps.lua @@ -24,7 +24,7 @@ rspamd_config:register_virtual_symbol('TOP', 1.0, id) rspamd_config:register_symbol('DEP1', 1.0, cb_dep1) rspamd_config:register_dependency('DEP1', 'TOP') -for i = 2,10 do +for i = 2, 10 do rspamd_config:register_symbol('DEP' .. tostring(i), 1.0, cb_gen(i - 1)) rspamd_config:register_dependency('DEP' .. tostring(i), 'DEP' .. tostring(i - 1)) end diff --git a/test/functional/lua/limits.lua b/test/functional/lua/limits.lua new file mode 100644 index 000000000..52fb47fb9 --- /dev/null +++ b/test/functional/lua/limits.lua @@ -0,0 +1,22 @@ +local true_cb_gen = function() + return function() + return true + end +end + +local test_weights = { 1, 2, 4, 8, 16 } +for _, i in ipairs(test_weights) do + rspamd_config:register_symbol('GR_POSITIVE' .. tostring(i), 1.0, true_cb_gen()) + + if i > 1 then + rspamd_config:register_dependency('GR_POSITIVE' .. tostring(i), 'GR_POSITIVE' .. tostring(i / 2)) + end + + rspamd_config:register_symbol('GR_NEGATIVE' .. tostring(i), 1.0, true_cb_gen()) + + if i > 1 then + rspamd_config:register_dependency('GR_NEGATIVE' .. tostring(i), 'GR_NEGATIVE' .. tostring(i / 2)) + end +end + +rspamd_config:register_dependency('GR_NEGATIVE1', 'GR_POSITIVE16')
\ No newline at end of file diff --git a/test/functional/lua/simple_plus.lua b/test/functional/lua/simple_plus.lua index 57aee6ae3..badde685f 100644 --- a/test/functional/lua/simple_plus.lua +++ b/test/functional/lua/simple_plus.lua @@ -1,7 +1,7 @@ local test_symbols = { SIMPLE_TEST_001 = 0.01, - SIMPLE_TEST_002 = 11, - SIMPLE_TEST_003 = 5.0, + SIMPLE_TEST_002 = 5.5, + SIMPLE_TEST_003 = 2.5, SIMPLE_TEST_004 = 10.0, SIMPLE_TEST_005 = 0.01, SIMPLE_TEST_006 = 0.10, diff --git a/test/lua/unit/rsa.lua b/test/lua/unit/rsa.lua index c67a36abb..019212df4 100644 --- a/test/lua/unit/rsa.lua +++ b/test/lua/unit/rsa.lua @@ -47,4 +47,14 @@ context("RSA signature verification test", function() sk, pk = rsa.keypair() assert_false(rsa.verify_memory(pk, sig, "test")) end) + + test("RSA-2048 keypair + sign + verify", function() + local sk, pk = rsa.keypair(2048) + local sig = rsa.sign_memory(sk, "test") + assert_true(rsa.verify_memory(pk, sig, "test")) + assert_false(rsa.verify_memory(pk, sig, "test1")) + -- Overwrite + sk, pk = rsa.keypair(2048) + assert_false(rsa.verify_memory(pk, sig, "test")) + end) end) diff --git a/test/lua/unit/ucl.lua b/test/lua/unit/ucl.lua new file mode 100644 index 000000000..1b975d390 --- /dev/null +++ b/test/lua/unit/ucl.lua @@ -0,0 +1,75 @@ +-- Test some UCL stuff + +context("UCL manipulation", function() + local ucl = require "ucl" + + local parser = ucl.parser() + local res, err = parser:parse_string('{"key":"val"}') + assert(res) + + local reply = parser:get_object_wrapped() + local expected = { + key = 'ohlol', + ololo = 'ohlol' + } + + test("UCL transparent test: object", function() + assert_equal(tostring(reply), '{"key":"val"}') + assert_equal(reply:type(), 'object') + assert_equal(reply:at('key'):unwrap(), 'val') + reply.ololo = 'ohlol' + reply.ololo = 'ohlol' + reply.key = 'ohlol' + assert_equal(reply:at('key'):unwrap(), 'ohlol') + + for k, v in reply:pairs() do + assert_equal(expected[k], v:unwrap()) + end + end) + + test("UCL transparent test: array", function() + parser = ucl.parser() + res, err = parser:parse_string('["e1","e2"]') + assert(res) + local ireply = parser:get_object_wrapped() + + assert_equal(tostring(ireply), '["e1","e2"]') + assert_equal(ireply:type(), 'array') + ireply[1] = 1 + ireply[1] = 1 + ireply[1] = 1 + ireply[1] = 1 + ireply[1] = 1 + ireply[ireply:len() + 1] = 100500 + local iexpected = { 1, "e2", 100500 } + for k, v in ireply:ipairs() do + assert_equal(v:unwrap(), iexpected[k]) + end + end) + + test("UCL transparent test: concat", function() + reply.tbl = ireply + expected.tbl = iexpected + for k, v in reply:pairs() do + if type(expected[k]) == 'table' then + for kk, vv in v:ipairs() do + assert_equal(expected[k][kk], vv:unwrap()) + end + else + assert_equal(expected[k], v:unwrap()) + end + end + end) + + test("UCL transparent test: implicit conversion array->object", function() + -- Assign empty table, so it'll be an array + reply.t = {} + assert_equal(reply.t:type(), 'array') + -- We can convert empty table to object + reply.t.test = 'test' + assert_equal(reply.t:type(), 'object') + assert_equal(reply.t.test:unwrap(), 'test') + end) + + collectgarbage() -- To ensure we don't crash with asan +end)
\ No newline at end of file diff --git a/test/rspamd_cryptobox_test.c b/test/rspamd_cryptobox_test.c index b32b2822b..03b833404 100644 --- a/test/rspamd_cryptobox_test.c +++ b/test/rspamd_cryptobox_test.c @@ -1,11 +1,11 @@ -/*- - * Copyright 2016 Vsevolod Stakhov +/* + * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -24,7 +24,6 @@ static const int mapping_size = 64 * 8192 + 1; static const int max_seg = 32; static const int random_fuzz_cnt = 10000; -enum rspamd_cryptobox_mode mode = RSPAMD_CRYPTOBOX_MODE_25519; static void * create_mapping(int mapping_len, unsigned char **beg, unsigned char **end) @@ -52,7 +51,7 @@ check_result(const rspamd_nm_t key, const rspamd_nonce_t nonce, uint64_t *t = (uint64_t *) begin; g_assert(rspamd_cryptobox_decrypt_nm_inplace(begin, end - begin, nonce, key, - mac, mode)); + mac)); while (t < (uint64_t *) end) { g_assert(*t == 0); @@ -174,33 +173,17 @@ void rspamd_cryptobox_test_func(void) /* Test baseline */ t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encrypt_nm_inplace(begin, end - begin, nonce, key, mac, - mode); + rspamd_cryptobox_encrypt_nm_inplace(begin, end - begin, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); msg_info("baseline encryption: %.0f", t2 - t1); - mode = RSPAMD_CRYPTOBOX_MODE_NIST; - t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encrypt_nm_inplace(begin, - end - begin, - nonce, - key, - mac, - mode); - t2 = rspamd_get_ticks(TRUE); - check_result(key, nonce, mac, begin, end); - - msg_info("openssl baseline encryption: %.0f", t2 - t1); - mode = RSPAMD_CRYPTOBOX_MODE_25519; - -start: /* A single chunk as vector */ seg[0].data = begin; seg[0].len = end - begin; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 1, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 1, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -213,7 +196,7 @@ start: seg[1].data = begin + seg[0].len; seg[1].len = (end - begin) - seg[0].len; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -225,7 +208,7 @@ start: seg[1].data = begin + seg[0].len; seg[1].len = (end - begin) - seg[0].len; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -237,7 +220,7 @@ start: seg[1].data = begin + seg[0].len; seg[1].len = (end - begin) - seg[0].len; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -250,7 +233,7 @@ start: seg[1].data = begin + seg[0].len; seg[1].len = (end - begin) - seg[0].len; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 2, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -265,7 +248,7 @@ start: seg[2].data = begin + seg[0].len + seg[1].len; seg[2].len = (end - begin) - seg[0].len - seg[1].len; t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, 3, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, 3, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -274,7 +257,7 @@ start: cnt = create_random_split(seg, max_seg, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -283,7 +266,7 @@ start: cnt = create_realistic_split(seg, max_seg, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -292,7 +275,7 @@ start: cnt = create_constrained_split(seg, max_seg + 1, 32, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -303,7 +286,7 @@ start: ms = ottery_rand_range(i % max_seg * 2) + 1; cnt = create_random_split(seg, ms, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -316,7 +299,7 @@ start: ms = ottery_rand_range(i % max_seg * 2) + 1; cnt = create_realistic_split(seg, ms, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -329,7 +312,7 @@ start: ms = ottery_rand_range(i % max_seg * 10) + 1; cnt = create_constrained_split(seg, ms, i, begin, end); t1 = rspamd_get_ticks(TRUE); - rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac, mode); + rspamd_cryptobox_encryptv_nm_inplace(seg, cnt, nonce, key, mac); t2 = rspamd_get_ticks(TRUE); check_result(key, nonce, mac, begin, end); @@ -338,10 +321,4 @@ start: msg_info("constrained fuzz iterations: %d", i); } } - - if (!checked_openssl) { - checked_openssl = TRUE; - mode = RSPAMD_CRYPTOBOX_MODE_NIST; - goto start; - } } diff --git a/test/rspamd_cxx_unit.cxx b/test/rspamd_cxx_unit.cxx index 3dde89d1c..b7cb0c6bf 100644 --- a/test/rspamd_cxx_unit.cxx +++ b/test/rspamd_cxx_unit.cxx @@ -24,6 +24,7 @@ #include "rspamd_cxx_unit_utils.hxx" #include "rspamd_cxx_local_ptr.hxx" #include "rspamd_cxx_unit_dkim.hxx" +#include "rspamd_cxx_unit_cryptobox.hxx" static gboolean verbose = false; static const GOptionEntry entries[] = @@ -83,4 +84,4 @@ int main(int argc, char **argv) rspamd_mempool_delete(pool); return res; -}
\ No newline at end of file +} diff --git a/test/rspamd_cxx_unit_cryptobox.hxx b/test/rspamd_cxx_unit_cryptobox.hxx new file mode 100644 index 000000000..5829b1e43 --- /dev/null +++ b/test/rspamd_cxx_unit_cryptobox.hxx @@ -0,0 +1,182 @@ +/* + * Copyright 2024 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Detached unit tests for the cryptobox */ + +#ifndef RSPAMD_RSPAMD_CXX_UNIT_CRYPTOBOX_HXX +#define RSPAMD_RSPAMD_CXX_UNIT_CRYPTOBOX_HXX +#include "libcryptobox/cryptobox.h" +#include <string> +#include <string_view> + +TEST_SUITE("rspamd_cryptobox") +{ + + TEST_CASE("rspamd_cryptobox_keypair") + { + rspamd_sk_t sk; + rspamd_pk_t pk; + + rspamd_cryptobox_keypair(pk, sk); + } + + TEST_CASE("rspamd_cryptobox_keypair_sig") + { + rspamd_sig_sk_t sk; + rspamd_sig_pk_t pk; + + rspamd_cryptobox_keypair_sig(pk, sk); + } + + TEST_CASE("rspamd_cryptobox_hash") + { + rspamd_cryptobox_hash_state_t p = {0}; + std::string key{"key"}; + + rspamd_cryptobox_hash_init(&p, reinterpret_cast<const unsigned char *>(key.data()), key.size()); + std::string data{"key"}; + rspamd_cryptobox_hash_update(&p, reinterpret_cast<const unsigned char *>(data.data()), data.size()); + + unsigned char out1[rspamd_cryptobox_HASHBYTES]; + rspamd_cryptobox_hash_final(&p, out1); + + unsigned char out2[rspamd_cryptobox_HASHBYTES]; + rspamd_cryptobox_hash(out2, + reinterpret_cast<const unsigned char *>(data.data()), data.size(), + reinterpret_cast<const unsigned char *>(key.data()), key.size()); + CHECK(memcmp(out1, out2, sizeof(out1)) == 0); + } + + TEST_CASE("rspamd_cryptobox_fast_hash") + { + rspamd_cryptobox_fast_hash_state_s *st = rspamd_cryptobox_fast_hash_new(); + uint64_t seed = 10; + rspamd_cryptobox_fast_hash_init(st, seed); + std::string data{"key"}; + + rspamd_cryptobox_fast_hash_update(st, + reinterpret_cast<const unsigned char *>(data.data()), + data.size()); + + uint64_t out1 = rspamd_cryptobox_fast_hash_final(st); + CHECK(out1 == 358126267837521635); + + uint64_t out2 = rspamd_cryptobox_fast_hash(reinterpret_cast<const unsigned char *>(data.data()), + data.size(), seed); + CHECK(out1 == out2); + + rspamd_cryptobox_fast_hash_free(st); + } + + TEST_CASE("rspamd_cryptobox_pbkdf") + { + std::string pass{"passpa"}; + std::string salt{"salt"}; + + uint8_t key1[256] = {0}; + gsize key_len1 = sizeof(key1); + + uint8_t key2[256] = {0}; + gsize key_len2 = sizeof(key2); + + unsigned int complexity = 10; + enum rspamd_cryptobox_pbkdf_type type = RSPAMD_CRYPTOBOX_PBKDF2; + + + CHECK(rspamd_cryptobox_pbkdf(pass.data(), pass.size(), + reinterpret_cast<const unsigned char *>(salt.data()), salt.size(), + key1, key_len1, complexity, type)); + CHECK(rspamd_cryptobox_pbkdf(pass.data(), pass.size(), + reinterpret_cast<const unsigned char *>(salt.data()), salt.size(), + key2, key_len2, complexity, type)); + CHECK(memcmp(key1, key2, key_len1) == 0); + + type = RSPAMD_CRYPTOBOX_CATENA; + CHECK(rspamd_cryptobox_pbkdf(pass.data(), pass.size(), + reinterpret_cast<const unsigned char *>(salt.data()), salt.size(), + key1, key_len1, complexity, type)); + CHECK(rspamd_cryptobox_pbkdf(pass.data(), pass.size(), + reinterpret_cast<const unsigned char *>(salt.data()), salt.size(), + key2, key_len2, complexity, type)); + CHECK(memcmp(key1, key2, key_len1) == 0); + } + + + TEST_CASE("rspamd_cryptobox_encrypt_inplace_25519") + { + unsigned char data[256]; + gsize len = sizeof(data); + rspamd_nonce_t nonce; + rspamd_pk_t pk; + rspamd_sk_t sk; + rspamd_mac_t sig; + + ottery_rand_bytes(nonce, sizeof(nonce)); + + rspamd_cryptobox_keypair(pk, sk); + + memset(sig, 0, sizeof(sig)); + + rspamd_cryptobox_encrypt_inplace(data, len, nonce, pk, sk, sig); + + CHECK(rspamd_cryptobox_decrypt_inplace(data, len, nonce, pk, sk, sig)); + } + + TEST_CASE("rspamd_cryptobox_sign_25519") + { + rspamd_sig_sk_t sk; + rspamd_sig_pk_t pk; + unsigned char sig[256]; + unsigned long long siglen; + std::string m{"data to be signed"}; + + rspamd_cryptobox_keypair_sig(pk, sk); + + rspamd_cryptobox_sign(sig, &siglen, + reinterpret_cast<const unsigned char *>(m.data()), m.size(), sk); + bool check_result = rspamd_cryptobox_verify(sig, siglen, + reinterpret_cast<const unsigned char *>(m.data()), m.size(), + pk); + CHECK(check_result == true); + } + + TEST_CASE("rspamd_keypair_encryption") + { + auto *kp = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX); + std::string data{"data to be encrypted"}; + unsigned char *out; + gsize outlen; + GError *err = nullptr; + + auto ret = rspamd_keypair_encrypt(kp, reinterpret_cast<const unsigned char *>(data.data()), data.size(), + &out, &outlen, &err); + CHECK(ret); + CHECK(err == nullptr); + + unsigned char *decrypted; + gsize decrypted_len; + ret = rspamd_keypair_decrypt(kp, out, outlen, &decrypted, &decrypted_len, &err); + CHECK(ret); + CHECK(err == nullptr); + CHECK(decrypted_len == data.size()); + CHECK(data == std::string_view{reinterpret_cast<const char *>(decrypted), decrypted_len}); + + g_free(out); + g_free(decrypted); + } +} + +#endif |