Explorar el Código

CTest: add safe conversion from cmDuration to integer types

A problem area by recent refactoring of time to std::chrono has been the
unsafe conversion from duration<double> to std::chrono::seconds, which
is of an unspecified integer type.

This commit adds a template function that for a given type provides a
safe conversion, effectively clamping a duration<double> into what fits
safely in that type. A specialisation for int and unsigned int are
provided.

It changes the protential problem areas to use this safe function.
Wouter Klouwen hace 8 años
padre
commit
ff62b00522

+ 3 - 0
Source/CMakeLists.txt

@@ -602,6 +602,9 @@ set(SRCS
   cm_codecvt.hxx
   cm_codecvt.cxx
   cm_thread.hxx
+
+  cmDuration.h
+  cmDuration.cxx
   )
 
 SET_PROPERTY(SOURCE cmProcessOutput.cxx APPEND PROPERTY COMPILE_DEFINITIONS

+ 11 - 12
Source/CTest/cmCTestMemCheckHandler.cxx

@@ -3,6 +3,7 @@
 #include "cmCTestMemCheckHandler.h"
 
 #include "cmCTest.h"
+#include "cmDuration.h"
 #include "cmSystemTools.h"
 #include "cmXMLParser.h"
 #include "cmXMLWriter.h"
@@ -920,12 +921,11 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput(
       break; // stop the copy of output if we are full
     }
   }
-  cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: "
-                       << std::chrono::duration_cast<std::chrono::seconds>(
-                            std::chrono::steady_clock::now() - sttime)
-                            .count()
-                       << "s)" << std::endl,
-                     this->Quiet);
+  cmCTestOptionalLog(
+    this->CTest, DEBUG, "End test (elapsed: "
+      << cmDurationTo<unsigned int>(std::chrono::steady_clock::now() - sttime)
+      << "s)" << std::endl,
+    this->Quiet);
   log = ostr.str();
   this->DefectCount += defects;
   return defects == 0;
@@ -966,12 +966,11 @@ bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
     results[err]++;
     defects++;
   }
-  cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: "
-                       << std::chrono::duration_cast<std::chrono::seconds>(
-                            std::chrono::steady_clock::now() - sttime)
-                            .count()
-                       << "s)" << std::endl,
-                     this->Quiet);
+  cmCTestOptionalLog(
+    this->CTest, DEBUG, "End test (elapsed: "
+      << cmDurationTo<unsigned int>(std::chrono::steady_clock::now() - sttime)
+      << "s)" << std::endl,
+    this->Quiet);
   if (defects) {
     // only put the output of Bounds Checker if there were
     // errors or leaks detected

+ 5 - 11
Source/CTest/cmCTestRunTest.cxx

@@ -621,17 +621,11 @@ bool cmCTestRunTest::ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
   if (testTimeOut == cmDuration::zero() && explicitTimeout) {
     timeout = cmDuration::zero();
   }
-  cmCTestOptionalLog(
-    this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index
-      << ": "
-      << "Test timeout computed to be: "
-      << (timeout == cmCTest::MaxDuration()
-            ? std::string("infinite")
-            : std::to_string(
-                std::chrono::duration_cast<std::chrono::seconds>(timeout)
-                  .count()))
-      << "\n",
-    this->TestHandler->GetQuiet());
+  cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index
+                       << ": "
+                       << "Test timeout computed to be: "
+                       << cmDurationTo<unsigned int>(timeout) << "\n",
+                     this->TestHandler->GetQuiet());
 
   this->TestProcess->SetTimeout(timeout);
 

+ 6 - 7
Source/CTest/cmCTestScriptHandler.cxx

@@ -159,9 +159,9 @@ void cmCTestScriptHandler::UpdateElapsedTime()
 {
   if (this->Makefile) {
     // set the current elapsed time
-    auto itime = std::chrono::duration_cast<std::chrono::seconds>(
-      std::chrono::steady_clock::now() - this->ScriptStartTime);
-    auto timeString = std::to_string(itime.count());
+    auto itime = cmDurationTo<unsigned int>(std::chrono::steady_clock::now() -
+                                            this->ScriptStartTime);
+    auto timeString = std::to_string(itime);
     this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString.c_str());
   }
 }
