diff --git a/.gitignore b/.gitignore index f54af81..fc748c9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,9 +32,11 @@ cert-STR52 cert-STR53 compile_commands.json dynamic_pointer_cast +lookAndSay safeComparison slice slide +strcat +strcat_s timeConversion to_string -lookAndSay diff --git a/CMakeLists.txt b/CMakeLists.txt index a87463d..80cfff2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,14 @@ -cmake_minimum_required(VERSION 3.15...3.19) +cmake_minimum_required(VERSION 3.14...3.30) option(CMAKE_EXPORT_COMPILE_COMMANDS "create compile_commands.json" ON) -project(cert-test LANGUAGES CXX VERSION 0.1.8) +project(cert-test LANGUAGES CXX C VERSION 0.1.9) #---------------------------------------------------------- # Compiler config #---------------------------------------------------------- if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) endif() @@ -28,12 +28,16 @@ find_program(CLANG_TIDY if(CLANG_TIDY) if(ENABLE_CLANG_TIDY) message(STATUS "set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY})") - set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_CMD} CACHE STRING "forced!" FORCE) + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY} CACHE STRING "forced!" FORCE) else() set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "forced!" FORCE) # delete it endif() endif() +add_library(rary STATIC library.cpp library.h) +target_include_directories(rary PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(rary PRIVATE cxx_std_98) + #---------------------------------------------------------- enable_testing() #---------------------------------------------------------- @@ -47,7 +51,7 @@ foreach(TEST_SOURCE ${TESTS}) add_executable(${TEST_TARGET} ${TEST_SOURCE}) target_compile_features(${TEST_TARGET} PRIVATE cxx_std_17) - target_link_libraries(${TEST_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/library.a) + target_link_libraries(${TEST_TARGET} PRIVATE rary) add_test(NAME "${TEST_TARGET}" COMMAND "${TEST_TARGET}") endforeach() @@ -98,8 +102,9 @@ find_package(Boost CONFIG) if(TARGET Boost::headers AND TARGET fmt::fmt) - add_executable(to_string to_string.cpp) - target_link_libraries(to_string fmt::fmt Boost::headers) + #FIXME! CK + #XXX add_executable(to_string to_string.cpp) + #XXX target_link_libraries(to_string fmt::fmt Boost::headers) endif() @@ -114,6 +119,10 @@ target_compile_features(dynamic_pointer_cast PRIVATE cxx_std_17) add_executable(slice slice.cpp) target_compile_features(slice PRIVATE cxx_std_17) +add_executable(strcat_s strcat_s.c) +target_compile_features(strcat_s PRIVATE cxx_std_17) +add_test(NAME strcat_s COMMAND strcat_s) + add_executable(lookAndSay lookAndSay.cpp) target_compile_features(lookAndSay PRIVATE cxx_std_17) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..c6a4af3 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,82 @@ +{ + "version": 9, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "${presetName}" + }, + "environment": { + "PRESET_NAME": "${presetName}" + } + }, + { + "name": "Debug", + "inherits": "default" + }, + { + "name": "Release", + "inherits": "default" + } + ], + "buildPresets": [ + { + "name": "Debug", + "configurePreset": "Debug" + }, + { + "name": "Release", + "configurePreset": "Release" + } + ], + "testPresets": [ + { + "name": "Debug", + "configurePreset": "Debug" + }, + { + "name": "Release", + "configurePreset": "Release" + } + + ], + "workflowPresets": [ + { + "name": "Debug", + "steps": [ + { + "type": "configure", + "name": "Debug" + }, + { + "type": "build", + "name": "Debug" + }, + { + "type": "test", + "name": "Debug" + } + ] + }, + { + "name": "Release", + "steps": [ + { + "type": "configure", + "name": "Release" + }, + { + "type": "build", + "name": "Release" + }, + { + "type": "test", + "name": "Release" + } + ] + } + ] +} diff --git a/GNUmakefile b/GNUmakefile index 8c23ca7..4e039c0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -21,13 +21,13 @@ GENERATOR?=-G Ninja TARGET_ARCH:= CPPFLAGS?=-isystem /usr/local/include -#XXX CC:=gcc-10 +#XXX CC:=gcc-14 CC:=clang -CFLAGS:=-std=c11 -Wextra -Wpedantic -Wshadow +CFLAGS:=-std=c17 -Wextra -Wpedantic -Wshadow -#XXX CXX:=g++-10 +#XXX CXX:=g++-14 CXX:=clang++ -CXXFLAGS:=-std=c++17 -Wextra -Wpedantic -Wshadow +CXXFLAGS:=-std=c++17 -Wextra -Wpedantic -Wshadow -Warray-bounds LDLIBS:=$(CURDIR)/library.a LDFLAGS:=-L/usr/local/lib @@ -41,7 +41,7 @@ BUILDDIR?=$(CURDIR)/build UNAME:=$(shell uname) ifeq ($(UNAME),Darwin) SCAN_BUILD?=/usr/local/opt/llvm/bin/scan-build - #XXX CPPFLAGS+=-isystem /usr/local/Cellar/llvm/11.0.0/include/c++/v1/ + #XXX CPPFLAGS+=-isystem /usr/local/Cellar/llvm/19.1.0/include/c++/v1/ else CLANG_VERSION:=$(shell clang --version | grep -w version | perl -n -e 'print if s/^.*clang version (\d+)\..*/$$1/') SCAN_BUILD:=$(shell which scan-build-$(CLANG_VERSION) || which scan-build) @@ -49,7 +49,7 @@ endif # -# from https://wiki.sei.cmu.edu/confluence/display/cplusplus/Clang +#XXX # from https://wiki.sei.cmu.edu/confluence/display/cplusplus/Clang # #XXX CXXFLAGS+=-analyzer-checker=cplusplus # EXP51-CPP. Do not delete an array through a pointer of the incorrect type @@ -89,7 +89,8 @@ export CC .PHONY: init all build check test format clean distclean TESTS:=$(wildcard cert-*.cpp) -#XXX TESTS+=cereal-test.cpp dynamic_pointer_cast.cpp safeComparison.cpp slice.cpp slide.cpp timeConversion.cpp to_string.cpp +#XXX TESTS+= strcat.cpp +#XXX TESTS+= cereal-test.cpp dynamic_pointer_cast.cpp safeComparison.cpp slice.cpp slide.cpp timeConversion.cpp to_string.cpp PROGRAMS:=$(TESTS:%.cpp=%) ###################################### diff --git a/cert-CTR58.cpp b/cert-CTR58.cpp index 130ab6a..c9aa059 100644 --- a/cert-CTR58.cpp +++ b/cert-CTR58.cpp @@ -10,7 +10,7 @@ namespace { -class MutablePredicate : public std::unary_function +class MutablePredicate : public std::__unary_function { size_t timesCalled; diff --git a/strcat.cpp b/strcat.cpp new file mode 100644 index 0000000..9aff1d3 --- /dev/null +++ b/strcat.cpp @@ -0,0 +1,110 @@ +// Abstract +// +// The standard C library includes functions that are designed to prevent +// buffer overflows, particularly strncpy() and strncat(). These +// universally available functions discard data larger than the specified +// length, regardless of whether it fits into the buffer. These functions +// are deprecated for new Windows code because they are frequently used +// incorrectly. +// +// see too +// https://us-cert.cisa.gov/bsi/articles/knowledge/coding-practices/strncpy-and-strncat +// https://us-cert.cisa.gov/bsi/articles/knowledge/coding-practices/strlcpy-and-strlcat +// and +// https://us-cert.cisa.gov/bsi/articles/knowledge/coding-practices/strcpy_s-and-strcat_s +// +#include +#include +#include + +constexpr size_t MAXPATHLEN{256}; +constexpr size_t MAX_STRING_LEN{50}; +constexpr const char* source = {"A not too long sentence here! "}; +constexpr const char* more = {"Too mutch ... text!!!!!"}; +const char *dir = "/usr/local/etc/snmp/"; +const char *file = "snmpd.conf"; + +namespace { + +#if !(defined(__APPLE__) || defined(__BSD__)) +// If the return value is >= dstsize, the output string has been +// truncated. It is the caller's responsibility to handle this +size_t strlcpy(char* dst, const char* src, size_t len) +{ + int n = snprintf(dst, len, "%s", src); + assert(n >= 0 && (size_t)n < len); + + return n; +} +#endif + +void good_sample() +{ + char pname[MAXPATHLEN]; + size_t capacity = sizeof(pname); + + // ... + + size_t n = strlcpy(pname, dir, capacity); + if (n >= capacity) + { + goto toolong; + } + + capacity = sizeof(pname) - n ; + if (strlcpy(pname + n, file, capacity ) >= capacity) + { + goto toolong; + } + + std::cout << pname << std::endl; + return; + +toolong: + std::cerr << "truncated pname!" << std::endl; + exit(EXIT_FAILURE); +} + +} // namespace local + +int main() +{ + good_sample(); + + char dest[MAX_STRING_LEN + 1]; + +#undef USE_STRNCPY +#ifndef USE_STRNCPY + strlcpy(dest, source, MAX_STRING_LEN); +#else + // The strncpy() function doesn't null terminate the destination + // string if the source string is at least as long as the + // destination. (This behavior is defined by the C99 specification.) + // As a result, the destination string MUST be null terminated after + // calling strncpy(). + strncpy(dest, source, MAX_STRING_LEN); + dest[MAX_STRING_LEN] = '\0'; // NOTE: to be sure! CK + + // There's also a performance problem with strncpy() in that it fills + // the entire destination buffer with null bytes after the source + // data has been exhausted! +#endif + + std::cout << dest << std::endl; + +#undef SHOW_THE_BUG +#ifdef SHOW_THE_BUG + strncat(dest, source, MAX_STRING_LEN); // FIXME: buffer overflow! + std::cout << dest << std::endl; + dest[MAX_STRING_LEN] = '\0'; +#endif + + // The problem is that the last argument to strncat() should NOT be + // the total buffer length! It should be the space remaining after + // the call to strncpy(). Both functions require that you specify the + // remaining space and not the total size of the buffer. + strncat(dest, more, sizeof(dest) - strlen(dest) - 1); + + assert(strlen(dest) == MAX_STRING_LEN); + std::cout << dest << std::endl; +} diff --git a/strcat_s.c b/strcat_s.c new file mode 100644 index 0000000..08cff6e --- /dev/null +++ b/strcat_s.c @@ -0,0 +1,77 @@ +#define __STDC_WANT_LIB_EXT1__ 1 +#include +#include +#include +#include + + +#if !(defined(__APPLE__) || defined(__BSD__)) +// Like snprintf(3), the strlcpy() and strlcat() functions return the +// total length of the string they tried to create. +size_t strlcpy(char* dst, const char* src, size_t capacity) +{ + assert(dst != nullptr); + int len = snprintf(dst, capacity, "%s", src); + printf("dst = \"%s\", len = %d, capacity = %zu\n", dst, len, capacity); + + if (len >= 0 && (size_t)len >= capacity) { + puts("Warning: truncated str at strlcpy()!"); + } + + return len; +} + +// If the return value is >= dstsize, the output string has been +// truncated. It is the caller's responsibility to handle this +size_t strlcat(char* dst, const char* src, size_t capacity) +{ + assert(dst != nullptr); + size_t actual_used = strlen(dst); + assert(actual_used < capacity); + + if (capacity > (actual_used + 1)) { + return strlcpy(dst + actual_used, src, capacity - actual_used); + } + + return actual_used + strlen(src); // NOTE: total_len +} +#endif + +int main(void) +{ + char dst[15 + 2 * 6 + 1] = "Hello "; + char src[6 + 1] = "World!"; + const size_t capacity = sizeof(dst); // NOTE: 28 + + size_t total_len = strlcat(dst, src, capacity); + total_len = strlcat(dst, " .............#", capacity); + printf("dst = \"%s\", len = %zu, capacity = %zu\n", dst, total_len, capacity); + if (total_len >= capacity) { + puts("Warning: truncated string!"); + } + // XXX else + { + total_len = strlcat(dst, " Goodbye World!", capacity); + printf("dst = \"%s\", len = %zu, capacity = %zu\n", dst, total_len, capacity); + assert(total_len == 42); + } + // XXX puts(dst); + + total_len = strlcpy(dst, "Hello World! .............#1234567890", capacity); + printf("dst = \"%s\", len = %zu, capacity = %zu\n", dst, total_len, capacity); + assert(total_len == 37); + +#if defined(__STDC_LIB_EXT1__) || defined(_WIN32) + set_constraint_handler_s(ignore_handler_s); + + int r = strcpy_s(dst, capacity, src); + printf("dst = \"%s\", r = %d\n", dst, r); + r = strcpy_s(dst, capacity, "Take even more tests."); + printf("dst = \"%s\", r = %d\n", dst, r); + + int err = strcat_s(dst, capacity, " .............. "); + printf("dst = \"%s\", err = %d\n", dst, err); + err = strcat_s(dst, capacity, " and this is too much"); + printf("dst = \"%s\", err = %d\n", dst, err); +#endif +} diff --git a/to_string.cpp b/to_string.cpp index f110b6f..49ec40a 100644 --- a/to_string.cpp +++ b/to_string.cpp @@ -3,9 +3,11 @@ #if __cpp_lib_format # include // c++20 only using std::format; +using std::print; #else # include using ::fmt::format; +using ::fmt::print; #endif #include @@ -13,6 +15,17 @@ using ::fmt::format; #include #include +void foo() +{ + bool b = true; + //TODO std::string s(std::to_string(b)); // OK + uint8_t v = b ? 1 : 0; + std::string s = boost::lexical_cast(static_cast(v)); // OK + //XXX int i = std::stoi(""); // NOTE: std::invalid_argument: stoi: no conversion + int i = std::stoi(s); + print("The value {} was converted to {}\n", v, i); +} + int main() { double maxDouble = std::numeric_limits::max(); @@ -27,8 +40,10 @@ int main() double back_cast = boost::lexical_cast(str); std::cout << "boost::lexical_cast(" << back_cast << ") == " << back_stod << std::endl; - std::string s = format("The format::answer is {}.", maxDouble); + std::string s = format("The format::answer is {}", maxDouble); std::cout << s << std::endl; + foo(); + return 0; }