blob: 53bf5ef372eeda99d385fe77772718197a6fc106 [file] [log] [blame]
# Copyright (c) 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper library for defining XMethod implementations on C++ classes.
Include this library and then define python implementations of C++ methods
using the Class and member_function decorator functions.
"""
import gdb
import gdb.xmethod
import operator
import re
class MemberFunction(object):
def __init__(self, return_type, name, arguments, wrapped_function):
self.return_type = return_type
self.name = name
self.arguments = arguments
self.function_ = wrapped_function
def __call__(self, *args):
self.function_(*args)
def member_function(return_type, name, arguments):
"""Decorate a member function.
See Class decorator for example usage within a class.
Args:
return_type: The return type of the function (e.g. 'int')
name: The function name (e.g. 'sum')
arguments: The argument types for this function (e.g. ['int', 'int'])
Each type can be a string (e.g. 'int', 'std::string', 'T*') or a
function which constructs the return type. See CreateTypeResolver
for details about type resolution.
"""
def DefineMember(fn):
return MemberFunction(return_type, name, arguments, fn)
return DefineMember
def Class(class_name, template_types):
"""Decorate a python class with its corresponding C++ type.
Args:
class_name: The canonical string identifier for the class (e.g. base::Foo)
template_types: An array of names for each templated type (e.g. ['K',
'V'])
Example:
As an example, the following is an implementation of size() and operator[]
on std::__1::vector, functions which are normally inlined and not
normally callable from gdb.
@class_methods.Class('std::__1::vector', template_types=['T'])
class LibcppVector(object):
@class_methods.member_function('T&', 'operator[]', ['int'])
def element(obj, i):
return obj['__begin_'][i]
@class_methods.member_function('size_t', 'size', [])
def size(obj):
return obj['__end_'] - obj['__begin_']
Note:
Note that functions are looked up by the function name, which means that
functions cannot currently have overloaded implementations for different
arguments.
"""
class MethodWorkerWrapper(gdb.xmethod.XMethod):
"""Wrapper of an XMethodWorker class as an XMethod."""
def __init__(self, name, worker_class):
super(MethodWorkerWrapper, self).__init__(name)
self.name = name
self.worker_class = worker_class
class ClassMatcher(gdb.xmethod.XMethodMatcher):
"""Matches member functions of one class template."""
def __init__(self, obj):
super(ClassMatcher, self).__init__(class_name)
# Constructs a regular expression to match this type.
self._class_regex = re.compile(
'^' + re.escape(class_name) +
('<.*>' if len(template_types) > 0 else '') + '$')
# Construct a dictionary and array of methods
self.dict = {}
self.methods = []
for name in dir(obj):
attr = getattr(obj, name)
if not isinstance(attr, MemberFunction):
continue
name = attr.name
return_type = CreateTypeResolver(attr.return_type)
arguments = [CreateTypeResolver(arg) for arg in
attr.arguments]
method = MethodWorkerWrapper(
attr.name,
CreateTemplatedMethodWorker(return_type,
arguments, attr.function_))
self.methods.append(method)
def match(self, class_type, method_name):
if not re.match(self._class_regex, class_type.tag):
return None
templates = [class_type.template_argument(i) for i in
range(len(template_types))]
return [method.worker_class(templates) for method in self.methods
if method.name == method_name and method.enabled]
def CreateTypeResolver(type_desc):
"""Creates a callback which resolves to the appropriate type when
invoked.
This is a helper to allow specifying simple types as strings when
writing function descriptions. For complex cases, a callback can be
passed which will be invoked when template instantiation is known.
Args:
type_desc: A callback generating the type or a string description of
the type to lookup. Supported types are classes in the
template_classes array (e.g. T) which will be looked up when those
templated classes are known, or globally visible type names (e.g.
int, base::Foo).
Types can be modified by appending a '*' or '&' to denote a
pointer or reference.
If a callback is used, the callback will be passed an array of the
instantiated template types.
Note:
This does not parse complex types such as std::vector<T>::iterator,
to refer to types like these you must currently write a callback
which constructs the appropriate type.
"""
if callable(type_desc):
return type_desc
if type_desc == 'void':
return lambda T: None
if type_desc[-1] == '&':
inner_resolver = CreateTypeResolver(type_desc[:-1])
return lambda template_types: inner_resolver(template_types).reference()
if type_desc[-1] == '*':
inner_resolver = CreateTypeResolver(type_desc[:-1])
return lambda template_types: inner_resolver(template_types).pointer()
try:
template_index = template_types.index(type_desc)
return operator.itemgetter(template_index)
except ValueError:
return lambda template_types: gdb.lookup_type(type_desc)
def CreateTemplatedMethodWorker(return_callback, args_callbacks,
method_callback):
class TemplatedMethodWorker(gdb.xmethod.XMethodWorker):
def __init__(self, templates):
super(TemplatedMethodWorker, self).__init__()
self._templates = templates
def get_arg_types(self):
return [cb(self._templates) for cb in args_callbacks]
def get_result_type(self, obj):
return return_callback(self._templates)
def __call__(self, *args):
return method_callback(*args)
return TemplatedMethodWorker
def DefineClass(obj):
matcher = ClassMatcher(obj)
gdb.xmethod.register_xmethod_matcher(None, matcher)
return matcher
return DefineClass