LLDB mainline
embedded_interpreter.py
Go to the documentation of this file.
1import sys
2import builtins
3import code
4import lldb
5import traceback
6
7try:
8 import readline
9 import rlcompleter
10except ImportError:
11 have_readline = False
12except AttributeError:
13 # This exception gets hit by the rlcompleter when Linux is using
14 # the readline suppression import.
15 have_readline = False
16else:
17 have_readline = True
18
20 if hasattr(readline, "backend"):
21 return readline.backend == "editline"
22 return "libedit" in getattr(readline, "__doc__", "")
23
24 if is_libedit():
25 readline.parse_and_bind("bind ^I rl_complete")
26 else:
27 readline.parse_and_bind("tab: complete")
28
29# When running one line, we might place the string to run in this string
30# in case it would be hard to correctly escape a string's contents
31
32g_run_one_line_str = None
33
34
36 pass
37
38
40 line = line.rstrip()
41 if line in ("exit", "quit"):
42 raise LLDBExit
43 return line
44
45
46def readfunc(prompt):
47 line = input(prompt)
48 return strip_and_check_exit(line)
49
50
51def readfunc_stdio(prompt):
52 sys.stdout.write(prompt)
53 sys.stdout.flush()
54 line = sys.stdin.readline()
55 # Readline always includes a trailing newline character unless the file
56 # ends with an incomplete line. An empty line indicates EOF.
57 if not line:
58 raise EOFError
59 return strip_and_check_exit(line)
60
61
62def run_python_interpreter(local_dict):
63 # Pass in the dictionary, for continuity from one session to the next.
64 try:
65 banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()'."
66 input_func = readfunc_stdio
67
68 is_atty = sys.stdin.isatty()
69 if is_atty:
70 banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D."
71 input_func = readfunc
72
73 code.interact(banner=banner, readfunc=input_func, local=local_dict)
74 except LLDBExit:
75 pass
76 except SystemExit as e:
77 if e.code:
78 print("Script exited with code %s" % e.code)
79
80
82 """Introspect a scripting extension base class and return a JSON schema
83 describing its members. Used by `scripting extension generate` (via
84 `ScriptInterpreterPython::GetExtensionSchema`) to emit a skeleton
85 subclass with `# TODO: Implement` stubs for each method the base class
86 defines. Each method entry carries the signature, type hints,
87 docstring, and whether it's `@abstractmethod`, so the generator can
88 decide which methods to stub out (all of them with `-a`, otherwise
89 just the abstract ones). The schema also lists non-callable
90 attributes the base class exposes -- class-level values plus
91 class-body type annotations -- so the generator can advertise them in
92 the derived class' docstring. A `typing_imports` field enumerates
93 every `typing` generic (`Optional`, `Union`, ...) referenced by the
94 signatures or attribute types, so the generator can add the right
95 `from typing import` line without having to re-scan strings."""
96 import inspect, json, typing
97
98 used_typing = set()
99
100 def _record_typing(s):
101 # Anything from `typing.__all__` referenced as a generic
102 # (`Optional[...]`) gets picked up. Keying off the `[` avoids
103 # matching identifiers that merely embed the name.
104 if not s:
105 return
106 for name in typing.__all__:
107 if f"{name}[" in s:
108 used_typing.add(name)
109
110 def _fmt_type(t):
111 # `type(None)` stringifies as `NoneType`; render it as the
112 # literal `None` so the annotation stays valid Python.
113 if t is type(None):
114 return "None"
115 if isinstance(t, type):
116 if t.__module__ == "builtins":
117 return t.__name__
118 return f"{t.__module__}.{t.__name__}"
119 # `typing` generic aliases stringify with a leading `typing.`
120 # (`typing.Optional[list]`); the module prefix is noise for a
121 # docstring. `Union[int, str, None]` also renders its `None`
122 # component as `NoneType`, so fix that up too.
123 formatted = str(t).replace("typing.", "").replace("NoneType", "None")
124 _record_typing(formatted)
125 return formatted
126
127 def _build_signature(func):
128 # Reconstruct the signature from resolved type hints so forward
129 # refs (`"ScriptedFrame"`) come out as their real class -- what
130 # `inspect.signature(...)`'s own `str` would render as
131 # `ForwardRef('ScriptedFrame')`.
132 try:
133 hints = typing.get_type_hints(func)
134 except Exception:
135 hints = {}
136 sig = inspect.signature(func)
137 parts = []
138 for name, param in sig.parameters.items():
139 piece = name
140 if name in hints:
141 piece += f": {_fmt_type(hints[name])}"
142 if param.default is not inspect.Parameter.empty:
143 piece += f" = {param.default!r}"
144 parts.append(piece)
145 rendered = "(" + ", ".join(parts) + ")"
146 if "return" in hints:
147 rendered += f" -> {_fmt_type(hints['return'])}"
148 return rendered
149
150 def _get_function_metadata(func):
151 try:
152 hints = typing.get_type_hints(func)
153 type_hints = {k: str(v) for k, v in hints.items()}
154 except Exception:
155 type_hints = {}
156 return {
157 "signature": _build_signature(func),
158 "type_hints": type_hints,
159 "is_abstract": getattr(func, "__isabstractmethod__", False),
160 "doc": inspect.getdoc(func),
161 }
162
163 try:
164 class_hints = typing.get_type_hints(cls)
165 except Exception:
166 class_hints = {}
167
168 members = []
169 attributes = []
170 seen_attrs = set()
171 for name, member in inspect.getmembers(cls):
172 if inspect.isfunction(member):
173 members.append({"name": name, **_get_function_metadata(member)})
174 continue
175 if name.startswith("_"):
176 continue
177 entry = {"name": name}
178 if name in class_hints:
179 entry["type"] = _fmt_type(class_hints[name])
180 attributes.append(entry)
181 seen_attrs.add(name)
182
183 # Class-body type annotations without a runtime value
184 # (`target: SBTarget`) don't show up in `inspect.getmembers`, so pick
185 # them up from the hint map directly.
186 for name in class_hints:
187 if name.startswith("_") or name in seen_attrs:
188 continue
189 attributes.append({"name": name, "type": _fmt_type(class_hints[name])})
190 seen_attrs.add(name)
191
192 return json.dumps(
193 {
194 "class": cls.__name__,
195 "module": cls.__module__,
196 "doc": inspect.getdoc(cls),
197 "members": members,
198 "attributes": attributes,
199 "typing_imports": sorted(used_typing),
200 },
201 separators=(",", ":"),
202 )
203
204
205def run_one_line(local_dict, input_string):
206 global g_run_one_line_str
207 try:
208 input_string = strip_and_check_exit(input_string)
209 repl = code.InteractiveConsole(local_dict)
210 if input_string:
211 # A newline is appended to support one-line statements containing
212 # control flow. For example "if True: print(1)" silently does
213 # nothing, but works with a newline: "if True: print(1)\n".
214 input_string += "\n"
215 repl.runsource(input_string)
216 elif g_run_one_line_str:
217 repl.runsource(g_run_one_line_str)
218 except LLDBExit:
219 pass
220 except SystemExit as e:
221 if e.code:
222 print("Script exited with code %s" % e.code)
run_one_line(local_dict, input_string)