[Lldb-commits] [lldb] Add the ability to define a Python based command that uses CommandObjectParsed (PR #70734)

via lldb-commits lldb-commits at lists.llvm.org
Mon Nov 13 13:52:37 PST 2023


================
@@ -1255,6 +1258,676 @@ class CommandObjectScriptingObject : public CommandObjectRaw {
   CompletionType m_completion_type = eNoCompletion;
 };
 
+
+/// This command implements a lldb parsed scripted command.  The command
+/// provides a definition of the options and arguments, and a option value
+/// setting callback, and then the command's execution function gets passed
+/// just the parsed arguments.
+/// Note, implementing a command in Python using these base interfaces is a bit
+/// of a pain, but it is much easier to export this low level interface, and
+/// then make it nicer on the Python side, than to try to do that in a
+/// script language neutral way.
+/// So I've also added a base class in Python that provides a table-driven
+/// way of defining the options and arguments, which automatically fills the
+/// option values, making them available as properties in Python.
+/// 
+class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
+private: 
+  class CommandOptions : public Options {
+  public:
+    CommandOptions(CommandInterpreter &interpreter, 
+        StructuredData::GenericSP cmd_obj_sp) : m_interpreter(interpreter), 
+            m_cmd_obj_sp(cmd_obj_sp) {}
+
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+      ScriptInterpreter *scripter = 
+        m_interpreter.GetDebugger().GetScriptInterpreter();
+      if (!scripter) {
+        error.SetErrorString("No script interpreter for SetOptionValue.");
+        return error;
+      }
+      if (!m_cmd_obj_sp) {
+        error.SetErrorString("SetOptionValue called with empty cmd_obj.");
+        return error;
+      }
+      if (!m_options_definition_up) {
+        error.SetErrorString("SetOptionValue called before options definitions "
+                             "were created.");
+        return error;
+      }
+      // Pass the long option, since you aren't actually required to have a
+      // short_option, and for those options the index or short option character
+      // aren't meaningful on the python side.
+      const char * long_option = 
+        m_options_definition_up.get()[option_idx].long_option;
+      bool success = scripter->SetOptionValueForCommandObject(m_cmd_obj_sp, 
+        execution_context, long_option, option_arg);
+      if (!success)
+        error.SetErrorStringWithFormatv("Error setting option: {0} to {1}",
+                                        long_option, option_arg);
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      ScriptInterpreter *scripter = 
+        m_interpreter.GetDebugger().GetScriptInterpreter();
+      if (!scripter) {
+        return;
+      }
+      if (!m_cmd_obj_sp) {
+        return;
+      }
+      scripter->OptionParsingStartedForCommandObject(m_cmd_obj_sp);
+    };
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      if (!m_options_definition_up)
+        return {};
+      return llvm::ArrayRef(m_options_definition_up.get(), m_num_options);
+    }
+    
+    static bool ParseUsageMaskFromArray(StructuredData::ObjectSP obj_sp, 
+        size_t counter, uint32_t &usage_mask, Status &error) {
+      // If the usage entry is not provided, we use LLDB_OPT_SET_ALL.
+      // If the usage mask is a UINT, the option belongs to that group.
+      // If the usage mask is a vector of UINT's, the option belongs to all the
+      // groups listed.
+      // If a subelement of the vector is a vector of two ints, then the option
+      // belongs to the inclusive range from the first to the second element.
+      if (!obj_sp) {
+        usage_mask = LLDB_OPT_SET_ALL;
+        return true;
+      }
+      
+      usage_mask = 0;
+      
+      StructuredData::UnsignedInteger *uint_val = 
+          obj_sp->GetAsUnsignedInteger();
+      if (uint_val) {
+        // If this is an integer, then this specifies a single group:
+        uint32_t value = uint_val->GetValue();
+        if (value == 0) {
+          error.SetErrorStringWithFormatv(
+              "0 is not a valid group for option {0}", counter);
+          return false;
+        }
+        usage_mask = (1 << (value - 1));
+        return true;
+      }
+      // Otherwise it has to be an array:
+      StructuredData::Array *array_val = obj_sp->GetAsArray();
+      if (!array_val) {
+        error.SetErrorStringWithFormatv(
+            "required field is not a array for option {0}", counter);
+        return false;
+      }
+      // This is the array ForEach for accumulating a group usage mask from
+      // an array of string descriptions of groups.
+      auto groups_accumulator 
+          = [counter, &usage_mask, &error] 
+            (StructuredData::Object *obj) -> bool {
+        StructuredData::UnsignedInteger *int_val = obj->GetAsUnsignedInteger();
+        if (int_val) {
+          uint32_t value = int_val->GetValue();
+          if (value == 0) {
+            error.SetErrorStringWithFormatv(
+                "0 is not a valid group for element {0}", counter);
+            return false;
+          }
+          usage_mask |= (1 << (value - 1));
+          return true;
+        }
+        StructuredData::Array *arr_val = obj->GetAsArray();
+        if (!arr_val) {
+          error.SetErrorStringWithFormatv(
+              "Group element not an int or array of integers for element {0}", 
+              counter);
+          return false; 
+        }
+        size_t num_range_elem = arr_val->GetSize();
+        if (num_range_elem != 2) {
+          error.SetErrorStringWithFormatv(
+              "Subranges of a group not a start and a stop for element {0}", 
+              counter);
+          return false; 
+        }
+        int_val = arr_val->GetItemAtIndex(0)->GetAsUnsignedInteger();
+        if (!int_val) {
+          error.SetErrorStringWithFormatv("Start element of a subrange of a "
+              "group not unsigned int for element {0}", counter);
+          return false; 
+        }
+        uint32_t start = int_val->GetValue();
+        int_val = arr_val->GetItemAtIndex(1)->GetAsUnsignedInteger();
+        if (!int_val) {
+          error.SetErrorStringWithFormatv("End element of a subrange of a group"
+              " not unsigned int for element {0}", counter);
+          return false; 
+        }
+        uint32_t end = int_val->GetValue();
+        if (start == 0 || end == 0 || start > end) {
+          error.SetErrorStringWithFormatv("Invalid subrange of a group: {0} - "
+              "{1} for element {2}", start, end, counter);
+          return false;
+        }
+        for (uint32_t i = start; i <= end; i++) {
+          usage_mask |= (1 << (i - 1));
+        }
+        return true;
+      };
+      return array_val->ForEach(groups_accumulator);
+    }
+    
+    
+    Status SetOptionsFromArray(StructuredData::Array &options, Status &error) {
+      m_num_options = options.GetSize();
+      m_options_definition_up.reset(new OptionDefinition[m_num_options]);
+      // We need to hand out pointers to contents of these vectors; we reserve
+      // as much as we'll need up front so they don't get freed on resize...
+      m_usage_container.reserve(m_num_options);
+      m_enum_storage.reserve(m_num_options);
+      m_enum_vector.reserve(m_num_options);
+      
+      size_t counter = 0;
+      size_t short_opt_counter = 0;
+      // This is the Array::ForEach function for adding option elements:
+      auto add_element = [this, &error, &counter, &short_opt_counter] 
+          (StructuredData::Object *object) -> bool {
+        StructuredData::Dictionary *opt_dict = object->GetAsDictionary();
+        if (!opt_dict) {
+          error.SetErrorString("Object in options array is not a dictionary");
+          return false;
+        }
+        OptionDefinition &option_def = m_options_definition_up.get()[counter];
+        
+        // We aren't exposing the validator yet, set it to null
+        option_def.validator = nullptr;
+        // We don't require usage masks, so set it to one group by default:
+        option_def.usage_mask = 1;
+        
+        // Now set the fields of the OptionDefinition Array from the dictionary:
+        //
+        // Note that I don't check for unknown fields in the option dictionaries
+        // so a scriptor can add extra elements that are helpful when they go to
+        // do "set_option_value"
+        
+        // Usage Mask:
+        StructuredData::ObjectSP obj_sp = opt_dict->GetValueForKey("groups");
+        if (obj_sp) {
+          ParseUsageMaskFromArray(obj_sp, counter, option_def.usage_mask, 
+              error);
+          if (error.Fail())
+            return false;
+        }
+
+        // Required:
+        option_def.required = false;
+        obj_sp = opt_dict->GetValueForKey("required");
+        if (obj_sp) {
+          StructuredData::Boolean *boolean_val = obj_sp->GetAsBoolean();
+          if (!boolean_val) {
+            error.SetErrorStringWithFormatv("'required' field is not a boolean "
+                "for option {0}", counter);
+            return false;
+          } 
+          option_def.required = boolean_val->GetValue();      
+        }
+        
+        // Short Option:
+        int short_option;
+        obj_sp = opt_dict->GetValueForKey("short_option");
+        if (obj_sp) {
+          // The value is a string, so pull the 
+          llvm::StringRef short_str = obj_sp->GetStringValue();
+          if (short_str.empty()) {
+            error.SetErrorStringWithFormatv("short_option field empty for "
+                "option {0}", counter);
+            return false;
+          } else if (short_str.size() != 1) {
+            error.SetErrorStringWithFormatv("short_option field has extra "
+                "characters for option {0}", counter);
+            return false;
+          }
+          short_option = (int) short_str[0];
+        } else {
+          // If the short option is not provided, then we need a unique value 
+          // less than the lowest printable ASCII character.
+          short_option = short_opt_counter++;
+        }
+        option_def.short_option = short_option;
+        
+        // Long Option:
+        std::string long_option;
+        obj_sp = opt_dict->GetValueForKey("long_option");
+        if (!obj_sp) {
+          error.SetErrorStringWithFormatv("required long_option missing from "
+          "option {0}", counter);
+          return false;
+        }
+        llvm::StringRef long_stref = obj_sp->GetStringValue();
+        if (long_stref.empty()) {
+          error.SetErrorStringWithFormatv("empty long_option for option {0}", 
+              counter);
+          return false;
+        }
+        auto inserted = g_string_storer.insert(long_stref.str());
+        option_def.long_option = ((*(inserted.first)).data());
+        
+        // Value Type:
+        obj_sp = opt_dict->GetValueForKey("value_type");
+        if (obj_sp) {
+          StructuredData::UnsignedInteger *uint_val 
+              = obj_sp->GetAsUnsignedInteger();
+          if (!uint_val) {
+            error.SetErrorStringWithFormatv("Value type must be an unsigned "
+                "integer");
+            return false;
+          }
+          uint64_t val_type = uint_val->GetValue();
+          if (val_type >= eArgTypeLastArg) {
+            error.SetErrorStringWithFormatv("Value type {0} beyond the "
+                "CommandArgumentType bounds", val_type);
+            return false;
+          }
+          option_def.argument_type = (CommandArgumentType) val_type;
+          option_def.option_has_arg = true;
+        } else {
+          option_def.argument_type = eArgTypeNone;
+          option_def.option_has_arg = false;
+        }
+        
+        // Completion Type:
+        obj_sp = opt_dict->GetValueForKey("completion_type");
+        if (obj_sp) {
+          StructuredData::UnsignedInteger *uint_val = obj_sp->GetAsUnsignedInteger();
+          if (!uint_val) {
+            error.SetErrorStringWithFormatv("Completion type must be an "
+                "unsigned integer for option {0}", counter);
+            return false;
+          }
+          uint64_t completion_type = uint_val->GetValue();
+          if (completion_type > eCustomCompletion) {
+            error.SetErrorStringWithFormatv("Completion type for option {0} "
+                "beyond the CompletionType bounds", completion_type);
+            return false;
+          }
+          option_def.completion_type = (CommandArgumentType) completion_type;
+        } else
+          option_def.completion_type = eNoCompletion;
+        
+        // Usage Text:
+        std::string usage_text;
+        obj_sp = opt_dict->GetValueForKey("usage");
+        if (!obj_sp) {
+          error.SetErrorStringWithFormatv("required usage missing from option "
+              "{0}", counter);
+          return false;
+        }
+        long_stref = obj_sp->GetStringValue();
+        if (long_stref.empty()) {
+          error.SetErrorStringWithFormatv("empty usage text for option {0}", 
+              counter);
+          return false;
+        }
+        m_usage_container[counter] = long_stref.str().c_str();
+        option_def.usage_text = m_usage_container[counter].data();
+
+        // Enum Values:
+        
+        obj_sp = opt_dict->GetValueForKey("enum_values");
+        if (obj_sp) {
+          StructuredData::Array *array = obj_sp->GetAsArray();
+          if (!array) {
+            error.SetErrorStringWithFormatv("enum values must be an array for "
+                "option {0}", counter);
+            return false;
+          }
+          size_t num_elem = array->GetSize();
+          size_t enum_ctr = 0;
+          m_enum_storage[counter] = std::vector<EnumValueStorer>(num_elem);
+          std::vector<EnumValueStorer> &curr_elem = m_enum_storage[counter];
+          
+          // This is the Array::ForEach function for adding enum elements:
+          // Since there are only two fields to specify the enum, use a simple
+          // two element array with value first, usage second.
+          // counter is only used for reporting so I pass it by value here.
+          auto add_enum = [&enum_ctr, &curr_elem, counter, &error] 
+              (StructuredData::Object *object) -> bool {
+            StructuredData::Array *enum_arr = object->GetAsArray();
+            if (!enum_arr) {
+              error.SetErrorStringWithFormatv("Enum values for option {0} not "
+                  "an array", counter);
+              return false;
+            }
+            size_t num_enum_elements = enum_arr->GetSize();
+            if (num_enum_elements != 2) {
+              error.SetErrorStringWithFormatv("Wrong number of elements: {0} "
+                  "for enum {1} in option {2}",
+                  num_enum_elements, enum_ctr, counter);
+              return false;
+            }
+            // Enum Value:
+            StructuredData::ObjectSP obj_sp = enum_arr->GetItemAtIndex(0);
+            llvm::StringRef val_stref = obj_sp->GetStringValue();
+            std::string value_cstr_str = val_stref.str().c_str();
+            
+            // Enum Usage:
+            obj_sp = enum_arr->GetItemAtIndex(1);
+            if (!obj_sp) {
+              error.SetErrorStringWithFormatv("No usage for enum {0} in option "
+                  "{1}",  enum_ctr, counter);
+              return false;
+            }
+            llvm::StringRef usage_stref = obj_sp->GetStringValue();
+            std::string usage_cstr_str = usage_stref.str().c_str();
+            curr_elem[enum_ctr] = EnumValueStorer(value_cstr_str, 
+                usage_cstr_str, enum_ctr);
+            
+            enum_ctr++;
+            return true;
+          }; // end of add_enum
+          
+          array->ForEach(add_enum);
+          if (!error.Success())
+            return false;
+          // We have to have a vector of elements to set in the options, make 
+          // that here:
+          for (auto &elem : curr_elem)
+            m_enum_vector[counter].emplace_back(elem.element);
+
+          option_def.enum_values = llvm::ArrayRef(m_enum_vector[counter]);
+        }
+        counter++;
+        return true;
+      }; // end of add_element
+      
+      options.ForEach(add_element);
+      return error;
+    }
+    
+  private:
+    struct EnumValueStorer {
+      EnumValueStorer() {
+        element.string_value = "value not set";
+        element.usage = "usage not set";
+        element.value = 0;
+      }
+      
+      EnumValueStorer(std::string &in_str_val, std::string &in_usage, 
+          size_t in_value) : value(in_str_val), usage(in_usage) {
+        SetElement(in_value);
+      }
+      
+      EnumValueStorer(const EnumValueStorer &in) : value(in.value), 
+          usage(in.usage) {
+        SetElement(in.element.value);
+      }
+      
+      EnumValueStorer &operator=(const EnumValueStorer &in) {
+        value = in.value;
+        usage = in.usage;
+        SetElement(in.element.value);
+        return *this;
+      }
+      
+      void SetElement(size_t in_value) {
+        element.value = in_value;
+        element.string_value = value.data();
+        element.usage = usage.data(); 
+      }
+      
+      std::string value;
+      std::string usage;
+      OptionEnumValueElement element;
+    };
+    // We have to provide char * values for the long option, usage and enum
+    // values, that's what the option definitions hold.
+    // The long option strings are quite likely to be reused in other added
+    // commands, so those are stored in a global set: g_string_storer.
+    // But the usages are much less likely to be reused, so those are stored in
+    // a vector in the command instance.  It gets resized to the correct size
+    // and then filled with null-terminated strings in the std::string, so the 
+    // are valid C-strings that won't move around.
+    // The enum values and descriptions are treated similarly - these aren't
+    // all that common so it's not worth the effort to dedup them.  
+    size_t m_num_options = 0;
+    std::unique_ptr<OptionDefinition> m_options_definition_up;
+    std::vector<std::vector<EnumValueStorer>> m_enum_storage;
+    std::vector<std::vector<OptionEnumValueElement>> m_enum_vector;
+    std::vector<std::string> m_usage_container;
+    CommandInterpreter &m_interpreter;
+    StructuredData::GenericSP m_cmd_obj_sp;
+    static std::unordered_set<std::string> g_string_storer;
+  };
+
+public:
+  CommandObjectScriptingObjectParsed(CommandInterpreter &interpreter,
+                               std::string name,
+                               StructuredData::GenericSP cmd_obj_sp,
+                               ScriptedCommandSynchronicity synch)
+      : CommandObjectParsed(interpreter, name.c_str()), 
+        m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), 
+        m_options(interpreter, cmd_obj_sp), m_fetched_help_short(false), 
+        m_fetched_help_long(false) {
----------------
jimingham wrote:

Okay

https://github.com/llvm/llvm-project/pull/70734


More information about the lldb-commits mailing list