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