llvm/lldb/examples/summaries/cocoa/CFArray.py

"""
LLDB AppKit formatters

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
"""
# example summary provider for NSArray
# the real summary is now C++ code built into LLDB
import lldb
import ctypes
import lldb.runtime.objc.objc_runtime
import lldb.formatters.metrics
import lldb.formatters.Logger

statistics = lldb.formatters.metrics.Metrics()
statistics.add_metric("invalid_isa")
statistics.add_metric("invalid_pointer")
statistics.add_metric("unknown_class")
statistics.add_metric("code_notrun")

# much less functional than the other two cases below
# just runs code to get to the count and then returns
# no children


class NSArrayKVC_SynthProvider:
    def adjust_for_architecture(self):
        pass

    def __init__(self, valobj, dict, params):
        logger = lldb.formatters.Logger.Logger()
        self.valobj = valobj
        self.update()

    def update(self):
        logger = lldb.formatters.Logger.Logger()
        self.adjust_for_architecture()

    def num_children(self):
        logger = lldb.formatters.Logger.Logger()
        stream = lldb.SBStream()
        self.valobj.GetExpressionPath(stream)
        num_children_vo = self.valobj.CreateValueFromExpression(
            "count", "(int)[" + stream.GetData() + " count]"
        )
        if num_children_vo.IsValid():
            return num_children_vo.GetValueAsUnsigned(0)
        return "<variable is not NSArray>"


# much less functional than the other two cases below
# just runs code to get to the count and then returns
# no children


class NSArrayCF_SynthProvider:
    def adjust_for_architecture(self):
        pass

    def __init__(self, valobj, dict, params):
        logger = lldb.formatters.Logger.Logger()
        self.valobj = valobj
        self.sys_params = params
        if not (self.sys_params.types_cache.ulong):
            self.sys_params.types_cache.ulong = self.valobj.GetType().GetBasicType(
                lldb.eBasicTypeUnsignedLong
            )
        self.update()

    def update(self):
        logger = lldb.formatters.Logger.Logger()
        self.adjust_for_architecture()

    def num_children(self):
        logger = lldb.formatters.Logger.Logger()
        num_children_vo = self.valobj.CreateChildAtOffset(
            "count", self.sys_params.cfruntime_size, self.sys_params.types_cache.ulong
        )
        return num_children_vo.GetValueAsUnsigned(0)


class NSArrayI_SynthProvider:
    def adjust_for_architecture(self):
        pass

    def __init__(self, valobj, dict, params):
        logger = lldb.formatters.Logger.Logger()
        self.valobj = valobj
        self.sys_params = params
        if not (self.sys_params.types_cache.long):
            self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType(
                lldb.eBasicTypeLong
            )
        self.update()

    def update(self):
        logger = lldb.formatters.Logger.Logger()
        self.adjust_for_architecture()

    # skip the isa pointer and get at the size
    def num_children(self):
        logger = lldb.formatters.Logger.Logger()
        count = self.valobj.CreateChildAtOffset(
            "count", self.sys_params.pointer_size, self.sys_params.types_cache.long
        )
        return count.GetValueAsUnsigned(0)


class NSArrayM_SynthProvider:
    def adjust_for_architecture(self):
        pass

    def __init__(self, valobj, dict, params):
        logger = lldb.formatters.Logger.Logger()
        self.valobj = valobj
        self.sys_params = params
        if not (self.sys_params.types_cache.long):
            self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType(
                lldb.eBasicTypeLong
            )
        self.update()

    def update(self):
        logger = lldb.formatters.Logger.Logger()
        self.adjust_for_architecture()

    # skip the isa pointer and get at the size
    def num_children(self):
        logger = lldb.formatters.Logger.Logger()
        count = self.valobj.CreateChildAtOffset(
            "count", self.sys_params.pointer_size, self.sys_params.types_cache.long
        )
        return count.GetValueAsUnsigned(0)


# this is the actual synth provider, but is just a wrapper that checks
# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an
# appropriate backend layer to do the computations


class NSArray_SynthProvider:
    def adjust_for_architecture(self):
        pass

    def __init__(self, valobj, dict):
        logger = lldb.formatters.Logger.Logger()
        self.valobj = valobj
        self.adjust_for_architecture()
        self.error = False
        self.wrapper = self.make_wrapper()
        self.invalid = self.wrapper is None

    def num_children(self):
        logger = lldb.formatters.Logger.Logger()
        if self.wrapper is None:
            return 0
        return self.wrapper.num_children()

    def update(self):
        logger = lldb.formatters.Logger.Logger()
        if self.wrapper is None:
            return
        self.wrapper.update()

    # this code acts as our defense against NULL and uninitialized
    # NSArray pointers, which makes it much longer than it would be otherwise
    def make_wrapper(self):
        logger = lldb.formatters.Logger.Logger()
        if self.valobj.GetValueAsUnsigned() == 0:
            self.error = True
            return lldb.runtime.objc.objc_runtime.InvalidPointer_Description(True)
        else:
            global statistics
            (
                class_data,
                wrapper,
            ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
                self.valobj, statistics
            )
            if wrapper:
                self.error = True
                return wrapper

        name_string = class_data.class_name()

        logger >> "Class name is " + str(name_string)

        if name_string == "__NSArrayI":
            wrapper = NSArrayI_SynthProvider(self.valobj, dict, class_data.sys_params)
            statistics.metric_hit("code_notrun", self.valobj.GetName())
        elif name_string == "__NSArrayM":
            wrapper = NSArrayM_SynthProvider(self.valobj, dict, class_data.sys_params)
            statistics.metric_hit("code_notrun", self.valobj.GetName())
        elif name_string == "__NSCFArray":
            wrapper = NSArrayCF_SynthProvider(self.valobj, dict, class_data.sys_params)
            statistics.metric_hit("code_notrun", self.valobj.GetName())
        else:
            wrapper = NSArrayKVC_SynthProvider(self.valobj, dict, class_data.sys_params)
            statistics.metric_hit(
                "unknown_class", str(self.valobj.GetName()) + " seen as " + name_string
            )
        return wrapper


def CFArray_SummaryProvider(valobj, dict):
    logger = lldb.formatters.Logger.Logger()
    provider = NSArray_SynthProvider(valobj, dict)
    if not provider.invalid:
        if provider.error:
            return provider.wrapper.message()
        try:
            summary = int(provider.num_children())
        except:
            summary = None
        logger >> "provider gave me " + str(summary)
        if summary is None:
            summary = "<variable is not NSArray>"
        elif isinstance(summary, str):
            pass
        else:
            # we format it like it were a CFString to make it look the same as
            # the summary from Xcode
            summary = (
                '@"' + str(summary) + (" objects" if summary != 1 else " object") + '"'
            )
        return summary
    return "Summary Unavailable"


def __lldb_init_module(debugger, dict):
    debugger.HandleCommand(
        "type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef"
    )