From 341f914e1de4ee1a3c01ae3dde98eb4b8cbcdb61 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Wed, 8 Jan 2025 13:08:42 -0500 Subject: [PATCH] *: Add some hardening compile options. Also fix some warnings and potential issues that building the entire EuropaTools codebase under those flags showed. Later on once we drop the libpixel and stbiw dependencies, we can probably turn on -Werror for release builds. --- CMakeLists.txt | 8 +++- cmake/CompilerFlags-GNU.cmake | 67 ++++++++++++++++++++++++++++++++ cmake/CompilerFlags.cmake | 17 ++++++++ cmake/ProjectFuncs.cmake | 37 ++++++++++++++++++ include/europa/io/YatfReader.hpp | 2 +- src/libeuropa/io/PakReader.cpp | 2 +- src/libeuropa/io/PakWriter.cpp | 2 +- src/libeuropa/io/StreamUtils.cpp | 3 +- src/libeuropa/io/YatfReader.cpp | 6 +-- src/tools/CMakeLists.txt | 2 + src/tools/eupak/CMakeLists.txt | 4 +- src/tools/eupak/Utils.cpp | 2 +- 12 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 cmake/CompilerFlags-GNU.cmake create mode 100644 cmake/CompilerFlags.cmake create mode 100644 cmake/ProjectFuncs.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e9523..8da2ca2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,14 +13,18 @@ if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}") message(FATAL_ERROR "In-source builds are strictly prohibited.") endif() -include(cmake/Policies.cmake) - project(EuropaTools VERSION 1.0.0 LANGUAGES C CXX DESCRIPTION "Tools for working with LEC Europa based games (Star Wars: Starfighter & Star Wars: Jedi Starfighter)" ) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +include(Policies) +include(ProjectFuncs) +include(CompilerFlags) + include(FetchContent) # declare dependencies diff --git a/cmake/CompilerFlags-GNU.cmake b/cmake/CompilerFlags-GNU.cmake new file mode 100644 index 0000000..3c3b0a5 --- /dev/null +++ b/cmake/CompilerFlags-GNU.cmake @@ -0,0 +1,67 @@ +# TODO: This currently assumes libstdc++, later on we should *probably* set this with some detection +# also TODO: Use a list so that this isn't one giant line (list JOIN should help.) +set(EUROPA_CORE_COMPILE_ARGS "-Wall -Wextra -Wformat=2 -Wimplicit-fallthrough -fvisibility=hidden") +set(EUROPA_CORE_LINKER_ARGS "-fuse-ld=${EUROPA_LINKER}") + +if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" + # If on Release use link-time optimizations. + # On clang we use ThinLTO for even better build performance. + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(EUROPA_CORE_COMPILE_ARGS "${EUROPA_CORE_COMPILE_ARGS} -flto=thin -ffunction-sections -fdata-sections") + set(EUROPA_CORE_LINKER_ARGS "${EUROPA_CORE_LINKER_ARGS} -flto=thin -ffunction-sections -fdata-sections") + else() + set(EUROPA_CORE_COMPILE_ARGS "${EUROPA_CORE_COMPILE_ARGS} -flto -ffunction-sections -fdata-sections") + set(EUROPA_CORE_LINKER_ARGS "${EUROPA_CORE_LINKER_ARGS} -flto -ffunction-sections -fdata-sections") + endif() +endif() + +set(_EUROPA_CORE_WANTED_SANITIZERS "") + +if("asan" IN_LIST EUROPA_SANITIZERS) + # Error if someone's trying to mix asan and tsan together since they aren't compatible. + if("tsan" IN_LIST EUROPA_SANITIZERS) + message(FATAL_ERROR "ASAN and TSAN cannot be used together.") + endif() + + message(STATUS "Enabling ASAN because it was in EUROPA_SANITIZERS") + list(APPEND _EUROPA_CORE_WANTED_SANITIZERS "address") +endif() + +if("tsan" IN_LIST EUROPA_SANITIZERS) + if("asan" IN_LIST EUROPA_SANITIZERS) + message(FATAL_ERROR "ASAN and TSAN cannot be used together.") + endif() + + message(STATUS "Enabling TSAN because it was in EUROPA_SANITIZERS") + list(APPEND _EUROPA_CORE_WANTED_SANITIZERS "thread") +endif() + +if("ubsan" IN_LIST EUROPA_SANITIZERS) + message(STATUS "Enabling UBSAN because it was in EUROPA_SANITIZERS") + list(APPEND _EUROPA_CORE_WANTED_SANITIZERS "undefined") +endif() + +list(LENGTH _EUROPA_CORE_WANTED_SANITIZERS _EUROPA_CORE_WANTED_SANITIZERS_LENGTH) +if(NOT _EUROPA_CORE_WANTED_SANITIZERS_LENGTH EQUAL 0) + list(JOIN _EUROPA_CORE_WANTED_SANITIZERS "," _EUROPA_CORE_WANTED_SANITIZERS_ARG) + message(STATUS "Enabled sanitizers: ${_EUROPA_CORE_WANTED_SANITIZERS_ARG}") + set(EUROPA_CORE_COMPILE_ARGS "${EUROPA_CORE_COMPILE_ARGS} -fsanitize=${_EUROPA_CORE_WANTED_SANITIZERS_ARG}") + set(EUROPA_CORE_LINKER_ARGS "${EUROPA_CORE_LINKER_ARGS} -fsanitize=${_EUROPA_CORE_WANTED_SANITIZERS_ARG}") +endif() + +# Set core CMake toolchain variables so that they get applied to all projects. +# A bit nasty, but /shrug, this way our third party libraries can be mostly sanitized/etc as well. +# We do NOT do this for CMake compiler features however. + +set(CMAKE_C_FLAGS "${EUROPA_CORE_COMPILE_ARGS}") +set(CMAKE_CXX_FLAGS "${EUROPA_CORE_COMPILE_ARGS}") + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -O0 -g3") +set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -O3 -g3") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -DNDEBUG -O3 -s") + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g3") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -O3 -g3") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -s") + +set(CMAKE_EXE_LINKER_FLAGS "${EUROPA_CORE_LINKER_ARGS} -Wl,-z,noexecstack,-z,relro,-z,now,--gc-sections") diff --git a/cmake/CompilerFlags.cmake b/cmake/CompilerFlags.cmake new file mode 100644 index 0000000..887923a --- /dev/null +++ b/cmake/CompilerFlags.cmake @@ -0,0 +1,17 @@ +# +# EuropaTools +# +# (C) 2021-2025 modeco80 +# +# SPDX-License-Identifier: MIT +# + +# Core compile arguments used for the whole project +# +# This is the driver, we include compiler/platform specific files here + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + include(CompilerFlags-GNU) +else() + message(FATAL_ERROR "Unsupported (for now?) compiler ${CMAKE_CXX_COMPILER_ID}") +endif() diff --git a/cmake/ProjectFuncs.cmake b/cmake/ProjectFuncs.cmake new file mode 100644 index 0000000..9f0d113 --- /dev/null +++ b/cmake/ProjectFuncs.cmake @@ -0,0 +1,37 @@ +# +# EuropaTools +# +# (C) 2021-2025 modeco80 +# +# SPDX-License-Identifier: MIT +# + +function(europa_target target) + # Set binary products to output in the build directory for easier access + set_target_properties( + ${target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}" + ) +endfunction() + +function(europa_set_alternate_linker) + find_program(LINKER_EXECUTABLE ld.${EUROPA_LINKER} ${EUROPA_LINKER}) + if(LINKER_EXECUTABLE) + message(STATUS "Using ${EUROPA_LINKER} as argument to -fuse-ld=") + else() + message(FATAL_ERROR "Linker ${EUROPA_LINKER} does not exist on your system. Please specify one which does or omit this option from your configure command.") + endif() +endfunction() + +# FIXME: Better MSVC detection +if(NOT WIN32) + # set the default linker based on compiler id, if one is not provided + # This is provided so that it can be overridden + if(NOT EUROPA_LINKER AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(EUROPA_LINKER "lld") + elseif(NOT EUROPA_LINKER) + set(EUROPA_LINKER "bfd") + endif() + + europa_set_alternate_linker() +endif() diff --git a/include/europa/io/YatfReader.hpp b/include/europa/io/YatfReader.hpp index 4f0ab4c..e5592e9 100644 --- a/include/europa/io/YatfReader.hpp +++ b/include/europa/io/YatfReader.hpp @@ -20,7 +20,7 @@ namespace europa::io { struct YatfReader { explicit YatfReader(std::istream& is); - void Init(std::istream& is); + void InitFromStream(std::istream& is); void ReadImage(); diff --git a/src/libeuropa/io/PakReader.cpp b/src/libeuropa/io/PakReader.cpp index 3111578..dbd1dd8 100644 --- a/src/libeuropa/io/PakReader.cpp +++ b/src/libeuropa/io/PakReader.cpp @@ -37,7 +37,7 @@ namespace europa::io { // Read the archive TOC stream.seekg(header_type.tocOffset, std::istream::beg); - for(auto i = 0; i < header_type.fileCount; ++i) { + for(std::uint32_t i = 0; i < header_type.fileCount; ++i) { // The first part of the TOC entry is always a VLE string, // which we don't store inside the type (because we can't) // diff --git a/src/libeuropa/io/PakWriter.cpp b/src/libeuropa/io/PakWriter.cpp index 8eb3d40..d234733 100644 --- a/src/libeuropa/io/PakWriter.cpp +++ b/src/libeuropa/io/PakWriter.cpp @@ -33,7 +33,7 @@ namespace europa::io { template constexpr T AlignBy(T value, std::size_t alignment) { - return (-value) & alignment - 1; + return (-value) & (alignment - 1); } void PakWriter::Write(std::ostream& os, std::vector&& vec, PakProgressReportSink& sink, SectorAlignment sectorAlignment) { diff --git a/src/libeuropa/io/StreamUtils.cpp b/src/libeuropa/io/StreamUtils.cpp index 6126a85..6f7138e 100644 --- a/src/libeuropa/io/StreamUtils.cpp +++ b/src/libeuropa/io/StreamUtils.cpp @@ -47,7 +47,6 @@ namespace europa::io::impl { std::string ReadPString(std::istream& is) { std::string s; - char c; if(!is) return ""; @@ -62,7 +61,7 @@ namespace europa::io::impl { s.resize(length - 1); // Read the string - for(auto i = 0; i < length-1; ++i) { + for(std::uint32_t i = 0; i < length-1; ++i) { s[i] = static_cast(is.get()); } static_cast(is.get()); diff --git a/src/libeuropa/io/YatfReader.cpp b/src/libeuropa/io/YatfReader.cpp index ac25302..3b02400 100644 --- a/src/libeuropa/io/YatfReader.cpp +++ b/src/libeuropa/io/YatfReader.cpp @@ -19,12 +19,12 @@ namespace europa::io { YatfReader::YatfReader(std::istream& is) : stream(is) { - Init(stream); + InitFromStream(stream); } - void YatfReader::Init(std::istream& is) { + void YatfReader::InitFromStream(std::istream& is) { // Read the image header. - header = impl::ReadStreamType(stream); + header = impl::ReadStreamType(is); if(!header.IsValid()) invalid = true; diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index ba42062..0236aba 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -13,11 +13,13 @@ add_executable(texdump texdump.cpp) target_link_libraries(texdump PUBLIC europa ) +europa_target(texdump) add_executable(jsfscramble jsfscramble.cpp) target_link_libraries(jsfscramble PUBLIC europa ) +europa_target(jsfscramble) #add_executable(paktest paktest.cpp) #target_link_libraries(paktest PUBLIC diff --git a/src/tools/eupak/CMakeLists.txt b/src/tools/eupak/CMakeLists.txt index ca636b9..b4b603e 100644 --- a/src/tools/eupak/CMakeLists.txt +++ b/src/tools/eupak/CMakeLists.txt @@ -28,4 +28,6 @@ configure_file(EupakConfig.hpp.in ) target_include_directories(eupak PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(eupak PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file +target_include_directories(eupak PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +europa_target(eupak) \ No newline at end of file diff --git a/src/tools/eupak/Utils.cpp b/src/tools/eupak/Utils.cpp index e1e519f..d786ca6 100644 --- a/src/tools/eupak/Utils.cpp +++ b/src/tools/eupak/Utils.cpp @@ -53,7 +53,7 @@ namespace eupak { auto count = std::strftime(&buf[0], sizeof(buf), format.data(), &tmObject); // an error occured, probably. - if(count == -1) + if(count == static_cast(-1)) return ""; return { buf, count };