mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-25 02:13:06 +00:00
0df2152307
This implements the first step at supporting C++: class types. In particular, this adds a new drgn_type_kind, DRGN_TYPE_CLASS, and support for parsing DW_TAG_class_type from DWARF. Although classes are not valid in C, this adds support for pretty printing them, for completeness.
436 lines
15 KiB
Python
436 lines
15 KiB
Python
from functools import reduce
|
|
import operator
|
|
import unittest
|
|
|
|
from drgn import (
|
|
Qualifiers,
|
|
array_type,
|
|
bool_type,
|
|
class_type,
|
|
complex_type,
|
|
enum_type,
|
|
float_type,
|
|
function_type,
|
|
int_type,
|
|
pointer_type,
|
|
struct_type,
|
|
typedef_type,
|
|
union_type,
|
|
void_type,
|
|
)
|
|
from tests import coord_type, point_type
|
|
from tests.libdrgn import C_TOKEN, drgn_lexer_c, Lexer
|
|
|
|
|
|
class TestPrettyPrintTypeName(unittest.TestCase):
|
|
def assertTypeName(self, type, expected, same_as_definition=False):
|
|
self.assertEqual(type.type_name(), expected)
|
|
if same_as_definition:
|
|
self.assertEqual(str(type), expected)
|
|
|
|
def assertQualifiedTypeName(self, expected, same_as_definition,
|
|
constructor, *args):
|
|
self.assertEqual(constructor(*args).type_name(), expected)
|
|
qualifiers = [
|
|
(Qualifiers.CONST, 'const'),
|
|
(Qualifiers.VOLATILE, 'volatile'),
|
|
(Qualifiers.RESTRICT, 'restrict'),
|
|
(Qualifiers.ATOMIC, '_Atomic'),
|
|
]
|
|
for qualifier in qualifiers:
|
|
t = constructor(*args, qualifiers=qualifier[0])
|
|
self.assertTypeName(t, qualifier[1] + ' ' + expected,
|
|
same_as_definition)
|
|
|
|
# All qualifiers.
|
|
t = constructor(
|
|
*args,
|
|
qualifiers=reduce(operator.or_, (qualifier[0] for qualifier in qualifiers)))
|
|
self.assertTypeName(t,
|
|
' '.join(qualifier[1] for qualifier in qualifiers) + ' ' + expected,
|
|
same_as_definition)
|
|
|
|
def test_void(self):
|
|
self.assertQualifiedTypeName('void', True, void_type)
|
|
|
|
def test_int(self):
|
|
self.assertQualifiedTypeName('int', True, int_type, 'int', 4, True)
|
|
|
|
def test_bool(self):
|
|
self.assertQualifiedTypeName('_Bool', True, bool_type, '_Bool', 1)
|
|
|
|
def test_float(self):
|
|
self.assertQualifiedTypeName('float', True, float_type, 'float', 4)
|
|
|
|
def test_complex(self):
|
|
self.assertQualifiedTypeName('double _Complex', True, complex_type,
|
|
'double _Complex', 16,
|
|
float_type('double', 8))
|
|
|
|
def test_struct(self):
|
|
self.assertQualifiedTypeName('struct point', True, struct_type,
|
|
'point')
|
|
self.assertQualifiedTypeName('struct <anonymous>', False, struct_type,
|
|
None)
|
|
|
|
def test_union(self):
|
|
self.assertQualifiedTypeName('union option', True, union_type,
|
|
'option'),
|
|
self.assertQualifiedTypeName('union <anonymous>', False, union_type,
|
|
None)
|
|
|
|
def test_class(self):
|
|
self.assertQualifiedTypeName('class coord', True, class_type,
|
|
'coord')
|
|
self.assertQualifiedTypeName('class <anonymous>', False, class_type,
|
|
None)
|
|
|
|
def test_enum(self):
|
|
self.assertQualifiedTypeName('enum color', True, enum_type, 'color',
|
|
None, None),
|
|
self.assertQualifiedTypeName('enum <anonymous>', False, enum_type,
|
|
None, None, None)
|
|
|
|
def test_typedef(self):
|
|
self.assertQualifiedTypeName('bool', False, typedef_type, 'bool',
|
|
bool_type('_Bool', 1))
|
|
|
|
def test_pointer(self):
|
|
self.assertTypeName(pointer_type(8, void_type()), 'void *', True)
|
|
t = pointer_type(8, void_type(Qualifiers.VOLATILE))
|
|
self.assertTypeName(t, 'volatile void *', True)
|
|
t = pointer_type(8, void_type(Qualifiers.VOLATILE), Qualifiers.CONST)
|
|
self.assertTypeName(t, 'volatile void * const', True)
|
|
t = pointer_type(8, t)
|
|
self.assertTypeName(t, 'volatile void * const *', True)
|
|
|
|
def test_array(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(array_type(None, i), 'int []', True)
|
|
self.assertTypeName(array_type(2, i), 'int [2]', True)
|
|
self.assertTypeName(array_type(2, array_type(3, i)), 'int [2][3]', True)
|
|
self.assertTypeName(array_type(2, array_type(3, array_type(4, i))),
|
|
'int [2][3][4]', True)
|
|
|
|
def test_array_of_pointers(self):
|
|
self.assertTypeName(array_type(2, array_type(3, pointer_type(8, int_type('int', 4, True)))),
|
|
'int *[2][3]', True)
|
|
|
|
def test_pointer_to_array(self):
|
|
self.assertTypeName(pointer_type(8, array_type(2, int_type('int', 4, True))),
|
|
'int (*)[2]', True)
|
|
|
|
def test_pointer_to_pointer_to_array(self):
|
|
self.assertTypeName(pointer_type(8, pointer_type(8, array_type(2, int_type('int', 4, True)))),
|
|
'int (**)[2]', True)
|
|
|
|
def test_pointer_to_array_of_pointers(self):
|
|
self.assertTypeName(pointer_type(8, array_type(2, pointer_type(8, int_type('int', 4, True)))),
|
|
'int *(*)[2]', True)
|
|
|
|
def test_array_of_pointers_to_array(self):
|
|
self.assertTypeName(array_type(2, pointer_type(8, array_type(3, int_type('int', 4, True)))),
|
|
'int (*[2])[3]', True)
|
|
|
|
def test_pointer_to_function(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(i, ((i,),), False)),
|
|
'int (*)(int)', True)
|
|
self.assertTypeName(pointer_type(8, function_type(i, ((i, 'x'),), False)),
|
|
'int (*)(int x)', True)
|
|
self.assertTypeName(pointer_type(8, function_type(i, ((i,), (float_type('float', 4),)), False)),
|
|
'int (*)(int, float)', True)
|
|
|
|
def test_pointer_to_function_returning_pointer(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(pointer_type(8, i), ((i,),), False)),
|
|
'int *(*)(int)', True)
|
|
self.assertTypeName(pointer_type(8, function_type(pointer_type(8, i), ((pointer_type(8, i),),), False)),
|
|
'int *(*)(int *)', True)
|
|
|
|
def test_pointer_to_function_returning_pointer_to_const(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(pointer_type(8, int_type('int', 4, True, Qualifiers.CONST)), ((i,),), False)),
|
|
'const int *(*)(int)', True)
|
|
|
|
def test_pointer_to_function_returning_const_pointer(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(pointer_type(8, i, Qualifiers.CONST), ((i,),), False)),
|
|
'int * const (*)(int)', True)
|
|
|
|
def test_const_pointer_to_function_returning_pointer(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(pointer_type(8, i), ((i,),), False), Qualifiers.CONST),
|
|
'int *(* const)(int)', True)
|
|
|
|
def test_array_of_pointers_to_functions(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(array_type(4, pointer_type(8, function_type(i, ((i,),), False))),
|
|
'int (*[4])(int)', True)
|
|
|
|
def test_array_of_const_pointers_to_functions(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(array_type(None, pointer_type(8, function_type(i, ((i,),), False), Qualifiers.CONST)),
|
|
'int (* const [])(int)', True)
|
|
|
|
def test_pointer_to_variadic_function(self):
|
|
i = int_type('int', 4, True)
|
|
self.assertTypeName(pointer_type(8, function_type(i, ((i,),), True)),
|
|
'int (*)(int, ...)', True)
|
|
|
|
def test_pointer_to_function_with_no_parameters(self):
|
|
self.assertTypeName(pointer_type(8, function_type(int_type('int', 4, True), (), False)),
|
|
'int (*)(void)', True)
|
|
|
|
def test_pointer_to_function_with_no_parameter_specification(self):
|
|
self.assertTypeName(pointer_type(8, function_type(int_type('int', 4, True), (), True)),
|
|
'int (*)()', True)
|
|
|
|
def test_function(self):
|
|
self.assertTypeName(function_type(int_type('int', 4, True), (), False),
|
|
'int (void)')
|
|
|
|
|
|
class TestPrettyPrintType(unittest.TestCase):
|
|
def assertPrettyPrint(self, type, expected):
|
|
self.assertEqual(str(type), expected)
|
|
|
|
def test_struct(self):
|
|
self.assertPrettyPrint(point_type, """\
|
|
struct point {
|
|
int x;
|
|
int y;
|
|
}""")
|
|
|
|
line_segment = struct_type('line_segment', 16, (
|
|
(point_type, 'a', 0),
|
|
(point_type, 'b', 8),
|
|
))
|
|
self.assertPrettyPrint(line_segment, """\
|
|
struct line_segment {
|
|
struct point a;
|
|
struct point b;
|
|
}""")
|
|
|
|
anonymous_point = struct_type(None, 8, (
|
|
(int_type('int', 4, True), 'x', 0),
|
|
(int_type('int', 4, True), 'y', 4),
|
|
))
|
|
self.assertPrettyPrint(anonymous_point, """\
|
|
struct {
|
|
int x;
|
|
int y;
|
|
}""")
|
|
|
|
# Member with anonymous struct type.
|
|
line_segment = struct_type('line_segment', 16, (
|
|
(anonymous_point, 'a', 0),
|
|
(anonymous_point, 'b', 8),
|
|
))
|
|
|
|
self.assertPrettyPrint(line_segment, """\
|
|
struct line_segment {
|
|
struct {
|
|
int x;
|
|
int y;
|
|
} a;
|
|
struct {
|
|
int x;
|
|
int y;
|
|
} b;
|
|
}""")
|
|
|
|
# Unnamed member.
|
|
point3 = struct_type('point3', 0, (
|
|
(anonymous_point, None, 0),
|
|
(int_type('int', 4, True), 'z', 8),
|
|
))
|
|
self.assertPrettyPrint(point3, """\
|
|
struct point3 {
|
|
struct {
|
|
int x;
|
|
int y;
|
|
};
|
|
int z;
|
|
}""")
|
|
|
|
def test_bit_field(self):
|
|
point = struct_type('point', 4, (
|
|
(int_type('int', 4, True), 'x', 0, 4),
|
|
(int_type('int', 4, True), 'y', 4, 8),
|
|
))
|
|
self.assertPrettyPrint(point, """\
|
|
struct point {
|
|
int x : 4;
|
|
int y : 8;
|
|
}""")
|
|
|
|
def test_union(self):
|
|
t = union_type('foo', 4, (
|
|
(int_type('int', 4, True), 'i'),
|
|
(array_type(4, int_type('unsigned char', 1, False)), 'a'),
|
|
))
|
|
self.assertPrettyPrint(t, """\
|
|
union foo {
|
|
int i;
|
|
unsigned char a[4];
|
|
}""")
|
|
|
|
t = union_type('foo', 4, (
|
|
(int_type('int', 4, True), 'i'),
|
|
(array_type(4, int_type('unsigned char', 1, False)), 'a'),
|
|
), Qualifiers.CONST)
|
|
self.assertPrettyPrint(t, """\
|
|
const union foo {
|
|
int i;
|
|
unsigned char a[4];
|
|
}""")
|
|
|
|
def test_class(self):
|
|
self.assertPrettyPrint(coord_type, """\
|
|
class coord {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
}""")
|
|
|
|
def test_enum(self):
|
|
t = enum_type('color', int_type('unsigned int', 4, False), (
|
|
('RED', 0),
|
|
('GREEN', 1),
|
|
('BLUE', 2),
|
|
))
|
|
self.assertPrettyPrint(t, """\
|
|
enum color {
|
|
RED = 0,
|
|
GREEN = 1,
|
|
BLUE = 2,
|
|
}""")
|
|
|
|
t = enum_type('color', int_type('unsigned int', 4, False), (
|
|
('RED', 0),
|
|
('GREEN', 1),
|
|
('BLUE', 2),
|
|
), Qualifiers.CONST)
|
|
self.assertPrettyPrint(t, """\
|
|
const enum color {
|
|
RED = 0,
|
|
GREEN = 1,
|
|
BLUE = 2,
|
|
}""")
|
|
|
|
t = enum_type(None, int_type('int', 4, True), (
|
|
('RED', 0),
|
|
('GREEN', -1),
|
|
('BLUE', -2),
|
|
))
|
|
self.assertPrettyPrint(t, """\
|
|
enum {
|
|
RED = 0,
|
|
GREEN = -1,
|
|
BLUE = -2,
|
|
}""")
|
|
|
|
def test_typedef(self):
|
|
self.assertPrettyPrint(typedef_type('INT', int_type('int', 4, True)),
|
|
'typedef int INT')
|
|
self.assertPrettyPrint(typedef_type('CINT', int_type('int', 4, True, Qualifiers.CONST)),
|
|
'typedef const int CINT')
|
|
self.assertPrettyPrint(typedef_type('INT', int_type('int', 4, True), Qualifiers.CONST),
|
|
'const typedef int INT')
|
|
self.assertPrettyPrint(typedef_type('string', pointer_type(8, int_type('char', 1, True))),
|
|
'typedef char *string')
|
|
|
|
t = typedef_type('Point', struct_type(None, 8, (
|
|
(int_type('int', 4, True), 'x', 0),
|
|
(int_type('int', 4, True), 'y', 4),
|
|
)))
|
|
self.assertPrettyPrint(t, """\
|
|
typedef struct {
|
|
int x;
|
|
int y;
|
|
} Point""")
|
|
|
|
def test_function_typedef(self):
|
|
self.assertPrettyPrint(typedef_type('fn', function_type(int_type('int', 4, True), (), False)),
|
|
'typedef int fn(void)')
|
|
|
|
def test_function_no_name(self):
|
|
self.assertRaisesRegex(ValueError, 'function must have name', str,
|
|
struct_type('foo', 8, (
|
|
(function_type(int_type('int', 4, True), (), False), None),
|
|
)))
|
|
|
|
|
|
class TestLexer(unittest.TestCase):
|
|
def lex(self, s):
|
|
lexer = Lexer(drgn_lexer_c, s)
|
|
while True:
|
|
token = lexer.pop()
|
|
if token.kind == C_TOKEN.EOF:
|
|
break
|
|
yield token
|
|
|
|
def test_empty(self):
|
|
lexer = Lexer(drgn_lexer_c, '')
|
|
for i in range(64):
|
|
self.assertEqual(lexer.pop().kind, C_TOKEN.EOF)
|
|
|
|
def test_symbols(self):
|
|
s = '()[]*.'
|
|
tokens = [
|
|
C_TOKEN.LPAREN,
|
|
C_TOKEN.RPAREN,
|
|
C_TOKEN.LBRACKET,
|
|
C_TOKEN.RBRACKET,
|
|
C_TOKEN.ASTERISK,
|
|
C_TOKEN.DOT,
|
|
]
|
|
self.assertEqual([token.kind for token in self.lex(s)], tokens)
|
|
|
|
def test_keywords(self):
|
|
s = """void char short int long signed unsigned _Bool float double
|
|
_Complex const restrict volatile _Atomic struct union enum"""
|
|
tokens = [
|
|
C_TOKEN.VOID,
|
|
C_TOKEN.CHAR,
|
|
C_TOKEN.SHORT,
|
|
C_TOKEN.INT,
|
|
C_TOKEN.LONG,
|
|
C_TOKEN.SIGNED,
|
|
C_TOKEN.UNSIGNED,
|
|
C_TOKEN.BOOL,
|
|
C_TOKEN.FLOAT,
|
|
C_TOKEN.DOUBLE,
|
|
C_TOKEN.COMPLEX,
|
|
C_TOKEN.CONST,
|
|
C_TOKEN.RESTRICT,
|
|
C_TOKEN.VOLATILE,
|
|
C_TOKEN.ATOMIC,
|
|
C_TOKEN.STRUCT,
|
|
C_TOKEN.UNION,
|
|
C_TOKEN.ENUM,
|
|
]
|
|
self.assertEqual([token.kind for token in self.lex(s)], tokens)
|
|
|
|
def test_identifiers(self):
|
|
s = '_ x foo _bar baz1'
|
|
tokens = s.split()
|
|
self.assertEqual([(token.kind, token.value) for token in self.lex(s)],
|
|
[(C_TOKEN.IDENTIFIER, value) for value in tokens])
|
|
|
|
def test_number(self):
|
|
s = '0 1234 0xdeadbeef'
|
|
tokens = s.split()
|
|
self.assertEqual([(token.kind, token.value) for token in self.lex(s)],
|
|
[(C_TOKEN.NUMBER, value) for value in tokens])
|
|
|
|
def test_invalid_number(self):
|
|
for s in ['0x', '1234y']:
|
|
self.assertRaisesRegex(SyntaxError, 'invalid number', list,
|
|
self.lex(s))
|
|
|
|
def test_invalid_character(self):
|
|
self.assertRaisesRegex(SyntaxError, 'invalid character', list,
|
|
self.lex('@'))
|