[Lldb-commits] [lldb] b783f70 - [lldb/DataFormatter] Check for overflow when finding NSDate epoch
Vedant Kumar via lldb-commits
lldb-commits at lists.llvm.org
Mon May 18 13:27:56 PDT 2020
Author: Vedant Kumar
Date: 2020-05-18T13:12:00-07:00
New Revision: b783f70a42575a5d9147bea1ac97e872370fe55b
URL: https://github.com/llvm/llvm-project/commit/b783f70a42575a5d9147bea1ac97e872370fe55b
DIFF: https://github.com/llvm/llvm-project/commit/b783f70a42575a5d9147bea1ac97e872370fe55b.diff
LOG: [lldb/DataFormatter] Check for overflow when finding NSDate epoch
Summary:
Fixes UBSan-reported issues where the date value inside of an
uninitialized NSDate overflows the 64-bit epoch.
rdar://61774575
Reviewers: JDevlieghere, mib, teemperor
Subscribers: mgorny, lldb-commits
Tags: #lldb
Differential Revision: https://reviews.llvm.org/D80150
Added:
lldb/include/lldb/DataFormatters/Mock.h
lldb/unittests/DataFormatter/MockTests.cpp
Modified:
lldb/source/Plugins/Language/ObjC/Cocoa.cpp
lldb/unittests/DataFormatter/CMakeLists.txt
Removed:
################################################################################
diff --git a/lldb/include/lldb/DataFormatters/Mock.h b/lldb/include/lldb/DataFormatters/Mock.h
new file mode 100644
index 000000000000..b3fc10cd2e51
--- /dev/null
+++ b/lldb/include/lldb/DataFormatters/Mock.h
@@ -0,0 +1,26 @@
+//===-- Mock.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_DATAFORMATTERS_MOCK_H
+#define LLDB_DATAFORMATTERS_MOCK_H
+
+namespace lldb_private {
+
+class Stream;
+
+namespace formatters {
+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_DATAFORMATTERS_MOCK_H
diff --git a/lldb/source/Plugins/Language/ObjC/Cocoa.cpp b/lldb/source/Plugins/Language/ObjC/Cocoa.cpp
index 8a44811dd36b..1ad443b8b74e 100644
--- a/lldb/source/Plugins/Language/ObjC/Cocoa.cpp
+++ b/lldb/source/Plugins/Language/ObjC/Cocoa.cpp
@@ -13,6 +13,7 @@
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/DataFormatters/Mock.h"
#include "lldb/DataFormatters/StringPrinter.h"
#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Host/Time.h"
@@ -27,6 +28,7 @@
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/bit.h"
+#include "llvm/Support/CheckedArithmetic.h"
#include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
@@ -785,6 +787,34 @@ static double decodeTaggedTimeInterval(uint64_t encodedTimeInterval) {
return llvm::bit_cast<double>(decodedBits);
}
+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;
+ }
+
+ if (date_value > std::numeric_limits<time_t>::max() ||
+ date_value < std::numeric_limits<time_t>::min())
+ return false;
+
+ time_t epoch = GetOSXEpoch();
+ if (auto osx_epoch = llvm::checkedAdd(epoch, (time_t)date_value))
+ 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 +858,16 @@ bool lldb_private::formatters::NSDateSummaryProvider(
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 +890,7 @@ bool lldb_private::formatters::NSDateSummaryProvider(
} 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(
diff --git a/lldb/unittests/DataFormatter/CMakeLists.txt b/lldb/unittests/DataFormatter/CMakeLists.txt
index 45011c56b0b0..716c8e735287 100644
--- a/lldb/unittests/DataFormatter/CMakeLists.txt
+++ b/lldb/unittests/DataFormatter/CMakeLists.txt
@@ -1,5 +1,6 @@
add_lldb_unittest(LLDBFormatterTests
FormatManagerTests.cpp
+ MockTests.cpp
StringPrinterTests.cpp
LINK_LIBS
diff --git a/lldb/unittests/DataFormatter/MockTests.cpp b/lldb/unittests/DataFormatter/MockTests.cpp
new file mode 100644
index 000000000000..0042888243f7
--- /dev/null
+++ b/lldb/unittests/DataFormatter/MockTests.cpp
@@ -0,0 +1,40 @@
+//===-- MockTests.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 "lldb/DataFormatters/Mock.h"
+#include "lldb/Utility/StreamString.h"
+#include "llvm/ADT/Optional.h"
+#include "gtest/gtest.h"
+#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(DataFormatterMockTest, NSDate) {
+ EXPECT_EQ(*formatDateValue(-63114076800), "0001-12-30 00:00:00 +0000");
+
+ // Can't convert the date_value to a time_t.
+ EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::max() + 1),
+ llvm::None);
+ EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::min() - 1),
+ llvm::None);
+
+ // Can't add the macOS epoch to the converted date_value (the add overflows).
+ EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::max()), llvm::None);
+ EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::min()), llvm::None);
+
+ EXPECT_EQ(*formatDateValue(0), "2001-01-01 00:00:00 UTC");
+}
More information about the lldb-commits
mailing list