// python 2 tapset
// Copyright (C) 2016 Red Hat Inc.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.

%{
/*
 * Defines borrowed from object.h.
 */

/* These flags are used to determine if a type is a subclass. */
#define Py2_TPFLAGS_INT_SUBCLASS         (1L<<23)
#define Py2_TPFLAGS_LONG_SUBCLASS        (1L<<24)
#define Py2_TPFLAGS_LIST_SUBCLASS        (1L<<25)
#define Py2_TPFLAGS_TUPLE_SUBCLASS       (1L<<26)
#define Py2_TPFLAGS_STRING_SUBCLASS      (1L<<27)
#define Py2_TPFLAGS_UNICODE_SUBCLASS     (1L<<28)
#define Py2_TPFLAGS_DICT_SUBCLASS        (1L<<29)
#define Py2_TPFLAGS_BASE_EXC_SUBCLASS    (1L<<30)
#define Py2_TPFLAGS_TYPE_SUBCLASS        (1L<<31)

/*
 * Defines borrowed from code.h.
 */

/* Masks for co_flags above */
#define Py2_CO_OPTIMIZED	0x0001
#define Py2_CO_NEWLOCALS	0x0002
#define Py2_CO_VARARGS		0x0004
#define Py2_CO_VARKEYWORDS	0x0008
#define Py2_CO_NESTED		0x0010
#define Py2_CO_GENERATOR	0x0020
/* The Py2_CO_NOFREE flag is set if there are no free or cell variables.
   This information is redundant, but it allows a single flag test
   to determine whether there is any extra work to be done when the
   call frame it setup.
*/
#define Py2_CO_NOFREE		0x0040

/*
 * Defines borrowed from longintrepr.h
 */
#define Py2Long_SHIFT_SMALL	15
#define Py2Long_SHIFT_BIG	30

%}

#
# Macros to cast to various python 2 types
#

###########################################
#
# FIXME: We've got a couple of problems with @cast() here.
#
# 1) The first thing in the @cast module list is 'python'. On some
#    systems, the python 2 executable is named
#    'python2'. Systemtap's configure script finds the right name for
#    the python 2 executable, but that information doesn't make it
#    here. This file may need to be generated at systemtap build time
#    (i.e. have a 'python2.stp.in' tapset file that gets turned into
#    'python2.stp').
#
# 2) The 2nd and 3rd items in the @cast module list is 64-bit and
#    32-bit versions of 'libpython2.7.so'. That is the name of the
#    current version of python 2 library on most systems. On older
#    systems, that version number might be different.  If we could use
#    wildcards in @cast module paths we could work around this problem
#    (see PR20394). To work around this issue, we'll include 2.6
#    versions of the shared library in the @cast module list. 
#
###########################################

@define Py2ModuleList %(
    "python:/usr/lib64/libpython2.7.so:/usr/lib/libpython2.7.so:/usr/lib64/libpython2.6.so:/usr/lib/libpython2.6.so"
%)

@define Py2Object(object) %(
    @cast(@object, "PyObject", @Py2ModuleList)
%)
@define Py2VarObject(object) %(
    @cast(@object, "PyVarObject", @Py2ModuleList)
%)
@define Py2IntObject(object) %(
    @cast(@object, "PyIntObject", @Py2ModuleList)
%)
@define Py2StringObject(object) %(
    @cast(@object, "PyStringObject", @Py2ModuleList)
%)
@define Py2UnicodeObject(object) %(
    @cast(@object, "PyUnicodeObject", @Py2ModuleList)
%)
@define Py2TypeObject(object) %(
    @cast(@object, "PyTypeObject", @Py2ModuleList)
%)
@define Py2TupleObject(object) %(
    @cast(@object, "PyTupleObject", @Py2ModuleList)
%)
@define Py2ListObject(object) %(
    @cast(@object, "PyListObject", @Py2ModuleList)
%)
@define Py2DictObject(object) %(
    @cast(@object, "PyDictObject", @Py2ModuleList)
%)
@define Py2DictEntry(object) %(
    @cast(@object, "PyDictEntry", @Py2ModuleList)
%)
@define Py2FrameObject(object) %(
    @cast(@object, "PyFrameObject", @Py2ModuleList)
%)
@define Py2CodeObject(object) %(
    @cast(@object, "PyCodeObject", @Py2ModuleList)
%)
@define Py2LongObject(object) %(
    @cast(@object, "PyLongObject", @Py2ModuleList)
%)
@define Py2ClassObject(object) %(
    @cast(@object, "PyClassObject", @Py2ModuleList)
%)
@define Py2InstanceObject(object) %(
    @cast(@object, "PyInstanceObject", @Py2ModuleList)
%)

