================
@@ -265,6 +267,349 @@ StructuredData::DictionarySP
ScriptInterpreterPython::GetInterpreterInfo() {
return info_json.CreateStructuredDictionary();
}
+llvm::Expected<std::string> ScriptInterpreterPython::ExtensionToImportPath(
+ lldb::ScriptedExtension extension) {
+ switch (extension) {
+ case eScriptedExtensionOperatingSystem:
+ return "lldb.plugins.operating_system";
+ case eScriptedExtensionScriptedPlatform:
+ return "lldb.plugins.scripted_platform";
+ case eScriptedExtensionScriptedProcess:
+ return "lldb.plugins.scripted_process";
+ case eScriptedExtensionScriptedHook:
+ return "lldb.plugins.scripted_hook";
+ case eScriptedExtensionScriptedBreakpointResolver:
+ return "lldb.plugins.scripted_breakpoint";
+ case eScriptedExtensionScriptedThreadPlan:
+ return "lldb.plugins.scripted_thread_plan";
+ case eScriptedExtensionScriptedFrameProvider:
+ return "lldb.plugins.scripted_frame_provider";
+ case eScriptedExtensionScriptedThread:
+ case eScriptedExtensionScriptedFrame:
+ return "lldb.plugins.scripted_process";
+ case eScriptedExtensionInvalid:
+ return llvm::createStringError("invalid extension name");
+ }
+ return llvm::createStringError("invalid extension name");
+}
+
+llvm::Expected<StructuredData::ObjectSP>
+ScriptInterpreterPython::GetExtensionSchema(
+ llvm::SmallVector<llvm::StringRef> &extension_path) {
+ lldb::ScriptedExtension extension =
+ ScriptInterpreter::StringToExtension(extension_path.back());
+ auto import_path_or_err = ExtensionToImportPath(extension);
+ if (!import_path_or_err)
+ return import_path_or_err.takeError();
+
+ StreamString command_stream;
+ // __import__(path, fromlist=['']) imports the submodule and returns it
+ // directly (rather than the top-level package), as a single expression --
+ // this keeps the whole call eval-able in one line while guaranteeing the
+ // module is imported first; referencing "<import_path>.<ClassName>"
+ // directly would only work if something else had already imported
+ // <import_path> as a side effect.
+ command_stream.Printf("lldb.embedded_interpreter.generate_extension_schema("
+ "__import__('%s', fromlist=['']).%s)",
+ import_path_or_err->c_str(),
+
ScriptInterpreter::ExtensionToString(extension).data());
+
+ // Use eScriptReturnTypeOpaqueObject: it transfers a real owned reference
+ // we can safely extract the string from. eScriptReturnTypeCharStrOrNone
+ // instead hands back a pointer to a temporary Python object's buffer
+ // that gets destroyed (and, for a freshly created string like this one,
+ // deallocated) as soon as ExecuteOneLineWithReturn returns -- reading
+ // it afterwards is a use-after-free.
+ void *result_obj = nullptr;
+ if (!ExecuteOneLineWithReturn(
+ command_stream.GetData(),
+ ScriptInterpreter::eScriptReturnTypeOpaqueObject, &result_obj,
+ ExecuteScriptOptions().SetEnableIO(false)))
+ return llvm::createStringError("invalid extension schema format");
+
+ // ExecuteOneLineWithReturn releases the GIL before returning, so touching
+ // the returned object (Str() below can execute arbitrary Python code) must
+ // re-acquire it first. py_result is scoped so its destructor (a DECREF)
+ // also runs before the GIL is released below, not after.
+ std::string schema_str;
+ {
+ PyGILState_STATE gil_state = PyGILState_Ensure();
+ {
+ PythonObject py_result(PyRefType::Owned,
+ static_cast<PyObject *>(result_obj));
+ if (py_result.IsAllocated() && py_result.get() != Py_None)
+ schema_str = py_result.Str().GetString().str();
+ }
+ PyGILState_Release(gil_state);
+ }
+
+ if (schema_str.empty())
+ return llvm::createStringError("empty extension schema");
+ return StructuredData::ParseJSON(schema_str);
+}
+
+llvm::Error ScriptInterpreterPython::ParseExtensionSchema(
+ Stream &s, llvm::StringRef output_script_prefix,
+ llvm::SmallVector<llvm::StringRef> &extension_path,
+ bool generate_non_abstract_methods, std::set<std::string> &typing_imports)
{
+ auto schema_or_err = GetExtensionSchema(extension_path);
+ if (!schema_or_err)
+ return schema_or_err.takeError();
+
+ StructuredData::ObjectSP schema = *schema_or_err;
+ StructuredData::Dictionary *dict = schema->GetAsDictionary();
+
+ // Merge each class' typing imports into the caller-owned set so the
+ // final `from typing import ...` line covers every class we emit.
+ StructuredData::Array *schema_typing;
+ if (dict->GetValueForKeyAsArray("typing_imports", schema_typing))
+ schema_typing->ForEach([&](StructuredData::Object *entry) {
+ if (auto *str = entry->GetAsString())
+ typing_imports.insert(str->GetValue().str());
+ return true;
+ });
+
+ llvm::StringRef base_class, import_path;
+ if (!dict->GetValueForKeyAsString("class", base_class))
+ return llvm::createStringError(
+ llvm::formatv("extension schema dictionary is missing 'class' key")
+ .str());
+ if (!dict->GetValueForKeyAsString("module", import_path))
+ return llvm::createStringError(
+ llvm::formatv("extension schema dictionary is missing 'module' key")
+ .str());
+
+ // imports
+ s.Printf("from %s import %s\n", import_path.data(), base_class.data());
+ s.EOL();
+
+ // class definition
+ s.Printf("class %s%s(%s):\n", output_script_prefix.data(), base_class.data(),
+ base_class.data());
+ s.IndentMore();
+
+ // Class docstring: list the non-callable members the base class exposes
+ // so the user sees what's available without having to hop back to the
+ // base class definition.
+ bool has_body = false;
+ StructuredData::Array *attributes;
+ if (dict->GetValueForKeyAsArray("attributes", attributes) &&
+ attributes->GetSize()) {
+ s.Indent();
+ s.PutCString("\"\"\"\n");
+ s.Indent();
+ s.Printf("Attributes inherited from %s:\n", base_class.data());
+ for (size_t i = 0; i < attributes->GetSize(); i++) {
+ auto maybe_dict = attributes->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ continue;
+ StructuredData::Dictionary *attr_dict = *maybe_dict;
+ llvm::StringRef attr_name;
+ if (!attr_dict->GetValueForKeyAsString("name", attr_name))
+ continue;
+ llvm::StringRef attr_type;
+ bool has_type = attr_dict->GetValueForKeyAsString("type", attr_type);
+ s.Indent();
+ s.Printf("- %s", attr_name.data());
+ if (has_type)
+ s.Printf(": %s", attr_type.data());
+ s.EOL();
+ }
+ s.Indent();
+ s.PutCString("\"\"\"\n\n");
+ has_body = true;
+ }
+
+ // members
+ StructuredData::Array *members;
+ if (!dict->GetValueForKeyAsArray("members", members))
+ return llvm::createStringError("missing 'members' key in extension
schema");
+
+ // If the base class doesn't mark anything `@abstractmethod`, the filter
+ // "only stub abstract methods" would leave the derived class empty --
+ // which isn't a useful starting point. Fall back to emitting every
+ // method in that case so the user has actual code to edit.
+ bool any_abstract = false;
+ for (size_t i = 0; i < members->GetSize(); i++) {
+ auto maybe_dict = members->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ continue;
+ bool is_abstract = false;
+ if ((*maybe_dict)->GetValueForKeyAsBoolean("is_abstract", is_abstract) &&
+ is_abstract) {
+ any_abstract = true;
+ break;
+ }
+ }
+ bool emit_all_methods = generate_non_abstract_methods || !any_abstract;
+
+ for (size_t i = 0; i < members->GetSize(); i++) {
+ auto maybe_dict = members->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ return llvm::createStringError(
+ llvm::formatv(
+ "member at index {0} in extension schema isn't a dictionary")
+ .str());
+
+ StructuredData::Dictionary *member_dict = *maybe_dict;
+ llvm::StringRef symbol, args;
+ if (!member_dict->GetValueForKeyAsString("name", symbol))
+ return llvm::createStringError(
+ llvm::formatv(
+ "member at index {0} in extension schema is missing 'name' key")
+ .str());
+ if (!member_dict->GetValueForKeyAsString("signature", args))
+ return llvm::createStringError(
+ llvm::formatv("member at index {0} in extension schema is missing "
+ "'signature' key")
+ .str());
+
+ bool is_abstract = false;
+ bool has_is_abstract =
+ member_dict->GetValueForKeyAsBoolean("is_abstract", is_abstract);
+ if (!emit_all_methods)
+ if (!has_is_abstract || !is_abstract)
+ continue;
+
+ s.Indent();
+ s.Printf("def %s%s:\n", symbol.data(), args.data());
+
+ s.IndentMore();
+ llvm::StringRef documentation;
+ if (member_dict->GetValueForKeyAsString("doc", documentation)) {
+ s.Indent();
+ s.PutCString("\"\"\"\n");
+
+ llvm::SmallVector<llvm::StringRef> lines;
+ documentation.split(lines, "\n");
+
+ for (llvm::StringRef line : lines) {
+ s.Indent();
+ s.PutCString(line);
+ s.EOL();
+ }
+
+ s.Indent();
+ s.PutCString("\"\"\"");
+ s.EOL();
+ }
+
+ if (symbol == "__init__") {
+ // The base class' constructor sets up attributes (e.g. self.target,
+ // self.process) that the inherited, non-overridden methods rely on.
+ // Forward the same arguments so that state is still initialized.
+ // Splitting the param list on `,` requires bracket-depth awareness
+ // because annotations like `Union[X, Y]` also contain commas.
+ llvm::StringRef params = args.trim("()");
+ std::vector<std::string> forwarded_args;
+ int depth = 0;
+ size_t start = 0;
+ auto flush = [&](size_t end) {
+ llvm::StringRef param = params.slice(start, end);
+ param = param.split(':').first.split('=').first.trim();
+ if (!param.empty() && param != "self")
+ forwarded_args.push_back(param.str());
+ };
+ for (size_t i = 0; i < params.size(); ++i) {
+ char c = params[i];
+ if (c == '[' || c == '(' || c == '{')
+ ++depth;
+ else if (c == ']' || c == ')' || c == '}')
+ --depth;
+ else if (c == ',' && depth == 0) {
+ flush(i);
+ start = i + 1;
+ }
+ }
+ flush(params.size());
+ s.Indent();
+ s.Printf("super().__init__(%s)\n",
+ llvm::join(forwarded_args, ", ").c_str());
+ }
+
+ s.Indent();
+ s.PutCString("# TODO: Implement\n");
+ s.Indent();
+ s.PutCString("pass\n\n");
+ s.IndentLess();
+ has_body = true;
+ }
+
+ // A class with no body is a Python syntax error, so emit `pass` when the
+ // base class has nothing to stub out (no methods and no attributes to
+ // document).
+ if (!has_body) {
+ s.Indent();
+ s.PutCString("pass\n");
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<FileSpec> ScriptInterpreterPython::GenerateExtensionTemplate(
+ const std::string name,
+ std::vector<std::pair<llvm::StringRef, llvm::SmallVector<llvm::StringRef>>>
+ &extensions,
+ bool generate_non_abstract_methods, std::string output_file) {
+ // `ParseExtensionSchema` accumulates every `typing` generic it sees
+ // (`Optional`, `Union`, `List`, ...) into this set so we can emit a
+ // targeted `from typing import ...` line only for what's actually
+ // referenced. The Python schema does the detection so we don't have
+ // to re-scan strings here.
+ std::set<std::string> typing_imports;
+ StreamString bodies;
+ for (auto extension_pair : extensions) {
+ if (llvm::Error err =
+ ParseExtensionSchema(bodies, name, extension_pair.second,
+ generate_non_abstract_methods,
typing_imports))
+ return std::move(err);
+ bodies.PutCString("\n\n");
+ }
+
+ StreamString generated_file_stream;
+ generated_file_stream.PutCString("import lldb\n");
+ if (!typing_imports.empty()) {
+ std::vector<std::string> sorted_imports(typing_imports.begin(),
+ typing_imports.end());
+ generated_file_stream.Format("from typing import {0}\n",
+ llvm::join(sorted_imports, ", "));
+ }
+ generated_file_stream.PutCString("\n");
+ generated_file_stream.PutCString(bodies.GetString());
+
+ FileSpec save_location;
+ if (output_file.empty()) {
+ const std::string file_name =
+ "lldb_" + llvm::StringRef(name).lower() + "_extension.py";
----------------
JDevlieghere wrote:
What happens if I pass `/` as the name? Should this be sanitized?
https://github.com/llvm/llvm-project/pull/209647
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits