GDBVariableObject.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Mon, 25 Feb 2019 17:55:20 +0000
changeset 177 e7bd05df3d6b
parent 160 b5b1e436a994
child 178 71baafd9bbcb
permissions -rw-r--r--
Introduce new internal API `GDBDebuggerObject >> updateFrom:` to update one ("old") object with another one ("new"). This is used in cases when we want to preserve an identity of API objects.

"{ Encoding: utf8 }"

"
jv:libgdbs - GNU Debugger Interface Library
Copyright (C) 2015-now Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
"{ Package: 'jv:libgdbs' }"

"{ NameSpace: Smalltalk }"

GDBDebuggerObject subclass:#GDBVariableObject
	instanceVariableNames:'parent name exp path thread frame value type numchild has_more
		children changed inScope visualizer dynamic'
	classVariableNames:'GdbDefaultVisualizer'
	poolDictionaries:''
	category:'GDB-Core'
!

!GDBVariableObject class methodsFor:'documentation'!

copyright
"
jv:libgdbs - GNU Debugger Interface Library
Copyright (C) 2015-now Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
! !

!GDBVariableObject class methodsFor:'initialization'!

initialize
    "Invoked at system start or when the class is dynamically loaded."

    GdbDefaultVisualizer := 'gdb.default_visualizer'

    "Modified: / 04-06-2018 / 15:48:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject class methodsFor:'instance creation'!

new
    "return an initialized instance"

    ^ self basicNew initialize.
! !

!GDBVariableObject class methodsFor:'accessing - GDB value descriptors'!

description
    ^ (super description)
        define:#name as:String;
        define:#numchild as:Integer;
        define:#value as:String;
        define:#type as:String;
        define:#'thread-id' as:Integer;
        define:#has_more as:Boolean;
        define:#dynamic as:Boolean;
        define:#displayhint as:String;
        yourself

    "Created: / 16-09-2014 / 23:59:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 27-02-2015 / 17:10:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'accessing'!