#
# Systemtap macros based on C macros in object.h.
#

@define Py2_REFCNT(object) %(
    @Py2Object(@object)->ob_refcnt
%)
@define Py2_TYPE(object) %(
    @Py2Object(@object)->ob_type
%)
@define Py2_SIZE(object) %(
    @Py2VarObject(@object)->ob_size
%)

@define Py2Type_HasFeature(t, f) %(
    ((@t->tp_flags & (@f)) != 0)
%)
@define Py2Type_FastSubclass(t, f) %(
    @Py2Type_HasFeature(@t, @f)
%)

#
# Systemtap macros based on C macros in stringobject.h.
#

@define Py2String_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_STRING_SUBCLASS %})
%)

#
# Systemtap macros based on C macros in unicodeobject.h.
#

@define Py2Unicode_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_UNICODE_SUBCLASS %})
%)
@define Py2Unicode_AS_UNICODE(op) %(
    (@Py2UnicodeObject(@op)->str)
%)

#
# Systemtap macros based on C macros in intobject.h.
#

@define Py2Int_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_INT_SUBCLASS %})
%)

@define Py2Int_AS_LONG(op) %(
    @Py2IntObject(@op)->ob_ival
%)

#
# Systemtap macros based on C macros in longobject.h.
#

@define Py2Long_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_LONG_SUBCLASS %})
%)

#
# Systemtap macros based on C macros in dictobject.h.
#

@define Py2Dict_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_DICT_SUBCLASS %})
%)

#
# Systemtap macros based on C macros in tupleobject.h.
#

@define Py2Tuple_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_TUPLE_SUBCLASS %})
%)

#
# Systemtap macros based on C macros in listobject.h.
#

@define Py2List_Check(op) %(
    @Py2Type_FastSubclass(@Py2_TYPE(@op), %{ Py2_TPFLAGS_LIST_SUBCLASS %})
%)

#
# Systemtap macros based on C macros in classobject.h.
#
# Note that the real *_Check() macros compare global variables. We'll
# just check the type name field instead.
#

@define Py2Class_Check(op) %(
    (python2_get_typename(@op) == "classobj")
%)
@define Py2Instance_Check(op) %(
    (python2_get_typename(@op) == "instance")
%)
@define Py2Method_Check(op) %(
    (python2_get_typename(@op) == "instancemethod")
%)

#
# Systemtap functions based on C functions in intobject.c.
#

private function Py2Int_AsLong:long(op:long)
{
    if (op && @Py2Int_Check(op))
	return @Py2Int_AS_LONG(op)
    error(sprintf("Py2Int_AsLong called on %s object at %p",
		  python2_get_typename(op), @__pointer(op)))
    return -1
}

#
# Systemtap functions based on C functions in longobject.c.
#

%{
/* Checking for overflow in PyLong_AsLong is a PITA since C doesn't define
 * anything about what happens when a signed integer operation overflows,
 * and some compilers think they're doing you a favor by being "clever"
 * then.  The bit pattern for the largest positive signed long is
 * (unsigned long)LONG_MAX, and for the smallest negative signed long
 * it is abs(LONG_MIN), which we could write -(unsigned long)LONG_MIN.
 * However, some other compilers warn about applying unary minus to an
 * unsigned operand.  Hence the weird "0-".
 */

#define PY2_LLONG_MIN		(-__LONG_LONG_MAX__ - 1L)
#define PY2_ABS_LLONG_MIN	(0-(unsigned long long)PY2_LLONG_MIN)
%}

/* Get a C long int from an int object or any object that has an __int__
 * method.
 *
 * On overflow, return -1 and throw an error.
 */

/*
 * The Py2Long_AsLongLong() function below is actually based on
 * PyLong_AsLong(). Python's PyLong_AsLongLong() converts the PyLong
 * to a Bytes object. This method is simpler.
 *
 * On 32-bit platforms, the default for PYLONG_BITS_IN_DIGIT is 15. On
 * 64-bit platforms, the default for PYLONG_BITS_IN_DIGIT is 30. We're
 * going to assume that to be true, even though it can be configured
 * differently.
 *
 * Since we can be on a 64-bit kernel but probing a 32-bit python 3
 * executable, we have to handle both cases at the same time.
 */

