vsk updated this revision to Diff 265830.
vsk added a comment.

Fix an ASSERT_LE that was actually meant to be an ASSERT_GE.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D80150/new/

https://reviews.llvm.org/D80150

Files:
  lldb/include/lldb/DataFormatters/FormattersHelpers.h
  lldb/source/Plugins/Language/ObjC/CF.cpp
  lldb/source/Plugins/Language/ObjC/Cocoa.cpp
  lldb/source/Plugins/Language/ObjC/Utilities.h
  lldb/unittests/Language/AppleObjC/CMakeLists.txt
  lldb/unittests/Language/AppleObjC/UtilitiesTests.cpp
  lldb/unittests/Language/CMakeLists.txt

Index: lldb/unittests/Language/CMakeLists.txt
===================================================================
--- lldb/unittests/Language/CMakeLists.txt
+++ lldb/unittests/Language/CMakeLists.txt
@@ -1,2 +1,6 @@
 add_subdirectory(CPlusPlus)
 add_subdirectory(Highlighting)
+
+if (APPLE)
+  add_subdirectory(AppleObjC)
+endif()
Index: lldb/unittests/Language/AppleObjC/UtilitiesTests.cpp
===================================================================
--- /dev/null
+++ lldb/unittests/Language/AppleObjC/UtilitiesTests.cpp
@@ -0,0 +1,60 @@
+//===-- UtilitiesTests.cpp ------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Plugins/Language/ObjC/Utilities.h"
+#include "lldb/Utility/StreamString.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+#include <cmath>
+#include <string>
+
+using namespace lldb_private;
+
+// Try to format the date_value field of a NSDate.
+static llvm::Optional<std::string> formatDateValue(double date_value) {
+  StreamString s;
+  bool succcess = formatters::NSDate::FormatDateValue(date_value, s);
+  if (succcess)
+    return std::string(s.GetData());
+  return llvm::None;
+}
+
+TEST(DataFormatterTest, NSDate) {
+  EXPECT_EQ(formatDateValue(-63114076800),
+            std::string("0001-12-30 00:00:00 +0000"));
+
+  // Assert that std::llrint clamps values greater/lesser than the max/min
+  // time_t values. If it doesn't, it won't make sense to proceed, as we're
+  // relying on this behavior to avoid float-cast-overflow UB.
+  const double greater_than_max_time = 1e200;
+  const double lesser_than_min_time = -1e200;
+  ASSERT_LE(static_cast<time_t>(std::llrint(greater_than_max_time)),
+            std::numeric_limits<time_t>::max());
+  ASSERT_GE(static_cast<time_t>(std::llrint(lesser_than_min_time)),
+            std::numeric_limits<time_t>::min());
+
+  // If the date_value `double` cannot be represented in a `time_t`, give up.
+  EXPECT_EQ(formatDateValue(greater_than_max_time), llvm::None);
+  EXPECT_EQ(formatDateValue(lesser_than_min_time), llvm::None);
+
+  // If the date_value `double` plus the epoch cannot be represented in a
+  // `time_t`, give up. Try to craft a double that will trigger this scenario:
+  // if we can't do it, don't proceed.
+  const time_t date_value_to_trigger_overflow =
+      std::numeric_limits<time_t>::max() - formatters::GetOSXEpoch() + 1;
+  const double date_value_to_trigger_overflow_as_double =
+      static_cast<double>(date_value_to_trigger_overflow);
+  ASSERT_LE(date_value_to_trigger_overflow,
+            static_cast<time_t>(date_value_to_trigger_overflow_as_double));
+  EXPECT_EQ(formatDateValue(date_value_to_trigger_overflow_as_double),
+            llvm::None);
+
+  EXPECT_EQ(formatDateValue(0), std::string("2001-01-01 00:00:00 UTC"));
+  EXPECT_EQ(formatDateValue(1024), std::string("2001-01-01 00:17:04 UTC"));
+}
Index: lldb/unittests/Language/AppleObjC/CMakeLists.txt
===================================================================
--- /dev/null
+++ lldb/unittests/Language/AppleObjC/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_lldb_unittest(LanguageAppleObjCTests
+  UtilitiesTests.cpp
+
+  LINK_LIBS
+    lldbPluginObjCLanguage
+  )
Index: lldb/source/Plugins/Language/ObjC/Utilities.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Language/ObjC/Utilities.h
@@ -0,0 +1,32 @@
+//===-- Utilities.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_LANGUAGE_OBJC_UTILITIES_H
+#define LLDB_PLUGINS_LANGUAGE_OBJC_UTILITIES_H
+
+#include <time.h>
+
+namespace lldb_private {
+
+class Stream;
+
+namespace formatters {
+
+/// Return an epoch time_t adjusted to the reference date set by the Cocoa
+/// framework.
+time_t GetOSXEpoch();
+
+namespace NSDate {
+/// Format the date_value field of a NSDate.
+bool FormatDateValue(double date_value, Stream &stream);
+} // namespace NSDate
+
+} // namespace formatters
+} // namespace lldb_private
+
+#endif // LLDB_PLUGINS_LANGUAGE_OBJC_UTILITIES_H
Index: lldb/source/Plugins/Language/ObjC/Cocoa.cpp
===================================================================
--- lldb/source/Plugins/Language/ObjC/Cocoa.cpp
+++ lldb/source/Plugins/Language/ObjC/Cocoa.cpp
@@ -8,6 +8,7 @@
 
 #include "Cocoa.h"
 