children
    self isValid ifFalse:[ ^ #() ].
    children isNil ifTrue:[ 
        (self isValid and:[has_more or:[ numchild isNil or:[numchild > 0]]]) ifTrue:[
            | result |

            result := debugger send: (GDBMI_var_list_children arguments: (Array with: '--all-values' with: name)).
            children := (result propertyAt: #children) ? #().
            children do:[:each | each setDebugger: debugger; setParent: self ].
            numchild := children size.
        ] ifFalse:[ 
            children := #().
        ].
    ].
    ^ children

    "Created: / 27-01-2018 / 22:53:15 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-06-2018 / 10:57:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

expression
    "Return the expression in target language to access the value (asDtring)
     The expression is relative to it's direct parent (if any),

     For example, consider

         typedef struct _point {
            int x;
            int y;
         } point;
         ...
         point p1 = { 10, 20 };

     and consider a variable object `o` that represents `y` member of point 
     `p1`. Then:

        o path '/ -> 'p1.y'

    while

        p expression '/ -> 'y'
    "

    ^ exp

    "Created: / 28-01-2018 / 21:36:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 05-02-2018 / 21:16:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

frame
    "If a variable object is bound to a specific thread and frame,
     returns that frame (as `GDBFrame`). Otherwise, return `nil`."

    parent notNil ifTrue:[ 
        self assert: frame isNil.
        ^ parent frame
    ].
    ^ frame.

    "Created: / 13-02-2018 / 21:25:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 15-02-2018 / 09:30:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

id
    "Returns the GDB ID (name) of the variable object. This is
    used in commands to identify a variable object instance."

    ^ name

    "Created: / 28-01-2018 / 21:35:33 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

index
    "Return the index of receiver within parent's children
     or `nil` if receiver is not a child varobj."

    parent isNil ifTrue:[ ^ nil ].
    ^ parent children indexOf: self.

    "Created: / 04-06-2018 / 15:01:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

parent
    ^ parent
!

path
    "Return (absolute) expression in target language to access the value 
     (asDtring).

     For example, consider

         typedef struct _point {
            int x;
            int y;
         } point;
         ...
         point p1 = { 10, 20 };

     and consider a variable object `o` that represents `y` member of point 
     `p1`. Then:

        o path '/ -> 'p1.y'

    while

        p expression '/ -> 'y'

    "

    "/ Raise an error early when #path is requested for a child of dynamic
    "/ varobj. Thie is not supported by GDB.
    "/ 
    "/ Although GDB should report an error [1], dur to a bug it report
    "/ either wrong (nonsense) value or crashes. A patch has been send
    "/ to the upstream [2], but meanwhile, check here as well in case someone
    "/ uses this with older / not yet patches version of GDB.
    "/ 
    "/ [1]: https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Variable-Objects.html
    "/ [2]: https://sourceware.org/ml/gdb-patches/2018-06/msg00058.html
    "/ 
    (parent notNil and:[parent isDynamic]) ifTrue:[ 
        GDBError signal: 'Invalid varobj, #path is not supported for children of a dynamic varobjs'.
        ^ self
    ].

    path isNil ifTrue:[ 
        | result |

        result := debugger send: (GDBMI_var_info_path_expression arguments: (Array with: name)).
        path := result propertyAt: #'path_expr'.
    ].
    ^ path

    "Created: / 05-02-2018 / 21:16:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 21-10-2018 / 08:06:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

thread
    "
    If a variable object is bound to a specific thread, returns
    that thread (as GDBThread). Otherwise, `nil` is returned.
    "
    | threadId |
    thread isInteger ifTrue:[ 
        threadId := thread.
        thread := debugger threadForId: threadId.
    ].
    ^ thread

    "Created: / 04-02-2018 / 21:35:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-02-2018 / 18:38:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

type
    ^ type
!

value
    changed value. "/ to force update if necessary
    self isValid ifFalse:[ ^ self class classResources string: '<invalid>' ].
    self inScope ifFalse:[ ^ self class classResources string: '<out-of-scope>' ].
    ^ value

    "Modified: / 12-02-2018 / 22:00:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

valueFormatted: aGDBOutputFormat
    "Return the value formatted using given format (as String)

    Supported formats are:
        * FormatSignedDecimal
        * FormatHexadecimal
        * FormatOctal
        * FormatPaddedHexadecimal

    To get the value in 'natural' format use plain #value 
    rather than #valueFormatted:
    "

    | format result |

    aGDBOutputFormat == FormatBinary ifTrue:[ 
        format := 'binary'
    ] ifFalse:[aGDBOutputFormat == FormatSignedDecimal ifTrue:[ 
        format := 'decimal'
    ] ifFalse:[aGDBOutputFormat == FormatHexadecimal ifTrue:[ 
        format := 'hexadecimal'
    ] ifFalse:[aGDBOutputFormat == FormatOctal ifTrue:[ 
        format := 'octal'
    ] ifFalse:[aGDBOutputFormat == FormatPaddedHexadecimal ifTrue:[ 
        format := 'zero-hexadecimal'
    ] ifFalse:[ 
        GDBError signal: 'Unsupported format: ', aGDBOutputFormat displayString  
    ]]]]].

    "/ If pretty printing in MI is enabled, GDB seems to ignore the -f arguments. To work arround
    "/ this, reset the visualizer to None, print the value and then set it back to default.
    "/ Pretty ugly, sigh.
    ^ [
        debugger isPrettyPrintingEnabled ifTrue:[ 
            debugger send: (GDBMI_var_set_visualizer arguments: (Array with: name with: 'None')) andWait: false.
        ].
        result := debugger send: (GDBMI_var_evaluate_expression arguments: (Array with: '-f' with: format with: name)).
        result value
    ] ensure:[ 
        debugger isPrettyPrintingEnabled ifTrue:[ 
            debugger send: (GDBMI_var_set_visualizer arguments: (Array with: name with: 'gdb.default_visualizer')) andWait: false.
        ].
    ].

    "Created: / 05-02-2018 / 23:34:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 21-10-2018 / 08:06:23 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

visualizer
    "Return the current visualizer used for the variable object.

     See https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Variable-Objects.html
    "
    debugger isPrettyPrintingEnabled ifFalse:[ 
        ^ 'None' 
    ].
    visualizer isNil ifTrue:[
        ^ GdbDefaultVisualizer
    ].
    ^ visualizer

    "Created: / 13-02-2018 / 23:18:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-06-2018 / 15:48:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

visualizer: aString
    "Set a visualizer for the variable object. `aString` is the 
     visualizer to use. The special value ‘None’ means to disable 
     any visualizer in use.

     If not ‘None’, `aString` must be a Python expression. This expression 
     must evaluate to a callable object which accepts a single argument.

     See https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Variable-Objects.html
    "
    |result |

    self assert: (debugger isPrettyPrintingEnabled or:[ aString = 'None' ]).
    self visualizer = aString ifTrue:[ 
        "/ Already set, no need to do anything.    
        ^ self 
    ].
    result := debugger send: (GDBMI_var_set_visualizer arguments: (Array with: name with: aString)) andWait: true.
    result isDone ifTrue:[ 
        visualizer := aString.
        "/ Since we have changed the visualizer, children 
        "/ may have changed too. Delete them and recreate 
        "/ them on demand (see #children)
        numchild := nil.
        children notNil ifTrue:[ 
            children do:[:child | child release ].
            children := nil.  
        ].
    ].

    "Created: / 13-02-2018 / 23:20:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 19-02-2018 / 15:44:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'copying'!

duplicate
    "Create and returns a duplicate of the receiver, representing
     the same value. Other than that the returned duplicate is completely 
     independent"

    | dup |

    "/ For children of non-dynamic varobjs duplication is done
    "/ by evaluating its "path" (a fully rooted expression) and 
    "/ returning the new varobj.
    "/ 
    "/ For children of **dynamic** varobjs, this is however more tricky.
    "/ They have no "path". Thus, we first duplicate the parent and then 
    "/ return child of the just duplicated parent at corresponding index.
    (self parent isNil or:[ self parent isDynamic not ]) ifTrue:[ 
        "/ Easy, evaluate path expression...
        dup := debugger evaluate: self path in: self frame
    ] ifFalse:[
        | dupParent |

        dupParent := self parent duplicate.
        dup := dupParent children at: self index.
    ].
    "/ Make sure the duplicate has the same visualizer
    debugger isPrettyPrintingEnabled ifTrue:[ 
        self visualizer ~= GdbDefaultVisualizer ifTrue:[ 
            dup visualizer: self visualizer
        ].
    ].
    ^ dup

    "Created: / 13-02-2018 / 22:17:36 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-06-2018 / 15:50:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'displaying'!

displayOn: aStream
    self displayOn: aStream indent: 0

    "Created: / 28-01-2018 / 21:40:23 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

displayOn: aStream indent: anInteger
    aStream next: anInteger * 4 put: Character space.
    aStream nextPutAll: exp; space; nextPut: $=; space.
    self hasChildren ifTrue:[ 
        aStream nextPut:${; cr.
        self children do:[:each | 
            each displayOn: aStream indent: anInteger + 1.
            aStream cr.
        ].
        aStream next: anInteger * 4 put: Character space; nextPut:$}.
    ] ifFalse: [ 
        aStream nextPutAll: value
    ].

    "Created: / 28-01-2018 / 21:42:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'finalization'!

executor
    ^ GDBVariableObjectExecutor new 
            setDebugger: debugger;
            setId: name;
            yourself

    "Created: / 28-01-2018 / 23:27:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

finalizationLobby
    self assert: debugger notNil.
    ^ debugger finalizationRegistry.

    "Created: / 28-01-2018 / 23:21:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'initialization & release'!

initialize
    "Invoked when a new instance is created."

    super initialize.
    inScope := true.
    dynamic := false.
    has_more := true.

    "Modified: / 04-06-2018 / 10:55:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

release
    "Releases the variable object and all its children. Once released, 
     the variable object is invalid (i.e., `#isValid` would return `false`) 
     and should not be used anymore."

    self release: true

    "Created: / 04-02-2018 / 23:21:22 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 13-02-2018 / 09:37:33 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

release: delete
    "Releases the variable object and all its children. If 
     `delete` is `true`, send `-var-delete`."

    self assert: debugger notNil.
    self assert: (delete not or:[ parent isNil ]) description: 'Only top-level objects can be deleted (for now)'.

    "/ Release all children first, but not not 'delete' them 
    "/ in GDB (will be done later)
    children notEmptyOrNil ifTrue:[ 
        children do: [ :child | child release:false ].
    ].
    children := nil.
    changed := [ changed := false. true ]. 

    "/ Now 'delete' the variable and all its children
    "/ in GDB
    delete ifTrue:[ 
        self unregisterForFinalization.
        debugger isConnected ifTrue:[
            debugger send: (GDBMI_var_delete arguments: (Array with: name))
        ].
    ].

    "/ Finally, clear the debugger instvar (it's tested in
    "/ `#isValid`
    debugger := nil.

    "Created: / 13-02-2018 / 09:36:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 13-02-2018 / 23:42:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"

!

setDebugger: aGDBDebugger
    super setDebugger: aGDBDebugger.
    aGDBDebugger notNil ifTrue:[
        changed := GDBTransientDataHolder debugger: debugger factory: [ self updateChanged ].
    ].

    "Created: / 01-02-2018 / 09:29:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setExpression: aString
    exp := aString

    "Created: / 28-01-2018 / 21:39:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setFrame: aGDBBrame
    frame := aGDBBrame

    "Created: / 15-02-2018 / 09:29:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setFrameId: aString
    frame := aString

    "Created: / 13-02-2018 / 21:24:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setParent: variableObjectOrNil
    self assert: (variableObjectOrNil isNil or:[ variableObjectOrNil isKindOf: self class ]).
    self assert: debugger notNil.
    parent := variableObjectOrNil

    "Created: / 27-01-2018 / 22:54:42 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 13-02-2018 / 22:02:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

thread_id: anInteger
    thread := anInteger

    "Created: / 04-02-2018 / 21:25:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !


!GDBVariableObject methodsFor:'printing & storing'!

printOn:aStream
    "append a printed representation if the receiver to the argument, aStream"

    super printOn:aStream.
    aStream nextPutAll:'('.
    value printOn: aStream.
    aStream nextPutAll:')'.

    "Created: / 13-06-2017 / 17:03:21 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'queries'!

hasChanged
    ^ changed value

    "Created: / 30-01-2018 / 00:27:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-02-2018 / 22:16:09 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hasChildren
    self isValid ifFalse:[ ^ false ].
    numchild isNil ifTrue:[
        children := nil.
        ^ self children notEmptyOrNil
    ].
    ^ numchild > 0

    "Created: / 27-01-2018 / 22:47:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 15-02-2018 / 13:40:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'testing'!

inScope
   changed value. "/ to force update if necessary
    ^ inScope

    "Created: / 12-02-2018 / 21:56:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isDynamic
    "Return true, if this varobj is a dynamic varobj, false otherwise"

    ^ dynamic

    "Created: / 01-06-2018 / 16:29:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isValid
    changed value. "/ to force update if necessary
    ^ debugger notNil

    "Created: / 04-02-2018 / 21:33:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-02-2018 / 22:28:14 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

parentIsDynamic
    ^ parent notNil and:[ parent parentIsDynamic or: [ parent isDynamic ] ]

    "Created: / 01-09-2018 / 22:52:54 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 03-09-2018 / 11:32:34 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject methodsFor:'updating'!

updateChanged
    "Check for updates and update all changed variables accordingly."

    debugger isNil ifTrue:[ 
        changed := false.
        ^ false.
    ].

    "/ If the thread is running, we cannot check for updates...
    (frame notNil and:[frame thread isRunning]) ifTrue:[
        ^ false
    ].    

    (self thread notNil and:[ self thread isValid not]) ifTrue:[ 
        self release.
        ^ true
    ].

    parent notNil ifTrue:[ 
        parent updateChanged
    ] ifFalse:[
        | result changelist |

        result := debugger send: (GDBMI_var_update arguments: (Array with: '--all-values' with: name)).
        changelist := result propertyAt: #changelist.
        self updateChanged: changelist.        
    ].
    ^ false

    "Created: / 30-01-2018 / 01:08:36 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 01-11-2018 / 13:33:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateChanged: changelist
    | change |

    changelist isEmptyOrNil ifTrue:[ ^ self ].
    change := changelist detect: [ :each | each id = self id ] ifNone:[nil].
    change notNil ifTrue:[ 
        value := change value.
        inScope := change inScope.
        inScope = 'invalid' ifTrue:[ 
            self release.
            ^ self.
        ].
        changed value: true.
        changelist remove: change.
        changelist isEmptyOrNil ifTrue:[ ^ self ].
    ] ifFalse:[ 
        changed value: false.
    ].
    children notEmptyOrNil ifTrue:[ 
        children do: [ :each | each updateChanged: changelist ]
    ].

    "Created: / 30-01-2018 / 01:09:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-02-2018 / 22:29:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBVariableObject class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !


GDBVariableObject initialize!