private function Py2Long_AsLongLongAndOverflow_Small:long(v:long)
{
    if (@defined(@Py2LongObject(0)->ob_digit[0])) {
	res = -1
	i = @Py2_SIZE(v)
	if (i == -1) {
	    /* We're using 'x & 0xffff' instead of '(short)x' */
	    res = -(@Py2LongObject(v)->ob_digit[0] & 0xffff)
	}
	else if (i == 0) {
	    res = 0
	}
	else if (i == 1) {
	    res = @Py2LongObject(v)->ob_digit[0]
	}
	else {
	    sign = 1
	    x = 0
	    if (i < 0) {
		sign = -1
		i = -(i)
	    }
	    while (--i >= 0) {
		prev = x
		x = ((x << %{ Py2Long_SHIFT_SMALL %})
		     | @Py2LongObject(v)->ob_digit[i])
		if ((x >> %{ Py2Long_SHIFT_SMALL %}) != prev) {
		    error("Python int too large")
		    return res
		}
	    }
	    /* Haven't lost any bits, but casting to long requires extra
	     * care (see comment above).
	     */
	    if (x <= %{ __LONG_LONG_MAX__ %}) {
		res = x * sign
	    }
	    else if (sign < 0 && x == %{ PY2_ABS_LLONG_MIN %}) {
		res = %{ PY2_LLONG_MIN %}
	    }
	    else {
		error("Python int too large")
		/* res is already set to -1 */
	    }
	}
	return res
    }
    else {
	error("Py2Long_AsLongLongAndOverflow_Small isn't implemented")
    }
}

private function Py2Long_AsLongLongAndOverflow_Big:long(v:long)
{
    if (@defined(@Py2LongObject(0)->ob_digit[0])) {
	res = -1
	i = @Py2_SIZE(v)
	if (i == -1) {
	    /* We're using 'x & 0xffffffff' instead of '(int32_t)x' */
	    res = -(@Py2LongObject(v)->ob_digit[0] & 0xffffffff)
	}
	else if (i == 0) {
	    res = 0
	}
	else if (i == 1) {
	    res = @Py2LongObject(v)->ob_digit[0]
	}
	else {
	    sign = 1
	    x = 0
	    if (i < 0) {
		sign = -1
		i = -(i)
	    }
	    while (--i >= 0) {
		prev = x
		x = ((x << %{ Py2Long_SHIFT_BIG %})
		     | @Py2LongObject(v)->ob_digit[i])
		if ((x >> %{ Py2Long_SHIFT_BIG %}) != prev) {
		    error("Python int too large")
		    return res
		}
	    }
	    /* Haven't lost any bits, but casting to long requires extra
	     * care (see comment above).
	     */
	    if (x <= %{ __LONG_LONG_MAX__ %}) {
		res = x * sign
	    }
	    else if (sign < 0 && x == %{ PY2_ABS_LLONG_MIN %}) {
		res = %{ PY2_LLONG_MIN %}
	    }
	    else {
		error("Python int too large")
		/* res is already set to -1 */
	    }
	}
	return res
    }
    else {
	error("Py2Long_AsLongLongAndOverflow_Big isn't implemented")
    }
}

private function Py2Long_AsLongLong:long(v:long)
{
    if (@defined(@Py2LongObject(0)->ob_digit[0])) {
	if (! @Py2Long_Check(v)) {    
	    error(sprintf("Py2Long_AsLong called on %s object at %p",
		  python2_get_typename(v), @__pointer(v)))
	    return -1
	}

%( CONFIG_64BIT == "y" %?
%( CONFIG_COMPAT == "y" %?
	if (@__compat_task) {
	    return Py2Long_AsLongLongAndOverflow_Small(v)
	}
%)
	return Py2Long_AsLongLongAndOverflow_Big(v)
%:
	return Py2Long_AsLongLongAndOverflow_Small(v)
%)
    }
    else {
	error("Py2Long_AsLongLong isn't implemented")
    }
}

#
# Systemtap functions based on C functions in stringobject.c.
#

private function Py2String_AsString:string(object:long)
{
    if (!@Py2String_Check(object)) {
	# NB: python returns value of string_getbuffer() here
	# (basically getting a string representation of any
	# object). We can't create new objects here, so we'll
	# just quit.
	warn(sprintf("Py2String_AsString called on non-string address 0x%p\n",
		     @__pointer(object)))
	return ""
    }
    try {
	return user_string(@Py2StringObject(object)->ob_sval)
    } catch {
	warn(sprintf("Py2String_AsString failed on address 0x%p\n",
		     @__pointer(@Py2StringObject(object)->ob_sval)))
	return ""
    }
}