+#include "Plugins/Language/ObjC/Utilities.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
 #include "lldb/Core/Mangled.h"
 #include "lldb/Core/ValueObject.h"
@@ -27,11 +28,14 @@
 
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/bit.h"
+#include "llvm/Support/CheckedArithmetic.h"
 
 #include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
 
 #include "NSString.h"
 
+#include <cmath>
+
 using namespace lldb;
 using namespace lldb_private;
 using namespace lldb_private::formatters;
@@ -785,6 +789,64 @@
   return llvm::bit_cast<double>(decodedBits);
 }
 
+// POSIX has an epoch on Jan-1-1970, but Cocoa prefers Jan-1-2001
+// this call gives the POSIX equivalent of the Cocoa epoch
+time_t lldb_private::formatters::GetOSXEpoch() {
+  static time_t epoch = 0;
+#ifdef __APPLE__
+  if (!epoch) {
+    tzset();
+    tm tm_epoch;
+    tm_epoch.tm_sec = 0;
+    tm_epoch.tm_hour = 0;
+    tm_epoch.tm_min = 0;
+    tm_epoch.tm_mon = 0;
+    tm_epoch.tm_mday = 1;
+    tm_epoch.tm_year = 2001 - 1900;
+    tm_epoch.tm_isdst = -1;
+    tm_epoch.tm_gmtoff = 0;
+    tm_epoch.tm_zone = nullptr;
+    epoch = timegm(&tm_epoch);
+  }
+#endif // __APPLE__
+  return epoch;
+}
+
+bool lldb_private::formatters::NSDate::FormatDateValue(double date_value,
+                                                       Stream &stream) {
+  if (date_value == -63114076800) {
+    stream.Printf("0001-12-30 00:00:00 +0000");
+    return true;
+  }
+
+  // Rely on implementation-defined behavior from std::llrint to clamp an
+  // out-of-range `double` value to a value representable by `time_t` without
+  // crashing LLDB with a floating-point exception.
+  //
+  // We're not distinguishing between the case where date_value "is" the max
+  // `time_t` value, and the case where std::llrint simply returns that value
+  // due to clamping, although technically we should be able to by inspecting
+  // the `math_errhandling` macro (which is set on a domain/overflow error).
+  const time_t date_value_as_time_t =
+      static_cast<time_t>(std::llrint(date_value));
+  time_t epoch = GetOSXEpoch();
+  if (auto osx_epoch = llvm::checkedAdd(epoch, date_value_as_time_t))
+    epoch = *osx_epoch;
+  else
+    return false;
+
+  tm *tm_date = gmtime(&epoch);
+  if (!tm_date)
+    return false;
+  std::string buffer(1024, 0);
+  if (strftime(&buffer[0], 1023, "%Z", tm_date) == 0)
+    return false;
+  stream.Printf("%04d-%02d-%02d %02d:%02d:%02d %s", tm_date->tm_year + 1900,
+                tm_date->tm_mon + 1, tm_date->tm_mday, tm_date->tm_hour,
+                tm_date->tm_min, tm_date->tm_sec, buffer.c_str());
+  return true;
+}
+
 bool lldb_private::formatters::NSDateSummaryProvider(
     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
   ProcessSP process_sp = valobj.GetProcessSP();
@@ -828,6 +890,16 @@
     if (descriptor->GetTaggedPointerInfo(&info_bits, &value_bits)) {
       date_value_bits = ((value_bits << 8) | (info_bits << 4));
       memcpy(&date_value, &date_value_bits, sizeof(date_value_bits));
+
+      // Accomodate the __NSTaggedDate format introduced in Foundation 1600.
+      if (class_name == g___NSTaggedDate) {
+        auto *apple_runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
+            ObjCLanguageRuntime::Get(*process_sp));
+        if (!apple_runtime)
+          return false;
+        if (apple_runtime->GetFoundationVersion() >= 1600)
+          date_value = decodeTaggedTimeInterval(value_bits << 4);
+      }
     } else {
       llvm::Triple triple(
           process_sp->GetTarget().GetArchitecture().GetTriple());
@@ -850,34 +922,7 @@
   } else
     return false;
 
