Resolved lldb summary, python misery

Discussion in 'Mac Programming' started by zeppenwolf, May 5, 2015.

  1. zeppenwolf, May 5, 2015
    Last edited: May 9, 2015

    zeppenwolf macrumors regular

    zeppenwolf

    Joined:
    Nov 17, 2009
    #1
    I'm trying to add an lldb summary to my Xcode project for a class PlugDocument. I added a 'summaries.py' file, and created a ~/.lldbinit file which points to the py file... the first version of my summaries file, cribbed from the web / WWDC, looks like:

    Code:
    def PlugDocument_summary( valueObj , dictionary ):
        nameAsStr = valueObj.GetChildMemberWithName('_pluginName').GetSummary()
        return 'pluginName: ' + nameAsStr
    
    
    def __lldb_init_module( debugger , dictionary ):
        debugger.HandleCommand( 'type summary add PlugDocument -F summaries.PlugDocument_summary')

    And that works fine, in the case where valueObj is a real ObjC object. But it "crashes" sorta kinda, when valueObj is zero, which can happen in the startup of the program. So I thought I would test for that case, and I tried changing the summary part to this:

    Code:
    def PlugDocument_summary( valueObj , dictionary ):
        print valueObj
        if valueObj == None:
            print "troo clause"
            return 'this ' + 'never happens'
        else:
            print "else clause"
            nameAsStr = valueObj.GetChildMemberWithName('_pluginName').GetSummary()
            return 'pluginName: ' + nameAsStr
    But the true clause will not happen, no matter what. I don't know python from an earthworm, if it's not obvious. But "valueObj" is zero, yet the statement "if valueObj == None" never evaluates to true?? What does python want from me here? I've tried a gazillion things, like adding parens, changing "None" to "0"... nothing works. Plz help-- the debug loop is enormous: forget compiling, I need to relaunch Xcode to see changes to the summary file. yikes.

    An example output:
    Code:
    (PlugDocument *) _plugCandidateData = 0x0000000000000000
    else clause
    Traceback (most recent call last):
      File "/Volumes/ [...] /summaries.py", line 11, in PlugDocument_summary
        return 'pluginName: ' + nameAsStr
    TypeError: cannot concatenate 'str' and 'NoneType' objects
     
  2. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #2
    Can you enter the Python debugger, pdb?

    I would surround the code in a try, put in a bare except (catches everything) then enter the pdb inside the except block. Then you'll be able to interactively check things like __class__.__name__ of your variable to see what type it's really of.

    Edit: Wait a minute. I see the problem. Your conditional is checking if one object is None. Then if it's not None, you call a method on it and store the return value. It's that return value which is None, not the initial object. So just move your if check.

    Code:
    def PlugDocument_summary( valueObj , dictionary ):
        print valueObj
        nameAsStr = valueObj.GetChildMemberWithName('_pluginName').GetSummary()
        if nameAsStr == None:
            print "troo clause"
            return 'this ' + 'sometimes happens'
        else:
            print "else clause"
            return 'pluginName: ' + nameAsStr
     
  3. zeppenwolf thread starter macrumors regular

    zeppenwolf

    Joined:
    Nov 17, 2009
    #3
    Your code works, (tHnaks!), however...

    I see what you're saying about nameAsStr being a None object, not the valueObj. But the tactic of executing this:
    Code:
    nameAsStr = valueObj.GetChildMemberWithName('_pluginName').GetSummary()
    
    regardless of the value of valueObj, means that I am now executing:
    Code:
    nameAsStr = (0x0).GetChildMemberWithName('_pluginName').GetSummary()
    
    Apparently that's just fine in python. And maybe it's just my pedigree as a C then C++ programmer, but I really don't want that kind of line in my code, not even in debugging helpers. Call it a matter of style, but beyond that, while you've transmogrified my code to something that works, you also skipped past what seemed like the most important point, from my POV, which is that I didn't intend for that line to execute in the first place, when valueObj was zero.

    But it DOES execute, because my if statement testing the "falsity" of valueObj ALWAYS FAILS. This is what I can't comprehend. See my example output: when I print valueObj, it is "0x0000000000000000". It doesn't get more zero than that. But if I attempt to discern that condition with "if valueObj == 0x0000000000000000":, that test fails. "if valueObj == 0x0": also fails. "if valueObj == 0": also fails. "if valueObj == None": also fails.

    Do you see what I'm saying? How, for the love of Ozzy can I detect that valueObj is zero ? There must be some weird nuance to the fact that, although it is zero, it is an ObjC class pointer, so it's not really zero, except that it's zero, but...

    ???

    IOW, I would really like it if I could get this template code to look like this:
    Code:
    def PlugDocument_summary( valueObj , dictionary ):
    
        if valueObj == 0:
            return ""
    
        nameAsStr = valueObj.GetChildMemberWithName('_pluginName').GetSummary()
        return 'pluginName: ' + nameAsStr
    but WHAT can I put for line 3 ???
     
  4. zeppenwolf thread starter macrumors regular

    zeppenwolf

    Joined:
    Nov 17, 2009
    #5
    Thank you for the effort, chown, but it looks to me as though googling for "python null" is not enough in this case. None of those links you listed changes anything, in my testing.

    In the first one, the SO post, the claim is made that:

    Code:
    The best way to check things for "Noneness" is to use the identity operator, is:
    
    if foo is None:
    In the code I've been referring to, if I adjust things to reflect the SO post, so that it looks like this...

    Code:
    def PlugDocument_summary( valueObj , dictionary ):
    	print valueObj
    
    	if valueObj is None:
    		return 'not klauz'
    then the printed value of valueObj is 0x0000000000000000, as usual, but the IF statement fails. (0x0000000000000000 is None) is not true.

    As for the second link, "5.1 Truth Testing":

    Code:
    Any object can be tested for truth value, for use in an if or while condition or as operand of the Boolean operations below. The following values are considered false:
    
    None
    
    False
    
    zero of any numeric type, for example, 0, 0L, 0.0, 0j.
    This is not just untrue in my case, it is hysterical. "zero of any numeric type" ??? "0x0000000000000000" should suffice, n'est-ce pas? But this simplest possible construction also fails, ( I mean, "not valueObj" is considered false ):

    Code:
    if not valueObj:
            return 'not klauz'
    As for the third link, well, I see nothing there that can help.

    Well, MacForums... I guess I've reached the end, eh? I have a variable which evaluates to zero for a good sixteen bytes, yet I cannot construct an IF statement to determine that the variable is zero, not for love or money. Cripes, how do I get into these situations?? Apparently, I had WAY too much fun in my previous life.
     
  5. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    The fact that it's printing 0x0000000000000000 doesn't necessarily imply it's a 64-bit zero. It could be a string containing those characters. Or it could be some other type.

    I think you'll need to examine the class of the object. This was mentioned in post #2.

    I'd also try the code interactively, and maybe make a test case to examine assumptions and expectations. For example, a genuine 64-bit zero plus 2 will be the number 2. Do the arithmetic and see what happens, but be careful about operator overloads.

    This reminds me of a bug I had once where part of a C string was an actual backslash followed by 'n'. I kept seeing it printed as "\n", and couldn't figure out why I wasn't seeing a newline. I ended up writing code to dump the chars in hex, and the problem became obvious. The debugging principles: Break It Down, Confirm Your Expectations.
     
  6. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #7
    In Python you would just run the string through repr. It would have printed out "\\n".

    ----------

    Print gives you a string intended for end-users. Use repr(valueObj) to get a string intended for debugging purposes.

    IE, printing the int 0 and the str 0 would both print out the exact same thing: 0. Passing each through repr first, though, would give you 0 and '0', respectively. 0 is False. '0' is True.
     
  7. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #8
    After poking around some more, I think this issue needs some clarification.

    First, the overall context. That context isn't Python per se, it's the embedded scripting language of lldb, which happens to be Python with some stuff added. This could have been emphasized more in the OP.

    Second, the context of when the Python func is executed, and what's being passed to it. It's not entirely clear from the OP, but it seems to be an Objective-C object, not a Python object. I'm not entirely clear on what the interoperation between the two objects is, i.e. how Obj-C objects are transformed to and from Python objects. Instead, I'll just make a few observations and comments.


    Observation 1: Objective-C has an actual zero for nil. Python has a Null Object, which is a singleton named None. That is, Python has a null object that is distinct from any numerical zero, and also distinct from any other object. One could make a fair case that the null object for Objective-C is the value zero cast to an id type, but that wouldn't change the fact that Python's null object doesn't have the value zero. I'm specifically referring to the Null Object pattern, which happens to be equal to zero in Obj-C but not in Python. One implication of this is that comparing to numerical zeros will never match.

    Observation 2: if Python's None isn't zero, then what is the interoperable translation from Objective-C's null object (nil) to Python's null object (None)? To me, this is the central question, and the unanswered question, lying at the heart of the original post. My answer is, "I don't know", however, I have some comments.


    Comment 1: I suspect more info will be revealed by looking at the actual class (type) of the object being received by the posted func.

    Comment 2: Here's what we know, i.e. the things that have been confirmed by testing:
    1. It isn't a numerical zero, otherwise the == 0 test would have worked.
    2. It isn't the None object, otherwise the is None test would have worked.
    3. It prints as 0x0000000000000000, which seems unusual if it were a plain number. AFAIK, Python numbers default to printing in decimal, not hex.
    4. The Python expression:
      Code:
      return 'pluginName: ' + nameAsStr
      
      is apparently producing this error message:
      Code:
      TypeError: cannot concatenate 'str' and 'NoneType' objects
      
      This strongly suggests that the object's type is NoneType. However, it's also pretty clear it isn't the singleton None. These two points taken together suggest an object other None whose type is NoneType. I honestly can't think of any other interpretation that matches the observations.
    Comment 3: So how does one determine the type of an object, and then how does one compare it to NoneType?
    https://docs.python.org/2/library/types.html
    Typical use is for functions that do different things depending on their argument types, like the following:
    Code:
    from types import *
    def delete(mylist, item):
        [COLOR="Red"]if type(item) is IntType[/COLOR]:
           del mylist[item]
        else:
           mylist.remove(item)
    
    I've hilited the syntax for the comparison in red. In the case of interest, we don't want IntType, but NoneType. The remainder of the example (i.e. what happens in the true or false branches, isn't relevant. Instead, use code similar to what's in the OP.

    Extremely condensed summary of the story to this point:
    Try this:
    Code:
    if type(valueObj) is NoneType:
    

    There's a fair chance some of what I've written is wrong. I think the overall context is right (lldb's embedded Python), and I think the list of What We Know is mostly right, but some of the other stuff could be wrong.

    If what I've written is mostly or even entirely correct, I can't help wondering where the "NoneType object representing Obj-C's nil but that isn't None" is documented. I find it hard to believe it's completely undocumented, although I could believe it might be glossed over. I'd search the lldb docs for the keyword NoneType, since that seems to be a salient fact about the object in question. This is left as an exercise for the interested reader.
     
  8. zeppenwolf thread starter macrumors regular

    zeppenwolf

    Joined:
    Nov 17, 2009
    #9
    Thanks, guys.

    Chown, you work WAY too hard trying to help me.

    The fact is, the answer has been staring us all in the face the whole time. Also, you were both right-- the answer comes immediately from examining the "type" of valueObj.

    The parameter "valueObj" is a "swig" object. It's a shame that nobody mentioned that, since I happen to have a degree in swigonomics from Arkham University...

    Code:
    repr( valueObj ) iz <lldb.SBValue; proxy of <Swig Object of type 'lldb::SBValue *' at 0x11c5b1600> >
    str( valueObj.GetValueType() ) iz 4
    valueObj.GetValue() iz 0x0000000000000000
    IOW, valueObj, of type (lldb::SBValue*), is a pointer to a WRAPPER class whose purpose is to manage access to the specific variable currently appearing in the debugger... Realizing that valueObj is a wrapper, everything falls into place. Writing these words now, I have to feel extremely foolish, since, "How could it be otherwise?!?"

    Just as ineluctable is the fact the valueObj isn't zero. It never was zero, it never will be zero, the concept makes no sense at all. If a suitable "swig" object could not be created, then the summary function would never be called in the first place-- there would be no work to do!!

    "print valueObj" displays 0x0 because the swig object assumes we care about the object it is liaising to; it assumes that we aren't interested in the swig object itself. Naturally. But all of my if statements did not have that sorta kinda "AI", they just compared null to the swig object itself, thus...

    Thus my bald spot is just a little larger than it was a week ago.

    Ah, well. For the record, ( I hope someone finds this from Google someday ), here's the template I'll be using for all the objects in my program:

    Code:
    def PlugDocument_summary( valueObj , dictionary ):
    
    	if not valueObj.GetValueAsUnsigned():
    		return
    
    	return valueObj.GetChildMemberWithName('_pluginName').GetSummary()
    Also for the record, for the if statement, "valueObj.GetValue()" will NOT suffice. Neither even will "bool(valueObj.GetValue())". Naturally, I don't understand why, but neither do I care at this point.

    Thanks for all the help, and I have to say that the paradigm which brought me here, providing custom summaries for items in the Xcode debugger... it's kinda cool. So this is a case where all the fuss was worth it, let's say. Rock on.
     

Share This Page