private function Py2String_Size:long(object:long)
{
    if (! @Py2String_Check(object)) {
	# NB: python code returns string_getsize(), creating a new
	# string representation of the object, then returning the
	# length of the new string. We can't create new objects here,
	# so we'll just quit.
	warn(sprintf("Py2string_Size called on non-string object address 0x%p\n",
		     @__pointer(object)))
	return 0
    }
    return @Py2_SIZE(object)
}

#
# Systemtap functions based on C functions in unicodeobject.c.
#

private function Py2Unicode_AsASCIIString:string(object:long)
{
    if (!@Py2Unicode_Check(object)) {
	warn(sprintf("Py2Unicode_AsASCIIString called on non-unicode string address 0x%p\n",
		     @__pointer(object)))
	return ""
    }
    try {
	return user_string_utf32(@Py2Unicode_AS_UNICODE(object))
    } catch {
	warn(sprintf("Py2Unicode_AsASCIIString failed on address 0x%p\n",
		     @__pointer(object)))
	return ""
    }
}

#
# Systemtap functions based on C functions in codeobject.c.
#

private function Py2Code_Addr2Line:long(code:long, addrq:long)
{
    /*
     * co_lnotab is used to compute the line number from a bytecode
     * index, addrq. See the file Objects/lnotab_notes.txt in the
     * python source for the details of the lnotab representation.
     *
     * We can't treat co_lnotab as a "real" null terminated string,
     * since co_lnotab can have embedded null characters. So, we'll
     * grab it character by character.
     */
    size = Py2String_Size(@Py2CodeObject(code)->co_lnotab) / 2
    co_lnotab_sval = @Py2StringObject(@Py2CodeObject(code)->co_lnotab)->ob_sval
    line = @Py2CodeObject(code)->co_firstlineno
    addr = 0
    p = 0
    while (--size >= 0) {
    	addr += user_char(co_lnotab_sval + p++)
	if (addr > addrq)
	    break
	    
	line += user_char(co_lnotab_sval + p++)
    }
    return line
}

#
# Systemtap functions based on C functions in frameobject.c.
#

private function Py2Frame_GetLineNumber:long(frame:long)
{
    # As of python 2.3, f_lineno is only valid when tracing is active.
    if (@Py2FrameObject(frame)->f_trace)
	return @Py2FrameObject(frame)->f_lineno
    else
	return Py2Code_Addr2Line(@Py2FrameObject(frame)->f_code,
				 @Py2FrameObject(frame)->f_lasti)
}

#
# Systemtap functions based on C functions in tupleobject.c.
#