@@ -569,10 +569,9 @@ int cmCTestScriptHandler::RunCurrentScript()
       auto interval = std::chrono::steady_clock::now() - startOfInterval;
       auto minimumInterval = cmDuration(this->MinimumInterval);
       if (interval < minimumInterval) {
-        auto sleepTime = std::chrono::duration_cast<std::chrono::seconds>(
-                           minimumInterval - interval)
-                           .count();
-        this->SleepInSeconds(static_cast<unsigned int>(sleepTime));
+        auto sleepTime =
+          cmDurationTo<unsigned int>(minimumInterval - interval);
+        this->SleepInSeconds(sleepTime);
       }
       if (this->EmptyBinDirOnce) {
         this->EmptyBinDir = false;

+ 6 - 10
Source/cmCTest.cxx

@@ -1092,14 +1092,11 @@ int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
   if (timeout <= cmDuration::zero()) {
     timeout = std::chrono::seconds(1);
   }
-  cmCTestLog(
-    this, HANDLER_VERBOSE_OUTPUT, "Test timeout computed to be: "
-      << (timeout == cmCTest::MaxDuration()
-            ? std::string("infinite")
-            : std::to_string(
-                std::chrono::duration_cast<std::chrono::seconds>(timeout)
-                  .count()))
-      << "\n");
+  cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "Test timeout computed to be: "
+               << (timeout == cmCTest::MaxDuration()
+                     ? std::string("infinite")
+                     : std::to_string(cmDurationTo<unsigned int>(timeout)))
+               << "\n");
   if (cmSystemTools::SameFile(argv[0], cmSystemTools::GetCTestCommand()) &&
       !this->ForceNewCTestProcess) {
     cmCTest inst;
@@ -1121,8 +1118,7 @@ int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
             timeout > cmDuration::zero()) {
           args.push_back("--test-timeout");
           std::ostringstream msg;
-          msg << std::chrono::duration_cast<std::chrono::seconds>(timeout)
-                   .count();
+          msg << cmDurationTo<unsigned int>(timeout);
           args.push_back(msg.str());
         }
         args.push_back(i);

+ 27 - 0
Source/cmDuration.cxx

@@ -0,0 +1,27 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#define CMDURATION_CPP
+#include "cmDuration.h"
+
+template <typename T>
+T cmDurationTo(const cmDuration& duration)
+{
+  /* This works because the comparison operators for duration rely on
+   * std::common_type.
+   * So for example duration<int>::max() gets promoted to a duration<double>,
+   * which can then be safely compared.
+   */
+  if (duration >= std::chrono::duration<T>::max()) {
+    return std::chrono::duration<T>::max().count();
+  }
+  if (duration <= std::chrono::duration<T>::min()) {
+    return std::chrono::duration<T>::min().count();
+  }
+  // Ensure number of seconds by defining ratio<1>
+  return std::chrono::duration_cast<std::chrono::duration<T, std::ratio<1>>>(
+           duration)
+    .count();
+}
+
+template int cmDurationTo<int>(const cmDuration&);
+template unsigned int cmDurationTo<unsigned int>(const cmDuration&);

+ 16 - 0
Source/cmDuration.h

@@ -6,3 +6,19 @@
 #include <ratio>
 
 typedef std::chrono::duration<double, std::ratio<1>> cmDuration;
+
+/*
+ * This function will return number of seconds in the requested type T.
+ *
+ * A duration_cast from duration<double> to duration<T> will not yield what
+ * one might expect if the double representation does not fit into type T.
+ * This function aims to safely convert, by clamping the double value between
+ * the permissible valid values for T.
+ */
+template <typename T>
+T cmDurationTo(const cmDuration& duration);
+
+#ifndef CMDURATION_CPP
+extern template int cmDurationTo<int>(const cmDuration&);
+extern template unsigned int cmDurationTo<unsigned int>(const cmDuration&);
+#endif