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