A very common pattern in our synthetic child providers was to make the child ValueObject using ValueObjectConstResult::Create or some form of the static ValueObject::CreateValueObjectFrom*** methods, and store and hand that out as the child. Doing that creates a "root" ValueObject whose lifecycle is not linked to the lifecycle of the ValueObject it is a child of. And that means it is possible that either the child or the parent could have gotten destroyed when the other ValueObject gets asked a question about it. For the most part this doesn't happen because there are usually enough other shared pointer references binding the two to keep both sides alive. But we have gotten a small but steady stream of reports for years now of crashes where a ValueObject accesses its ClusterManager but that has already been deleted. I've never been able to find a reproducible case of this, but one plausible cause is that we are violating the contract that "all the children of a ValueObject have coterminous lifespans, enforced by the ClusterManager". So it is unsurprising that they might sometimes not stay alive as long as they should. This patch addresses that by providing a way to use these static create methods but passing in the ClusterManager to be used, and adds or modifies where extant the CreateChildValueObjectFrom*** methods to pass in the parent manager. This patch is not complete. It turns out that routing the ClusterManager from CreateValueObjectFromExpression all the way to where the expression result VO is made is quite intrusive, and would have made this already quite large (though largly mechanical) patch become unweildy. Since I am mostly concerned about synthetic child providers, and we discourage using expressions for them, I think that's an acceptable separation. I also added a test where the ValueObjectSynthetic hands out its children, checking whether the child being handed out is in fact owned by the parent handing it out. It is controlled by a setting (target.check-vo-ownership) which is currently off by default - since I can't provide a way to do this correctly for expressions yet. But I turn it on in the testsuite. That's the real testing strategy for this patch, since its goal is to ensure that all the children we hand out are managed by their parents. I didn't put an equivalent check in ValueObjectVariable because that system really doesn't allow a way to do this incorrectly. I also fixed some of the tests that were making children using CreateValueObjectFromExpression to make their children with CreateValueObjectFromData instead. I could have opted the tests out of the check, but using the expression parser to do this work is not best practice, so I didn't want to leave bad examples around. I left one case that does do it with expressions, however, so we still have a test for that not-recommended but people do it anyway path.
166 lines
5.1 KiB
Python
166 lines
5.1 KiB
Python
import lldb
|
|
|
|
|
|
class jasSynthProvider:
|
|
def __init__(self, valobj, dict):
|
|
self.valobj = valobj
|
|
|
|
def num_children(self):
|
|
return 2
|
|
|
|
def get_child_at_index(self, index):
|
|
child = None
|
|
if index == 0:
|
|
child = self.valobj.GetChildMemberWithName("A")
|
|
if index == 1:
|
|
import lldb
|
|
|
|
data = lldb.SBData.CreateDataFromInt(1)
|
|
type = self.valobj.target.FindFirstType("int")
|
|
child = self.valobj.CreateValueFromData("X", data, type)
|
|
return child
|
|
|
|
def get_child_index(self, name):
|
|
if name == "A":
|
|
return 0
|
|
if name == "X":
|
|
return 1
|
|
return None
|
|
|
|
|
|
def ccc_summary(sbvalue, internal_dict):
|
|
sbvalue = sbvalue.GetNonSyntheticValue()
|
|
# This tests that the SBValue.GetNonSyntheticValue() actually returns a
|
|
# non-synthetic value. If it does not, then sbvalue.GetChildMemberWithName("a")
|
|
# in the following statement will call the 'get_child_index' method of the
|
|
# synthetic child provider CCCSynthProvider below (which return the "b" field").
|
|
return "CCC object with leading value " + str(sbvalue.GetChildMemberWithName("a"))
|
|
|
|
|
|
def ccc_synthetic(sbvalue, internal_dict):
|
|
sbvalue = sbvalue.GetSyntheticValue()
|
|
# This tests that the SBValue.GetSyntheticValue() actually returns a
|
|
# synthetic value. If it does, then sbvalue.GetChildMemberWithName("a")
|
|
# in the following statement will call the 'get_child_index' method of the
|
|
# synthetic child provider CCCSynthProvider below (which return the "b" field").
|
|
return "CCC object with leading synthetic value " + str(
|
|
sbvalue.GetChildMemberWithName("a")
|
|
)
|
|
|
|
|
|
def bar_int_synthetic(sbvalue, internal_dict):
|
|
sbvalue = sbvalue.GetSyntheticValue()
|
|
# This tests that the SBValue.GetSyntheticValue() actually returns no
|
|
# value when the value has no synthetic representation.
|
|
return "bar_int synthetic: " + str(sbvalue)
|
|
|
|
|
|
class CCCSynthProvider(object):
|
|
def __init__(self, sbvalue, internal_dict):
|
|
self._sbvalue = sbvalue
|
|
|
|
def num_children(self):
|
|
return 3
|
|
|
|
def get_child_index(self, name):
|
|
if name == "a":
|
|
# Return b for test.
|
|
return 1
|
|
raise RuntimeError("I don't want to be called!")
|
|
|
|
def get_child_at_index(self, index):
|
|
if index == 0:
|
|
return self._sbvalue.GetChildMemberWithName("a")
|
|
if index == 1:
|
|
return self._sbvalue.GetChildMemberWithName("b")
|
|
if index == 2:
|
|
return self._sbvalue.GetChildMemberWithName("c")
|
|
|
|
|
|
def empty1_summary(sbvalue, internal_dict):
|
|
return "I am an empty Empty1"
|
|
|
|
|
|
class Empty1SynthProvider(object):
|
|
def __init__(self, sbvalue, internal_dict):
|
|
self._sbvalue = sbvalue
|
|
|
|
def num_children(self):
|
|
return 0
|
|
|
|
def get_child_at_index(self, index):
|
|
return None
|
|
|
|
|
|
def empty2_summary(sbvalue, internal_dict):
|
|
return "I am an empty Empty2"
|
|
|
|
|
|
class Empty2SynthProvider(object):
|
|
def __init__(self, sbvalue, internal_dict):
|
|
self._sbvalue = sbvalue
|
|
|
|
def num_children(self):
|
|
return 0
|
|
|
|
def get_child_at_index(self, index):
|
|
return None
|
|
|
|
|
|
def __lldb_init_module(debugger, dict):
|
|
debugger.CreateCategory("JASSynth").AddTypeSynthetic(
|
|
lldb.SBTypeNameSpecifier("JustAStruct"),
|
|
lldb.SBTypeSynthetic.CreateWithClassName("synth.jasSynthProvider"),
|
|
)
|
|
cat = debugger.CreateCategory("CCCSynth")
|
|
cat.AddTypeSynthetic(
|
|
lldb.SBTypeNameSpecifier("CCC"),
|
|
lldb.SBTypeSynthetic.CreateWithClassName(
|
|
"synth.CCCSynthProvider", lldb.eTypeOptionCascade
|
|
),
|
|
)
|
|
cat.AddTypeSummary(
|
|
lldb.SBTypeNameSpecifier("CCC"),
|
|
lldb.SBTypeSummary.CreateWithFunctionName(
|
|
"synth.ccc_summary", lldb.eTypeOptionCascade
|
|
),
|
|
)
|
|
cat.AddTypeSynthetic(
|
|
lldb.SBTypeNameSpecifier("Empty1"),
|
|
lldb.SBTypeSynthetic.CreateWithClassName("synth.Empty1SynthProvider"),
|
|
)
|
|
cat.AddTypeSummary(
|
|
lldb.SBTypeNameSpecifier("Empty1"),
|
|
lldb.SBTypeSummary.CreateWithFunctionName("synth.empty1_summary"),
|
|
)
|
|
cat.AddTypeSynthetic(
|
|
lldb.SBTypeNameSpecifier("Empty2"),
|
|
lldb.SBTypeSynthetic.CreateWithClassName("synth.Empty2SynthProvider"),
|
|
)
|
|
cat.AddTypeSummary(
|
|
lldb.SBTypeNameSpecifier("Empty2"),
|
|
lldb.SBTypeSummary.CreateWithFunctionName(
|
|
"synth.empty2_summary", lldb.eTypeOptionHideEmptyAggregates
|
|
),
|
|
)
|
|
cat2 = debugger.CreateCategory("CCCSynth2")
|
|
cat2.AddTypeSynthetic(
|
|
lldb.SBTypeNameSpecifier("CCC"),
|
|
lldb.SBTypeSynthetic.CreateWithClassName(
|
|
"synth.CCCSynthProvider", lldb.eTypeOptionCascade
|
|
),
|
|
)
|
|
cat2.AddTypeSummary(
|
|
lldb.SBTypeNameSpecifier("CCC"),
|
|
lldb.SBTypeSummary.CreateWithFunctionName(
|
|
"synth.ccc_synthetic", lldb.eTypeOptionCascade
|
|
),
|
|
)
|
|
cat3 = debugger.CreateCategory("BarIntSynth")
|
|
cat3.AddTypeSummary(
|
|
lldb.SBTypeNameSpecifier("int"),
|
|
lldb.SBTypeSummary.CreateWithFunctionName(
|
|
"synth.bar_int_synthetic", lldb.eTypeOptionCascade
|
|
),
|
|
)
|