1# Xmethod commands.
2# Copyright 2013-2020 Free Software Foundation, Inc.
3
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17import gdb
18import re
19
20"""GDB commands for working with xmethods."""
21
22
23def validate_xm_regexp(part_name, regexp):
24    try:
25        return re.compile(regexp)
26    except SyntaxError:
27        raise SyntaxError("Invalid %s regexp: %s", part_name, regexp)
28
29
30def parse_xm_command_args(arg):
31    """Parses the arguments passed to a xmethod command.
32
33    Arguments:
34        arg: The argument string passed to a xmethod command.
35
36    Returns:
37        A 3-tuple: (<locus matching regular expression>,
38                    <matcher matching regular expression>,
39                    <name matching regular experession>)
40    """
41    argv = gdb.string_to_argv(arg)
42    argc = len(argv)
43    if argc > 2:
44        raise SyntaxError("Too many arguments to command.")
45    locus_regexp = ""
46    matcher_name_regexp = ""
47    xm_name_regexp = None
48    if argc >= 1:
49        locus_regexp = argv[0]
50    if argc == 2:
51        parts = argv[1].split(";", 1)
52        matcher_name_regexp = parts[0]
53        if len(parts) > 1:
54            xm_name_regexp = parts[1]
55    if xm_name_regexp:
56        name_re = validate_xm_regexp("xmethod name", xm_name_regexp)
57    else:
58        name_re = None
59    return (validate_xm_regexp("locus", locus_regexp),
60            validate_xm_regexp("matcher name", matcher_name_regexp),
61            name_re)
62
63
64def get_global_method_matchers(locus_re, matcher_re):
65    """Returns a dict of matching globally registered xmethods.
66
67    Arguments:
68        locus_re: Even though only globally registered xmethods are
69                  looked up, they will be looked up only if 'global' matches
70                  LOCUS_RE.
71        matcher_re: The regular expression matching the names of xmethods.
72
73    Returns:
74        A dict of matching globally registered xmethod matchers.  The only
75        key in the dict will be 'global'.
76    """
77    locus_str = "global"
78    xm_dict = { locus_str: [] }
79    if locus_re.match("global"):
80        xm_dict[locus_str].extend(
81            [m for m in gdb.xmethods if matcher_re.match(m.name)])
82    return xm_dict
83
84
85def get_method_matchers_in_loci(loci, locus_re, matcher_re):
86    """Returns a dict of matching registered xmethods in the LOCI.
87
88    Arguments:
89        loci: The list of loci to lookup matching xmethods in.
90        locus_re: If a locus is an objfile, then xmethod matchers will be
91                  looked up in it only if its filename matches the regular
92                  expression LOCUS_RE.  If a locus is the current progspace,
93                  then xmethod matchers will be looked up in it only if the
94                  string "progspace" matches LOCUS_RE.
95        matcher_re: The regular expression to match the xmethod matcher
96                    names.
97
98    Returns:
99        A dict of matching xmethod matchers.  The keys of the dict are the
100        filenames of the loci the xmethod matchers belong to.
101    """
102    xm_dict = {}
103    for locus in loci:
104        if isinstance(locus, gdb.Progspace):
105            if not locus_re.match('progspace'):
106                continue
107            locus_type = "progspace"
108        else:
109            if not locus_re.match(locus.filename):
110                continue
111            locus_type = "objfile"
112        locus_str = "%s %s" % (locus_type, locus.filename)
113        xm_dict[locus_str] = [
114            m for m in locus.xmethods if matcher_re.match(m.name)]
115    return xm_dict
116
117
118def print_xm_info(xm_dict, name_re):
119    """Print a dictionary of xmethods."""
120    def get_status_string(m):
121        if not m.enabled:
122            return " [disabled]"
123        else:
124          return ""
125
126    if not xm_dict:
127        return
128    for locus_str in xm_dict:
129        if not xm_dict[locus_str]:
130            continue
131        print ("Xmethods in %s:" % locus_str)
132        for matcher in xm_dict[locus_str]:
133            print ("  %s%s" % (matcher.name, get_status_string(matcher)))
134            if not matcher.methods:
135                continue
136            for m in matcher.methods:
137                if name_re is None or name_re.match(m.name):
138                    print ("    %s%s" % (m.name, get_status_string(m)))
139
140
141def set_xm_status1(xm_dict, name_re, status):
142    """Set the status (enabled/disabled) of a dictionary of xmethods."""
143    for locus_str, matchers in xm_dict.items():
144        for matcher in matchers:
145            if not name_re:
146                # If the name regex is missing, then set the status of the
147                # matcher and move on.
148                matcher.enabled = status
149                continue
150            if not matcher.methods:
151                # The methods attribute could be None.  Move on.
152                continue
153            for m in matcher.methods:
154                if name_re.match(m.name):
155                    m.enabled = status
156
157
158def set_xm_status(arg, status):
159    """Set the status (enabled/disabled) of xmethods matching ARG.
160    This is a helper function for enable/disable commands.  ARG is the
161    argument string passed to the commands.
162    """
163    locus_re, matcher_re, name_re = parse_xm_command_args(arg)
164    set_xm_status1(get_global_method_matchers(locus_re, matcher_re), name_re,
165                   status)
166    set_xm_status1(
167        get_method_matchers_in_loci(
168            [gdb.current_progspace()], locus_re, matcher_re),
169        name_re,
170        status)
171    set_xm_status1(
172        get_method_matchers_in_loci(gdb.objfiles(), locus_re, matcher_re),
173        name_re,
174        status)
175
176
177class InfoXMethod(gdb.Command):
178    """GDB command to list registered xmethod matchers.
179
180Usage: info xmethod [LOCUS-REGEXP [NAME-REGEXP]]
181
182LOCUS-REGEXP is a regular expression matching the location of the
183xmethod matchers.  If it is omitted, all registered xmethod matchers
184from all loci are listed.  A locus could be 'global', a regular expression
185matching the current program space's filename, or a regular expression
186matching filenames of objfiles.  Locus could be 'progspace' to specify that
187only xmethods from the current progspace should be listed.
188
189NAME-REGEXP is a regular expression matching the names of xmethod
190matchers.  If this omitted for a specified locus, then all registered
191xmethods in the locus are listed.  To list only a certain xmethods
192managed by a single matcher, the name regexp can be specified as
193matcher-name-regexp;xmethod-name-regexp."""
194
195    def __init__(self):
196        super(InfoXMethod, self).__init__("info xmethod",
197                                          gdb.COMMAND_DATA)
198
199    def invoke(self, arg, from_tty):
200        locus_re, matcher_re, name_re = parse_xm_command_args(arg)
201        print_xm_info(get_global_method_matchers(locus_re, matcher_re),
202                      name_re)
203        print_xm_info(
204            get_method_matchers_in_loci(
205                [gdb.current_progspace()], locus_re, matcher_re),
206            name_re)
207        print_xm_info(
208            get_method_matchers_in_loci(gdb.objfiles(), locus_re, matcher_re),
209            name_re)
210
211
212class EnableXMethod(gdb.Command):
213    """GDB command to enable a specified (group of) xmethod(s).
214
215Usage: enable xmethod [LOCUS-REGEXP [NAME-REGEXP]]
216
217LOCUS-REGEXP is a regular expression matching the location of the
218xmethod matchers.  If it is omitted, all registered xmethods matchers
219from all loci are enabled.  A locus could be 'global', a regular expression
220matching the current program space's filename, or a regular expression
221matching filenames of objfiles.  Locus could be 'progspace' to specify that
222only xmethods from the current progspace should be enabled.
223
224NAME-REGEXP is a regular expression matching the names of xmethods
225within a given locus.  If this omitted for a specified locus, then all
226registered xmethod matchers in the locus are enabled.  To enable only
227a certain xmethods managed by a single matcher, the name regexp can be
228specified as matcher-name-regexp;xmethod-name-regexp."""
229
230    def __init__(self):
231        super(EnableXMethod, self).__init__("enable xmethod",
232                                            gdb.COMMAND_DATA)
233
234    def invoke(self, arg, from_tty):
235        set_xm_status(arg, True)
236
237
238class DisableXMethod(gdb.Command):
239    """GDB command to disable a specified (group of) xmethod(s).
240
241Usage: disable xmethod [LOCUS-REGEXP [NAME-REGEXP]]
242
243LOCUS-REGEXP is a regular expression matching the location of the
244xmethod matchers.  If it is omitted, all registered xmethod matchers
245from all loci are disabled.  A locus could be 'global', a regular
246expression matching the current program space's filename, or a regular
247expression filenames of objfiles. Locus could be 'progspace' to specify
248that only xmethods from the current progspace should be disabled.
249
250NAME-REGEXP is a regular expression matching the names of xmethods
251within a given locus.  If this omitted for a specified locus, then all
252registered xmethod matchers in the locus are disabled.  To disable
253only a certain xmethods managed by a single matcher, the name regexp
254can be specified as matcher-name-regexp;xmethod-name-regexp."""
255
256    def __init__(self):
257        super(DisableXMethod, self).__init__("disable xmethod",
258                                             gdb.COMMAND_DATA)
259
260    def invoke(self, arg, from_tty):
261        set_xm_status(arg, False)
262
263
264def register_xmethod_commands():
265    """Installs the xmethod commands."""
266    InfoXMethod()
267    EnableXMethod()
268    DisableXMethod()
269
270
271register_xmethod_commands()
272