Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorLeitwolf112026-09-28 01:10:14 +0300
committerLeitwolf112026-09-28 01:10:14 +0300
commit97328fefe9650c84b594f1cea212c48562e1745b (patch)
tree8905f56f9a2f337637d6ed9c86675b52829239b5 /src
parentfbd2ec1c12c3ea39df0b7af01b6ab11360302299 (diff)
downloadcatch3-97328fefe9650c84b594f1cea212c48562e1745b.tar.gz
Harden Catch3 exit guards and pin build workflows
Diffstat (limited to 'src')
-rw-r--r--src/catch2/catch_session.cpp102
-rw-r--r--src/catch2/catch_tostring.cpp30
2 files changed, 111 insertions, 21 deletions
diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp
index 78cb5a06..56612ba1 100644
--- a/src/catch2/catch_session.cpp
+++ b/src/catch2/catch_session.cpp
@@ -27,11 +27,23 @@
#include <catch2/internal/catch_istream.hpp>
#include <cassert>
+#include <cerrno>
#include <cstdio>
#include <cstdlib>
+#include <cstring>
#include <exception>
#include <iomanip>
+#if defined( _WIN32 )
+# include <fcntl.h>
+# include <io.h>
+# include <sys/stat.h>
+#else
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
namespace Catch {
namespace {
@@ -150,29 +162,93 @@ namespace Catch {
getMutableRegistryHub().getMutableTestCaseRegistry().enableFilenameTags();
}
- // Creates empty file at path. The path must be writable, we do not
- // try to create directories in path because that's hard in C++14.
+ std::FILE* openGuardFile( std::string const& guardFilePath,
+ int& error ) {
+#if defined( _MSC_VER )
+ std::FILE* file = nullptr;
+ error = fopen_s( &file, guardFilePath.c_str(), "w" );
+ return error ? nullptr : file;
+#elif defined( _WIN32 )
+ const int descriptor = _open( guardFilePath.c_str(),
+ _O_WRONLY | _O_CREAT | _O_TRUNC |
+ _O_BINARY | _O_NOINHERIT,
+ _S_IREAD | _S_IWRITE );
+ if ( descriptor == -1 ) {
+ error = errno;
+ return nullptr;
+ }
+ std::FILE* const file = _fdopen( descriptor, "w" );
+ if ( file == nullptr ) {
+ error = errno;
+ _close( descriptor );
+ }
+ return file;
+#else
+ int flags = O_WRONLY | O_CREAT | O_TRUNC;
+# if defined( O_NONBLOCK )
+ flags |= O_NONBLOCK;
+# endif
+# if defined( O_CLOEXEC )
+ flags |= O_CLOEXEC;
+# endif
+# if defined( O_NOFOLLOW )
+ // Do not let a stale guard-file symlink redirect this write.
+ flags |= O_NOFOLLOW;
+# endif
+ const int descriptor =
+ ::open( guardFilePath.c_str(), flags, S_IRUSR | S_IWUSR );
+ if ( descriptor == -1 ) {
+ error = errno;
+ return nullptr;
+ }
+
+ struct stat fileStatus = {};
+ if ( ::fstat( descriptor, &fileStatus ) != 0 ) {
+ error = errno;
+ ::close( descriptor );
+ return nullptr;
+ }
+ if ( !S_ISREG( fileStatus.st_mode ) ) {
+ error = EINVAL;
+ ::close( descriptor );
+ return nullptr;
+ }
+ // The creation mode is already owner-only; also tighten an
+ // existing regular guard file before it is reused.
+ if ( ::fchmod( descriptor, S_IRUSR | S_IWUSR ) != 0 ) {
+ error = errno;
+ ::close( descriptor );
+ return nullptr;
+ }
+
+ std::FILE* const file = ::fdopen( descriptor, "w" );
+ if ( file == nullptr ) {
+ error = errno;
+ ::close( descriptor );
+ }
+ return file;
+#endif
+ }
+
+ // Creates an owner-only guard file. The path must be writable; we do
+ // not create its parent directories.
void setUpGuardFile( std::string const& guardFilePath ) {
if ( !guardFilePath.empty() ) {
-#if defined( _MSC_VER )
- std::FILE* file = nullptr;
- if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) {
- char msgBuffer[100];
- const auto err = errno;
+ int err = 0;
+ std::FILE* const file = openGuardFile( guardFilePath, err );
+ if ( file == nullptr ) {
+ char msgBuffer[100] = {};
std::string errMsg;
+#if defined( _MSC_VER )
if ( !strerror_s( msgBuffer, err ) ) {
errMsg = msgBuffer;
} else {
errMsg = "Could not translate errno to a string";
}
-
#else
- std::FILE* file = std::fopen( guardFilePath.c_str(), "w" );
- if ( !file ) {
- const auto err = errno;
- const char* errMsg = std::strerror( err );
+ const char* const message = std::strerror( err );
+ errMsg = message ? message : "Unknown error";
#endif
-
CATCH_RUNTIME_ERROR( "Could not open the exit guard file '"
<< guardFilePath << "' because '"
<< errMsg << "' (" << err << ')' );
diff --git a/src/catch2/catch_tostring.cpp b/src/catch2/catch_tostring.cpp
index dad62410..1fbf3a54 100644
--- a/src/catch2/catch_tostring.cpp
+++ b/src/catch2/catch_tostring.cpp
@@ -64,26 +64,40 @@ namespace Detail {
}
std::string formatTimeT(std::time_t time) {
-#ifdef _MSC_VER
std::tm timeInfo = {};
+#ifdef _MSC_VER
const auto err = gmtime_s( &timeInfo, &time );
if ( err ) {
return "gmtime from provided timepoint has failed. This "
"happens e.g. with pre-1970 dates using Microsoft libc";
}
+#elif defined( __MINGW32__ )
+ if ( gmtime_s( &timeInfo, &time ) != 0 ) {
+ return "gmtime from provided timepoint has failed";
+ }
+#elif defined( CATCH_PLATFORM_PLAYSTATION )
+ gmtime_s( &time, &timeInfo );
+#elif defined( __IAR_SYSTEMS_ICC__ )
+ // This toolchain does not provide gmtime_r. Copy the result immediately
+ // so the formatter does not retain the C library's static buffer.
+ const std::tm* const converted = std::gmtime( &time );
+ if ( converted == nullptr ) {
+ return "gmtime from provided timepoint has failed";
+ }
+ timeInfo = *converted;
#else
- std::tm* timeInfo = std::gmtime( &time );
+ if ( gmtime_r( &time, &timeInfo ) == nullptr ) {
+ return "gmtime from provided timepoint has failed";
+ }
#endif
auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" );
- char timeStamp[timeStampSize];
+ char timeStamp[timeStampSize] = {};
const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
-#ifdef _MSC_VER
- std::strftime( timeStamp, timeStampSize, fmt, &timeInfo );
-#else
- std::strftime( timeStamp, timeStampSize, fmt, timeInfo );
-#endif
+ if ( std::strftime( timeStamp, timeStampSize, fmt, &timeInfo ) == 0 ) {
+ return "gmtime from provided timepoint has failed";
+ }
return std::string( timeStamp, timeStampSize - 1 );
}