xref: /openbsd-src/gnu/llvm/clang/utils/clangdiag.py (revision a9ac8606c53d55cee9c3a39778b249c51df111ef)
1*a9ac8606Spatrick#!/usr/bin/env python
2e5dd7070Spatrick
3e5dd7070Spatrick#----------------------------------------------------------------------
4e5dd7070Spatrick# Be sure to add the python path that points to the LLDB shared library.
5e5dd7070Spatrick#
6e5dd7070Spatrick# # To use this in the embedded python interpreter using "lldb" just
7e5dd7070Spatrick# import it with the full path using the "command script import"
8e5dd7070Spatrick# command
9e5dd7070Spatrick#   (lldb) command script import /path/to/clandiag.py
10e5dd7070Spatrick#----------------------------------------------------------------------
11e5dd7070Spatrick
12e5dd7070Spatrickfrom __future__ import absolute_import, division, print_function
13e5dd7070Spatrickimport lldb
14e5dd7070Spatrickimport argparse
15e5dd7070Spatrickimport shlex
16e5dd7070Spatrickimport os
17e5dd7070Spatrickimport re
18e5dd7070Spatrickimport subprocess
19e5dd7070Spatrick
20e5dd7070Spatrickclass MyParser(argparse.ArgumentParser):
21e5dd7070Spatrick    def format_help(self):
22e5dd7070Spatrick        return '''     Commands for managing clang diagnostic breakpoints
23e5dd7070Spatrick
24e5dd7070SpatrickSyntax: clangdiag enable [<warning>|<diag-name>]
25e5dd7070Spatrick        clangdiag disable
26e5dd7070Spatrick        clangdiag diagtool [<path>|reset]
27e5dd7070Spatrick
28e5dd7070SpatrickThe following subcommands are supported:
29e5dd7070Spatrick
30e5dd7070Spatrick      enable   -- Enable clang diagnostic breakpoints.
31e5dd7070Spatrick      disable  -- Disable all clang diagnostic breakpoints.
32e5dd7070Spatrick      diagtool -- Return, set, or reset diagtool path.
33e5dd7070Spatrick
34e5dd7070SpatrickThis command sets breakpoints in clang, and clang based tools, that
35e5dd7070Spatrickemit diagnostics.  When a diagnostic is emitted, and clangdiag is
36e5dd7070Spatrickenabled, it will use the appropriate diagtool application to determine
37e5dd7070Spatrickthe name of the DiagID, and set breakpoints in all locations that
38e5dd7070Spatrick'diag::name' appears in the source.  Since the new breakpoints are set
39e5dd7070Spatrickafter they are encountered, users will need to launch the executable a
40e5dd7070Spatricksecond time in order to hit the new breakpoints.
41e5dd7070Spatrick
42e5dd7070SpatrickFor in-tree builds, the diagtool application, used to map DiagID's to
43e5dd7070Spatricknames, is found automatically in the same directory as the target
44e5dd7070Spatrickexecutable.  However, out-or-tree builds must use the 'diagtool'
45e5dd7070Spatricksubcommand to set the appropriate path for diagtool in the clang debug
46e5dd7070Spatrickbin directory.  Since this mapping is created at build-time, it's
47e5dd7070Spatrickimportant for users to use the same version that was generated when
48e5dd7070Spatrickclang was compiled, or else the id's won't match.
49e5dd7070Spatrick
50e5dd7070SpatrickNotes:
51e5dd7070Spatrick- Substrings can be passed for both <warning> and <diag-name>.
52e5dd7070Spatrick- If <warning> is passed, only enable the DiagID(s) for that warning.
53e5dd7070Spatrick- If <diag-name> is passed, only enable that DiagID.
54e5dd7070Spatrick- Rerunning enable clears existing breakpoints.
55e5dd7070Spatrick- diagtool is used in breakpoint callbacks, so it can be changed
56e5dd7070Spatrick  without the need to rerun enable.
57e5dd7070Spatrick- Adding this to your ~.lldbinit file makes clangdiag available at startup:
58e5dd7070Spatrick  "command script import /path/to/clangdiag.py"
59e5dd7070Spatrick
60e5dd7070Spatrick'''
61e5dd7070Spatrick
62e5dd7070Spatrickdef create_diag_options():
63e5dd7070Spatrick    parser = MyParser(prog='clangdiag')
64e5dd7070Spatrick    subparsers = parser.add_subparsers(
65e5dd7070Spatrick        title='subcommands',
66e5dd7070Spatrick        dest='subcommands',
67e5dd7070Spatrick        metavar='')
68e5dd7070Spatrick    disable_parser = subparsers.add_parser('disable')
69e5dd7070Spatrick    enable_parser = subparsers.add_parser('enable')
70e5dd7070Spatrick    enable_parser.add_argument('id', nargs='?')
71e5dd7070Spatrick    diagtool_parser = subparsers.add_parser('diagtool')
72e5dd7070Spatrick    diagtool_parser.add_argument('path', nargs='?')
73e5dd7070Spatrick    return parser
74e5dd7070Spatrick
75e5dd7070Spatrickdef getDiagtool(target, diagtool = None):
76e5dd7070Spatrick    id = target.GetProcess().GetProcessID()
77e5dd7070Spatrick    if 'diagtool' not in getDiagtool.__dict__:
78e5dd7070Spatrick        getDiagtool.diagtool = {}
79e5dd7070Spatrick    if diagtool:
80e5dd7070Spatrick        if diagtool == 'reset':
81e5dd7070Spatrick            getDiagtool.diagtool[id] = None
82e5dd7070Spatrick        elif os.path.exists(diagtool):
83e5dd7070Spatrick            getDiagtool.diagtool[id] = diagtool
84e5dd7070Spatrick        else:
85e5dd7070Spatrick            print('clangdiag: %s not found.' % diagtool)
86e5dd7070Spatrick    if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]:
87e5dd7070Spatrick        getDiagtool.diagtool[id] = None
88e5dd7070Spatrick        exe = target.GetExecutable()
89e5dd7070Spatrick        if not exe.Exists():
90e5dd7070Spatrick            print('clangdiag: Target (%s) not set.' % exe.GetFilename())
91e5dd7070Spatrick        else:
92e5dd7070Spatrick            diagtool = os.path.join(exe.GetDirectory(), 'diagtool')
93e5dd7070Spatrick            if os.path.exists(diagtool):
94e5dd7070Spatrick                getDiagtool.diagtool[id] = diagtool
95e5dd7070Spatrick            else:
96e5dd7070Spatrick                print('clangdiag: diagtool not found along side %s' % exe)
97e5dd7070Spatrick
98e5dd7070Spatrick    return getDiagtool.diagtool[id]
99e5dd7070Spatrick
100e5dd7070Spatrickdef setDiagBreakpoint(frame, bp_loc, dict):
101e5dd7070Spatrick    id = frame.FindVariable("DiagID").GetValue()
102e5dd7070Spatrick    if id is None:
103e5dd7070Spatrick        print('clangdiag: id is None')
104e5dd7070Spatrick        return False
105e5dd7070Spatrick
106e5dd7070Spatrick    # Don't need to test this time, since we did that in enable.
107e5dd7070Spatrick    target = frame.GetThread().GetProcess().GetTarget()
108e5dd7070Spatrick    diagtool = getDiagtool(target)
109e5dd7070Spatrick    name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip();
110e5dd7070Spatrick    # Make sure we only consider errors, warnings, and extensions.
111e5dd7070Spatrick    # FIXME: Make this configurable?
112e5dd7070Spatrick    prefixes = ['err_', 'warn_', 'exp_']
113e5dd7070Spatrick    if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]):
114e5dd7070Spatrick        bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec())
115e5dd7070Spatrick        bp.AddName("clang::Diagnostic")
116e5dd7070Spatrick
117e5dd7070Spatrick    return False
118e5dd7070Spatrick
119e5dd7070Spatrickdef enable(exe_ctx, args):
120e5dd7070Spatrick    # Always disable existing breakpoints
121e5dd7070Spatrick    disable(exe_ctx)
122e5dd7070Spatrick
123e5dd7070Spatrick    target = exe_ctx.GetTarget()
124e5dd7070Spatrick    numOfBreakpoints = target.GetNumBreakpoints()
125e5dd7070Spatrick
126e5dd7070Spatrick    if args.id:
127e5dd7070Spatrick        # Make sure we only consider errors, warnings, and extensions.
128e5dd7070Spatrick        # FIXME: Make this configurable?
129e5dd7070Spatrick        prefixes = ['err_', 'warn_', 'exp_']
130e5dd7070Spatrick        if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]):
131e5dd7070Spatrick            bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec())
132e5dd7070Spatrick            bp.AddName("clang::Diagnostic")
133e5dd7070Spatrick        else:
134e5dd7070Spatrick            diagtool = getDiagtool(target)
135e5dd7070Spatrick            list = subprocess.check_output([diagtool, "list-warnings"]).rstrip();
136e5dd7070Spatrick            for line in list.splitlines(True):
137e5dd7070Spatrick                m = re.search(r' *(.*) .*\[\-W' + re.escape(args.id) + r'.*].*', line)
138e5dd7070Spatrick                # Make sure we only consider warnings.
139e5dd7070Spatrick                if m and m.group(1).startswith('warn_'):
140e5dd7070Spatrick                    bp = target.BreakpointCreateBySourceRegex(m.group(1), lldb.SBFileSpec())
141e5dd7070Spatrick                    bp.AddName("clang::Diagnostic")
142e5dd7070Spatrick    else:
143e5dd7070Spatrick        print('Adding callbacks.')
144e5dd7070Spatrick        bp = target.BreakpointCreateByName('DiagnosticsEngine::Report')
145e5dd7070Spatrick        bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint')
146e5dd7070Spatrick        bp.AddName("clang::Diagnostic")
147e5dd7070Spatrick
148e5dd7070Spatrick    count = target.GetNumBreakpoints() - numOfBreakpoints
149e5dd7070Spatrick    print('%i breakpoint%s added.' % (count, "s"[count==1:]))
150e5dd7070Spatrick
151e5dd7070Spatrick    return
152e5dd7070Spatrick
153e5dd7070Spatrickdef disable(exe_ctx):
154e5dd7070Spatrick    target = exe_ctx.GetTarget()
155e5dd7070Spatrick    # Remove all diag breakpoints.
156e5dd7070Spatrick    bkpts = lldb.SBBreakpointList(target)
157e5dd7070Spatrick    target.FindBreakpointsByName("clang::Diagnostic", bkpts)
158e5dd7070Spatrick    for i in range(bkpts.GetSize()):
159e5dd7070Spatrick        target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID())
160e5dd7070Spatrick
161e5dd7070Spatrick    return
162e5dd7070Spatrick
163e5dd7070Spatrickdef the_diag_command(debugger, command, exe_ctx, result, dict):
164e5dd7070Spatrick    # Use the Shell Lexer to properly parse up command options just like a
165e5dd7070Spatrick    # shell would
166e5dd7070Spatrick    command_args = shlex.split(command)
167e5dd7070Spatrick    parser = create_diag_options()
168e5dd7070Spatrick    try:
169e5dd7070Spatrick        args = parser.parse_args(command_args)
170e5dd7070Spatrick    except:
171e5dd7070Spatrick        return
172e5dd7070Spatrick
173e5dd7070Spatrick    if args.subcommands == 'enable':
174e5dd7070Spatrick        enable(exe_ctx, args)
175e5dd7070Spatrick    elif args.subcommands == 'disable':
176e5dd7070Spatrick        disable(exe_ctx)
177e5dd7070Spatrick    else:
178e5dd7070Spatrick        diagtool = getDiagtool(exe_ctx.GetTarget(), args.path)
179e5dd7070Spatrick        print('diagtool = %s' % diagtool)
180e5dd7070Spatrick
181e5dd7070Spatrick    return
182e5dd7070Spatrick
183e5dd7070Spatrickdef __lldb_init_module(debugger, dict):
184e5dd7070Spatrick    # This initializer is being run from LLDB in the embedded command interpreter
185e5dd7070Spatrick    # Make the options so we can generate the help text for the new LLDB
186e5dd7070Spatrick    # command line command prior to registering it with LLDB below
187e5dd7070Spatrick    parser = create_diag_options()
188e5dd7070Spatrick    the_diag_command.__doc__ = parser.format_help()
189e5dd7070Spatrick    # Add any commands contained in this module to LLDB
190e5dd7070Spatrick    debugger.HandleCommand(
191e5dd7070Spatrick        'command script add -f clangdiag.the_diag_command clangdiag')
192e5dd7070Spatrick    print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.')
193