private function Py2Tuple_Size:long(op:long)
{
    if (!@Py2Tuple_Check(op)) {
	error(sprintf("Py2Tuple_Size called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
	return 0
    }
    return @Py2_SIZE(op)
}

private function Py2Tuple_GetItem:long(op:long, i:long)
{
    if (!@Py2Tuple_Check(op)) {
	error(sprintf("Py2Tuple_GetItem called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
	return 0
    }
    if (i < 0 || i >= @Py2_SIZE(op)) {
        error(sprintf("tuple index %d out of range (%d)", i, @Py2_SIZE(op)))
        return 0
    }
    return @Py2TupleObject(op)->ob_item[i]
}

private function Py2Tuple_Repr:string(object:long)
{
    if (!@Py2Tuple_Check(object)) {
	error(sprintf("Py2Tuple_Repr called on %s object at %p",
		      python2_get_typename(object), @__pointer(object)))
	return ""
    }
    n = @Py2_SIZE(object)
    if (n == 0)
	return "()"

    retstr = "("
    first = 1
    for (i = 0; i < n; i++) {
	if (! first)
	    retstr .= " "
	first = 0
	retstr .= Py2Object_Repr(Py2Tuple_GetItem(object, i))
    }
    retstr .= ")"
    return retstr
}

#
# Systemtap functions based on C functions in listobject.c.
#

private function Py2List_GetItem:long(op:long, i:long)
{
    if (!@Py2List_Check(op)) {
	error(sprintf("Py2List_GetItem called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
        return 0
    }
    if (i < 0 || i >= @Py2_SIZE(op)) {
        error(sprintf("tuple index %d out of range (%d)", i, @Py2_SIZE(op)))
        return 0
    }
    return @Py2ListObject(op)->ob_item[i];
}

private function Py2List_Repr:string(object:long)
{
    if (!@Py2List_Check(object)) {
	error(sprintf("Py2List_Repr called on %s object at %p",
		      python2_get_typename(object), @__pointer(object)))
	return ""
    }
    n = @Py2_SIZE(object)
    if (n == 0)
	return "[]"

    retstr = "["
    first = 1
    for (i = 0; i < n; i++) {
	if (! first)
	    retstr .= " "
	first = 0
	retstr .= Py2Object_Repr(Py2List_GetItem(object, i))
    }
    retstr .= "]"
    return retstr
}

#
# Systemtap functions based on C functions in dictobject.c.
#

private function Py2Dict_GetItem:long(op:long, key:string)
{
    if (!@Py2Dict_Check(op)) {
	error(sprintf("Py2Dict_GetItem called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
        return 0
    }
    // We'd like to hash 'key' here, but we can't (easily). Python
    // randomizes its hash function at python process startup. So,
    // instead of hashing the key and then searching for that hash in
    // the dictionary, we'll do a linear search of the dictionary for
    // the key.
    n = @Py2DictObject(op)->ma_mask;
    for (i = 0; i <= n; i++) {
	entry_ptr = &@Py2DictObject(op)->ma_table[i]
	if (@Py2DictEntry(entry_ptr)->me_key == 0
	    || @Py2DictEntry(entry_ptr)->me_value == 0
	    || ! @Py2String_Check(@Py2DictEntry(entry_ptr)->me_key))
	   continue
	if (Py2String_AsString(@Py2DictEntry(entry_ptr)->me_key) == key)
	   return @Py2DictEntry(entry_ptr)->me_value
    }
    return 0
}

# This function isn't really based on a function in dictobject.c, but
# it is based on Py2Dict_GetItem().
private function Py2Dict_GetItem_FromLong:long(op:long, key:long)
{
    if (!@Py2Dict_Check(op)) {
	error(sprintf("Py2Dict_GetItem called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
        return 0
    }
    // We'd like to hash 'key' here, but we can't (easily). Python
    // randomizes its hash function at python process startup. So,
    // instead of hashing the key and then searching for that hash in
    // the dictionary, we'll do a linear search of the dictionary for
    // the key.
    n = @Py2DictObject(op)->ma_mask;
    for (i = 0; i <= n; i++) {
	entry_ptr = &@Py2DictObject(op)->ma_table[i]
	if (@Py2DictEntry(entry_ptr)->me_key == 0
	    || @Py2DictEntry(entry_ptr)->me_value == 0
	    || (! @Py2Int_Check(@Py2DictEntry(entry_ptr)->me_key)
		&& ! @Py2Long_Check(@Py2DictEntry(entry_ptr)->me_key)))
	   continue
	if ((@Py2Int_Check(@Py2DictEntry(entry_ptr)->me_key)
	     && @Py2Int_AS_LONG(@Py2DictEntry(entry_ptr)->me_key) == key)
	    || (@Py2Long_Check(@Py2DictEntry(entry_ptr)->me_key)
		&& Py2Long_AsLongLong(@Py2DictEntry(entry_ptr)->me_key)
		== key))
	   return @Py2DictEntry(entry_ptr)->me_value
    }
    return 0
}

private function Py2Dict_Repr:string(object:long)
{
    if (!@Py2Dict_Check(object)) {
	error(sprintf("Py2Dict_Repr called on %s object at %p",
		      python2_get_typename(object), @__pointer(object)))
	return ""
    }
    n = @Py2DictObject(object)->ma_mask;
    if (n == 0)
	return "{}"

    retstr = "{"
    first = 1
    for (i = 0; i <= n; i++) {
	entry_ptr = &@Py2DictObject(object)->ma_table[i]
	if (@Py2DictEntry(entry_ptr)->me_key == 0
	    || @Py2DictEntry(entry_ptr)->me_value == 0)
	   continue

	if (! first)
	    retstr .= " "
	first = 0
	retstr .= Py2Object_Repr(@Py2DictEntry(entry_ptr)->me_key)
	retstr .= ":"
	retstr .= Py2Object_Repr(@Py2DictEntry(entry_ptr)->me_value)
    }
    retstr .= "}"
    return retstr
}

#
# Systemtap functions based on C functions in classobject.c.
#

private function Py2Class_Lookup:long(op:long, name:string)
{
    v = Py2Dict_GetItem(@Py2ClassObject(op)->cl_dict, name)
    if (v != 0)
       return v

    n = Py2Tuple_Size(@Py2ClassObject(op)->cl_bases)
    for (i = 0; i < n; i++) {
	v = Py2Class_Lookup(Py2Tuple_GetItem(@Py2ClassObject(op)->cl_bases, i),
			    name)
	if (v != 0)
	    return v
    }
    return 0
}

private function Py2Class_GetAttr:long(op:long, name:string)
{
    if (!@Py2Class_Check(op)) {
	error(sprintf("Py2Class_GetAttr called on %s object at %p",
		      python2_get_typename(op), @__pointer(op)))
	return 0
    }
    if (substr(name, 0, 2) == "__") {
        if (name == "__dict__")
	    return @Py2ClassObject(op)->cl_dict
	if (name == "__bases__")
	    return @Py2ClassObject(op)->cl_bases
	if (name == "__name__") {
	    if (@Py2ClassObject(op)->cl_name == 0)
		return 0
	    return @Py2ClassObject(op)->cl_name
	}
    }
    v = Py2Class_Lookup(op, name)
    if (v == 0) {
	error(sprintf("class %s has no attribute '%s'",
		      Py2String_AsString(@Py2ClassObject(op)->cl_name),
		      name))
	return 0
    }
    return v
}

private function Py2Class_Repr:string(object:long)
{
    if (!@Py2Class_Check(object)) {
	error(sprintf("Py2Class_Repr called on %s object at %p",
		      python2_get_typename(object), @__pointer(object)))
	return ""
    }
    mod = Py2Dict_GetItem(@Py2ClassObject(object)->cl_dict, "__module__")
    if (@Py2ClassObject(object)->cl_name == 0
	|| !@Py2String_Check(@Py2ClassObject(object)->cl_name))
	name = "?"
    else
	name = Py2String_AsString(@Py2ClassObject(object)->cl_name)
    if (mod == 0 || !@Py2String_Check(mod))
        return sprintf("<class ?.%s at %p>", name, @__pointer(object))
    else
        return sprintf("<class %s.%s at %p>", name,
		       Py2String_AsString(mod), @__pointer(object))
}

private function Py2Instance_GetAttr:long(inst:long, name:string)
{
    if (!@Py2Instance_Check(inst)) {
	error(sprintf("Py2Instance_GetAttr called on %s object at %p",
		      python2_get_typename(inst), @__pointer(inst)))
	return 0
    }
    if (substr(name, 0, 2) == "__") {
        if (name == "__dict__") 
	    return @Py2InstanceObject(inst)->in_dict
	if (name == "__class__")
	    return @Py2InstanceObject(inst)->in_class
    }

    v = Py2Dict_GetItem(@Py2InstanceObject(inst)->in_dict, name)
    if (v != 0)
	return v
    v = Py2Class_Lookup(@Py2InstanceObject(inst)->in_class, name)
    if (v == 0) {
	error(sprintf("%s instance has no attribute '%s'",
		      Py2String_AsString(@Py2InstanceObject(inst)->in_class->cl_name),
		      name))
	return 0
    }
    return v
}

private function Py2Instance_Repr:string(object:long)
{
    if (!@Py2Instance_Check(object)) {
	error(sprintf("Py2Instance_Repr called on %s object at %p",
		      python2_get_typename(object), @__pointer(object)))
	return ""
    }

    # Note that class instances can have custom '__repr__'
    # functions. But, we can't call them. We'll just use the default
    # 'repr' logic. 

    classname = @Py2InstanceObject(object)->in_class->cl_name
    mod = Py2Dict_GetItem(@Py2InstanceObject(object)->in_class->cl_dict,
			  "__module__")

    if (classname == 0
	|| !@Py2String_Check(classname))
	cname = "?"
    else
	cname = Py2String_AsString(classname)
    if (mod == 0 || !@Py2String_Check(mod))
        return sprintf("<?.%s instance at %p>", cname, @__pointer(object))
    else
        return sprintf("<%s.%s instance at %p>", Py2String_AsString(mod),
		       cname, @__pointer(object))
}

#
# Systemtap functions based on C functions in object.c.
#

function Py2Object_Repr:string(object:long)
{
    if (object == 0)
	return "<NULL>"
    if (@Py2String_Check(object))
	return sprintf("\"%s\"", Py2String_AsString(object))
    if (@Py2Unicode_Check(object))
	return sprintf("\"%s\"", Py2Unicode_AsASCIIString(object))
    if (@Py2Int_Check(object))
    	return sprintf("%#x", Py2Int_AsLong(object))
    if (@Py2Long_Check(object))
    	return sprintf("%#x", Py2Long_AsLongLong(object))
    if (@Py2Tuple_Check(object))
	return Py2Tuple_Repr(object)
    if (@Py2List_Check(object))
	return Py2List_Repr(object)
    if (@Py2Dict_Check(object))
	return Py2Dict_Repr(object)
    if (@Py2Instance_Check(object))
	return Py2Instance_Repr(object)
    if (@Py2Class_Check(object))
	return Py2Class_Repr(object)
    return sprintf("<%s object at %p>", python2_get_typename(object),
		   @__pointer(object))
}

# This function isn't really based on a function in object.c, but
# it is based on Py2Object_Repr().
private function Py2Object_Repr:string(object:long, index:long)
{
    if (object == 0)
	return "<NULL>"
    if (@Py2String_Check(object))
	return sprintf("\"%c\"", stringat(Py2String_AsString(object), index))
    if (@Py2Unicode_Check(object))
	return sprintf("\"%c\"", stringat(Py2Unicode_AsASCIIString(object),
					  index))
    if (@Py2Tuple_Check(object))
	return Py2Object_Repr(Py2Tuple_GetItem(object, index))
    if (@Py2List_Check(object))
	return Py2Object_Repr(Py2List_GetItem(object, index))
    if (@Py2Dict_Check(object))
	return Py2Object_Repr(Py2Dict_GetItem_FromLong(object, index))

    error(sprintf("TypeError: '%s' object cannot be indexed",
	  python2_get_typename(object)))
    return ""
}

# This function isn't really based on a function in object.c, but
# it is based on Py2Object_Repr().
private function Py2Object_GetItem:string(object:long, index:long)
{
    if (object == 0) {
	error("TypeError: None object cannot be indexed")
	return 0
    }
    if (@Py2Tuple_Check(object))
	return Py2Tuple_GetItem(object, index)
    if (@Py2List_Check(object))
	return Py2List_GetItem(object, index)
    if (@Py2Dict_Check(object))
	return Py2Dict_GetItem_FromLong(object, index)

    error(sprintf("TypeError: '%s' object cannot be indexed",
	  python2_get_typename(object)))
    return 0
}

function Py2Object_GetAttr:long(object:long, attr:string)
{
    if (object == 0) {
	error("The None object has no attributes")
	return 0
    }
    if (@Py2Instance_Check(object))
	return Py2Instance_GetAttr(object, attr)
    if (@Py2Class_Check(object))
	return Py2Class_GetAttr(object, attr)

    error(sprintf("The %s object has no attributes",
		  python2_get_typename(object)))
    return 0
}

#
# Systemtap support functions for python 2.
#

/*
 * python2_print_backtrace - Print python backtrace
 *
 * Description: This function is equivalent to
 * print(python2_sprint_backtrace(frame)), except that deeper stack
 * tracing may be supported. 
 *
 * Note that users should call 'python_print_backtrace()', and the
 * translator will call the appropriate python 2 or python 3 function
 * with the correct argument.
 */
function python2_print_backtrace:long(frame:long)
{
    printf("Traceback (most recent call first):\n")
    while (frame != 0) {
	lineno = Py2Frame_GetLineNumber(frame)
	f_code = @Py2FrameObject(frame)->f_code
	filename = Py2String_AsString(@Py2CodeObject(f_code)->co_filename)
	name = Py2String_AsString(@Py2CodeObject(f_code)->co_name)

	# Quit when we make it back to the HelperSDT module.
	if (isinstr(filename, "/HelperSDT/"))
	    break;

	printf("  File \"%s\", line %d, in %s\n", filename, lineno, name)
	# NB: We'd like to print the source line here as python does,
	# but we can't. Python opens up the file and finds the
	# appropriate line, but we can't really do that when we're in
	# the kernel.

	frame = @Py2FrameObject(frame)->f_back
    }
}

/*
 * python2_sprint_backtrace - Get python backtrace
 * 
 * Description: This function returns a string containing a python
 * backtrace.  Output may be truncated as per maximum string length
 * (MAXSTRINGLEN).
 *
 * Note that users should call 'python_sprint_backtrace()', and the
 * translator will call the appropriate python 2 or python 3 function
 * with the correct argument.
 */
function python2_sprint_backtrace:string(frame:long)
{
    retstr = "Traceback (most recent call first):\n"
    while (frame != 0) {
	lineno = Py2Frame_GetLineNumber(frame)
	f_code = @Py2FrameObject(frame)->f_code
	filename = Py2String_AsString(@Py2CodeObject(f_code)->co_filename)
	name = Py2String_AsString(@Py2CodeObject(f_code)->co_name)

	# Quit when we make it back to the HelperSDT module.
	if (isinstr(filename, "/HelperSDT/"))
	    break;

	retstr .= sprintf("  File \"%s\", line %d, in %s\n", filename,
			  lineno, name)

	frame = @Py2FrameObject(frame)->f_back
    }
    return retstr
}

/*
 * python2_get_typename - Get python object type name
 *
 * Description: This function returns a string describing the type of
 * a python object.
 */
private function python2_get_typename:string(obj:long)
{
    if (obj == 0)
	return ""
    return user_string(@Py2_TYPE(obj)->tp_name)
}

/*
 * python2_get_locals - Get python local variables
 *
 * Description: This function returns a list of python local variables
 * from a frame.
 *
 * frame: python frame object pointer
 * flags: 0: function parameters only ($$parms), 1: locals only
 * ($$locals), 2: all local vars, parameters and locals ($$vars)
 *
 * Note that users shouldn't call this directly, they should refer to
 * '$$parms', '$$locals', and '$$vars' in a python probe and the
 * translator will generate code that calls this function with the
 * correct arguments.
 */
function python2_get_locals:string(frame:long, flags:long)
{
    f_code = @Py2FrameObject(frame)->f_code
	
    # flags == 2: get all variables
    if (flags == 2) {		
	i = 0
	n = @Py2CodeObject(f_code)->co_nlocals
    }
    else {
	n = @Py2CodeObject(f_code)->co_argcount
	if (@Py2CodeObject(f_code)->co_flags & %{ Py2_CO_VARARGS %})
	    n++
	if (@Py2CodeObject(f_code)->co_flags & %{ Py2_CO_VARKEYWORDS %})
	    n++

	# flags == 0 (parms only): 0 to n (argcount)
	if (flags == 0)
	    i = 0
	# flags == 1 (locals only): n (argcount) to max
	else if (flags == 1) {
	    i = n
	    n = @Py2CodeObject(f_code)->co_nlocals
	}
    }
    if (i < 0 || i > n) {
        error(sprintf("python2_get_locals: invalid indicies (%d-%d)", i, n))
        return ""
    }
	    
    # Each element in co_varnames is a tuple of strings. The values
    # are in f_localsplus.
    p = @Py2CodeObject(f_code)->co_varnames
    localsplus = @Py2FrameObject(frame)->f_localsplus
    first = 1
    for (; i < n; i++) {
	if (! first)
	    retstr .= " "
	first = 0
	key_obj = Py2Tuple_GetItem(p, i)
	value_obj = user_long(localsplus + (i * %{ sizeof(intptr_t) %}))
	retstr .= sprintf("%s=%s", Py2String_AsString(key_obj),
			  Py2Object_Repr(value_obj))
    }
    return retstr
}

function python2_get_var_obj:long(frame:long, var:string)
{
    f_code = @Py2FrameObject(frame)->f_code
    n = @Py2CodeObject(f_code)->co_nlocals

    # Each element in co_varnames is a tuple of strings. The values
    # are in f_localsplus.
    p = @Py2CodeObject(f_code)->co_varnames
    localsplus = @Py2FrameObject(frame)->f_localsplus
    for (i = 0; i < n; i++) {
	key_obj = Py2Tuple_GetItem(p, i)
	if (var == Py2String_AsString(key_obj)) {
	    return user_long(localsplus + (i * %{ sizeof(intptr_t) %}))
	}
    }

    # If we can't find it in the locals, we look in the globals.
    f_globals = @Py2FrameObject(frame)->f_globals
    value_obj = Py2Dict_GetItem(f_globals, var)
    if (value_obj == 0) {
	error(sprintf("Python variable '%s' cannot be found", var))
	return 0
    }
    return value_obj
}

function python2_get_var_obj:long(frame:long, var:string, index:long)
{
    f_code = @Py2FrameObject(frame)->f_code
    n = @Py2CodeObject(f_code)->co_nlocals
	    
    # Each element in co_varnames is a tuple of strings. The values
    # are in f_localsplus.
    p = @Py2CodeObject(f_code)->co_varnames
    localsplus = @Py2FrameObject(frame)->f_localsplus
    for (i = 0; i < n; i++) {
	key_obj = Py2Tuple_GetItem(p, i)
	if (var == Py2String_AsString(key_obj)) {
	    return user_long(localsplus + (i * %{ sizeof(intptr_t) %}))
	}
    }

    # If we can't find it in the locals, we look in the globals.
    f_globals = @Py2FrameObject(frame)->f_globals
    value_obj = Py2Dict_GetItem(f_globals, var)
    if (value_obj == 0) {
	error(sprintf("Python variable '%s' cannot be found", var))
	return 0
    }
    return value_obj
}