-  if (date_value == -63114076800) {
-    stream.Printf("0001-12-30 00:00:00 +0000");
-    return true;
-  }
-
-  // Accomodate for the __NSTaggedDate format introduced in Foundation 1600.
-  if (class_name == g___NSTaggedDate) {
-    auto *runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
-        ObjCLanguageRuntime::Get(*process_sp));
-    if (runtime && runtime->GetFoundationVersion() >= 1600)
-      date_value = decodeTaggedTimeInterval(value_bits << 4);
-  }
-
-  // this snippet of code assumes that time_t == seconds since Jan-1-1970 this
-  // is generally true and POSIXly happy, but might break if a library vendor
-  // decides to get creative
-  time_t epoch = GetOSXEpoch();
-  epoch = epoch + (time_t)date_value;
-  tm *tm_date = gmtime(&epoch);
-  if (!tm_date)
-    return false;
-  std::string buffer(1024, 0);
-  if (strftime(&buffer[0], 1023, "%Z", tm_date) == 0)
-    return false;
-  stream.Printf("%04d-%02d-%02d %02d:%02d:%02d %s", tm_date->tm_year + 1900,
-                tm_date->tm_mon + 1, tm_date->tm_mday, tm_date->tm_hour,
-                tm_date->tm_min, tm_date->tm_sec, buffer.c_str());
-  return true;
+  return NSDate::FormatDateValue(date_value, stream);
 }
 
 bool lldb_private::formatters::ObjCClassSummaryProvider(
@@ -1098,29 +1143,6 @@
   return true;
 }
 
-// POSIX has an epoch on Jan-1-1970, but Cocoa prefers Jan-1-2001
-// this call gives the POSIX equivalent of the Cocoa epoch
-time_t lldb_private::formatters::GetOSXEpoch() {
-  static time_t epoch = 0;
-  if (!epoch) {
-#ifndef _WIN32
-    tzset();
-    tm tm_epoch;
-    tm_epoch.tm_sec = 0;
-    tm_epoch.tm_hour = 0;
-    tm_epoch.tm_min = 0;
-    tm_epoch.tm_mon = 0;
-    tm_epoch.tm_mday = 1;
-    tm_epoch.tm_year = 2001 - 1900;
-    tm_epoch.tm_isdst = -1;
-    tm_epoch.tm_gmtoff = 0;
-    tm_epoch.tm_zone = nullptr;
-    epoch = timegm(&tm_epoch);
-#endif
-  }
-  return epoch;
-}
-
 template bool lldb_private::formatters::NSDataSummaryProvider<true>(
     ValueObject &, Stream &, const TypeSummaryOptions &);
 
Index: lldb/source/Plugins/Language/ObjC/CF.cpp
===================================================================
--- lldb/source/Plugins/Language/ObjC/CF.cpp
+++ lldb/source/Plugins/Language/ObjC/CF.cpp
@@ -8,6 +8,7 @@
 
 #include "CF.h"
 
+#include "Plugins/Language/ObjC/Utilities.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
 #include "lldb/Core/ValueObject.h"
 #include "lldb/Core/ValueObjectConstResult.h"
Index: lldb/include/lldb/DataFormatters/FormattersHelpers.h
===================================================================
--- lldb/include/lldb/DataFormatters/FormattersHelpers.h
+++ lldb/include/lldb/DataFormatters/FormattersHelpers.h
@@ -58,8 +58,6 @@
 
 lldb::ValueObjectSP GetValueOfLibCXXCompressedPair(ValueObject &pair);
 
-time_t GetOSXEpoch();
-
 struct InferiorSizedWord {
 
   InferiorSizedWord(const InferiorSizedWord &word) : ptr_size(word.ptr_size) {
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to