[lldb/DataFormatters] Display null C++ pointers as nullptr

Display null pointer as `nullptr`, `nil` and `NULL` for C++,
Objective-C/Objective-C++ and C respectively. The original motivation
for this patch was to display a null std::string pointer as nullptr
instead of "", but the fix seemed generic enough to be done for all
summary providers.

Differential revision: https://reviews.llvm.org/D77153
This commit is contained in:
Jonas Devlieghere 2020-11-12 12:11:47 -08:00
parent 5ab1702129
commit 406ad18748
14 changed files with 61 additions and 23 deletions

View File

@ -211,6 +211,10 @@ public:
// nil/null object, this method returns true
virtual bool IsNilReference(ValueObject &valobj);
/// Returns the summary string for ValueObjects for which IsNilReference() is
/// true.
virtual llvm::StringRef GetNilReferenceSummaryString() { return {}; }
// for a ValueObject of some "reference type", if the language provides a
// technique to decide whether the reference has ever been assigned to some
// object, this method will return true if such detection is possible, and if

View File

@ -355,22 +355,33 @@ void ValueObjectPrinter::GetValueSummaryError(std::string &value,
if (err_cstr)
error.assign(err_cstr);
if (ShouldPrintValueObject()) {
if (IsNil())
summary.assign("nil");
else if (IsUninitialized())
summary.assign("<uninitialized>");
else if (m_options.m_omit_summary_depth == 0) {
TypeSummaryImpl *entry = GetSummaryFormatter();
if (entry)
m_valobj->GetSummaryAsCString(entry, summary,
m_options.m_varformat_language);
else {
const char *sum_cstr =
m_valobj->GetSummaryAsCString(m_options.m_varformat_language);
if (sum_cstr)
summary.assign(sum_cstr);
}
if (!ShouldPrintValueObject())
return;
if (IsNil()) {
lldb::LanguageType lang_type =
(m_options.m_varformat_language == lldb::eLanguageTypeUnknown)
? m_valobj->GetPreferredDisplayLanguage()
: m_options.m_varformat_language;
if (Language *lang_plugin = Language::FindPlugin(lang_type)) {
summary.assign(lang_plugin->GetNilReferenceSummaryString().str());
} else {
// We treat C as the fallback language rather than as a separate Language
// plugin.
summary.assign("NULL");
}
} else if (IsUninitialized()) {
summary.assign("<uninitialized>");
} else if (m_options.m_omit_summary_depth == 0) {
TypeSummaryImpl *entry = GetSummaryFormatter();
if (entry) {
m_valobj->GetSummaryAsCString(entry, summary,
m_options.m_varformat_language);
} else {
const char *sum_cstr =
m_valobj->GetSummaryAsCString(m_options.m_varformat_language);
if (sum_cstr)
summary.assign(sum_cstr);
}
}
}
@ -403,7 +414,9 @@ bool ValueObjectPrinter::PrintValueAndSummaryIfNeeded(bool &value_printed,
// this thing is nil (but show the value if the user passes a format
// explicitly)
TypeSummaryImpl *entry = GetSummaryFormatter();
if (!IsNil() && !IsUninitialized() && !m_value.empty() &&
const bool has_nil_or_uninitialized_summary =
(IsNil() || IsUninitialized()) && !m_summary.empty();
if (!has_nil_or_uninitialized_summary && !m_value.empty() &&
(entry == nullptr ||
(entry->DoesPrintValue(m_valobj) ||
m_options.m_format != eFormatDefault) ||

View File

@ -358,6 +358,7 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
} else {
if (expr_result) {
result_valobj_sp = expr_result->GetValueObject();
result_valobj_sp->SetPreferredDisplayLanguage(language);
LLDB_LOG(log,
"== [UserExpression::Evaluate] Execution completed "

View File

@ -1134,6 +1134,15 @@ CPlusPlusLanguage::GetHardcodedSynthetics() {
return g_formatters;
}
bool CPlusPlusLanguage::IsNilReference(ValueObject &valobj) {
if (!Language::LanguageIsCPlusPlus(valobj.GetObjectRuntimeLanguage()) ||
!valobj.IsPointerType())
return false;
bool canReadValue = true;
bool isZero = valobj.GetValueAsUnsigned(0, &canReadValue) == 0;
return canReadValue && isZero;
}
bool CPlusPlusLanguage::IsSourceFile(llvm::StringRef file_path) const {
const auto suffixes = {".cpp", ".cxx", ".c++", ".cc", ".c",
".h", ".hh", ".hpp", ".hxx", ".h++"};

View File

@ -88,6 +88,10 @@ public:
HardcodedFormatters::HardcodedSyntheticFinder
GetHardcodedSynthetics() override;
bool IsNilReference(ValueObject &valobj) override;
llvm::StringRef GetNilReferenceSummaryString() override { return "nullptr"; }
bool IsSourceFile(llvm::StringRef file_path) const override;
const Highlighter *GetHighlighter() const override { return &m_highlighter; }

View File

@ -119,6 +119,8 @@ public:
bool IsNilReference(ValueObject &valobj) override;
llvm::StringRef GetNilReferenceSummaryString() override { return "nil"; }
bool IsSourceFile(llvm::StringRef file_path) const override;
const Highlighter *GetHighlighter() const override { return &m_highlighter; }

View File

@ -27,6 +27,8 @@ public:
return lldb::eLanguageTypeObjC_plus_plus;
}
llvm::StringRef GetNilReferenceSummaryString() override { return "nil"; }
bool IsSourceFile(llvm::StringRef file_path) const override;
const Highlighter *GetHighlighter() const override { return &m_highlighter; }

View File

@ -39,4 +39,4 @@ class TestCase(TestBase):
# Both `std::vector` and the type of the member have forward
# declarations before their definitions.
self.expect("expr --raw -- v",
substrs=['(std::__1::vector<int>) $0 = {', 'f = 0x', '}'])
substrs=['(std::__1::vector<int>) $0 = {', 'f = nullptr', '}'])

View File

@ -80,6 +80,7 @@ class LibcxxStringDataFormatterTestCase(TestBase):
'(%s::u32string) u32_empty = ""'%ns,
'(%s::basic_string<unsigned char, %s::char_traits<unsigned char>, '
'%s::allocator<unsigned char> >) uchar = "aaaaa"'%(ns,ns,ns),
'(%s::string *) null_str = nullptr'%ns,
])
self.runCmd("n")
@ -117,6 +118,7 @@ class LibcxxStringDataFormatterTestCase(TestBase):
'(%s::u32string) u32_empty = ""'%ns,
'(%s::basic_string<unsigned char, %s::char_traits<unsigned char>, '
'%s::allocator<unsigned char> >) uchar = "aaaaa"'%(ns,ns,ns),
'(%s::string *) null_str = nullptr'%ns,
])
# The test assumes that std::string is in its cap-size-data layout.

View File

@ -74,6 +74,7 @@ int main()
std::u32string u32_string(U"🍄🍅🍆🍌");
std::u32string u32_empty(U"");
std::basic_string<unsigned char> uchar(5, 'a');
std::string *null_str = nullptr;
#if _LIBCPP_ABI_VERSION == 1
std::string garbage1, garbage2, garbage3, garbage4, garbage5;

View File

@ -62,7 +62,7 @@ class AnonymousTestCase(TestBase):
# These should display correctly.
self.expect("expression pz", VARIABLES_DISPLAYED_CORRECTLY,
substrs=["(type_z *) $", " = 0x0000"])
substrs=["(type_z *) $", " = NULL"])
self.expect("expression z.y", VARIABLES_DISPLAYED_CORRECTLY,
substrs=["(type_y) $", "dummy = 2"])

View File

@ -1,7 +1,5 @@
"""Test that the expression parser doesn't get confused by 'id' and 'Class'"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
@ -21,7 +19,6 @@ class TestObjCBuiltinTypes(TestBase):
self.main_source, '// Set breakpoint here.')
@add_test_categories(['pyapi'])
#<rdar://problem/10591460> [regression] Can't print ivar value: error: reference to 'id' is ambiguous
def test_with_python_api(self):
"""Test expression parser respect for ObjC built-in types."""
self.build()
@ -57,4 +54,7 @@ class TestObjCBuiltinTypes(TestBase):
self.expect("expr (foo)", patterns=["\(ns::id\) \$.* = 0"])
self.expect("expr id my_id = 0; my_id", patterns=["\(id\) \$.* = nil"])
self.expect("expr --language Objective-C++ -- id my_id = 0; my_id",
patterns=["\(id\) \$.* = nil"])
self.expect("expr --language C++ -- id my_id = 0; my_id",
patterns=["\(id\) \$.* = nullptr"])