xref: /aosp_15_r20/external/pigweed/pw_rpc/fuzz/argparse.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_rpc/fuzz/argparse.h"
16 
17 #include <cctype>
18 #include <cstring>
19 
20 #include "pw_assert/check.h"
21 #include "pw_log/log.h"
22 #include "pw_string/string_builder.h"
23 
24 namespace pw::rpc::fuzz {
25 namespace {
26 
27 // Visitor to `ArgVariant` used by `ParseArgs` below.
28 struct ParseVisitor {
29   std::string_view arg0;
30   std::string_view arg1;
31 
32   template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ParseVisitor33   ParseStatus operator()(Parser& parser) {
34     return parser.Parse(arg0, arg1);
35   }
36 };
37 
38 // Visitor to `ArgVariant` used by `GetArg` below.
39 struct ValueVisitor {
40   std::string_view name;
41 
42   template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ValueVisitor43   std::optional<ArgVariant> operator()(Parser& parser) {
44     std::optional<ArgVariant> result;
45     if (parser.short_name() == name || parser.long_name() == name) {
46       result.emplace(parser.value());
47     }
48     return result;
49   }
50 };
51 
52 // Visitor to `ArgVariant` used by `PrintUsage` below.
53 const size_t kMaxUsageLen = 256;
54 struct UsageVisitor {
55   StringBuffer<kMaxUsageLen>* buffer;
56 
operator ()pw::rpc::fuzz::__anon5e30d65c0111::UsageVisitor57   void operator()(const BoolParser& parser) const {
58     auto short_name = parser.short_name();
59     auto long_name = parser.long_name();
60     *buffer << " [" << short_name << "|--[no-]" << long_name.substr(2) << "]";
61   }
62 
63   template <typename T>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::UsageVisitor64   void operator()(const UnsignedParser<T>& parser) const {
65     auto short_name = parser.short_name();
66     auto long_name = parser.long_name();
67     *buffer << " ";
68     if (!parser.positional()) {
69       *buffer << "[";
70       if (!short_name.empty()) {
71         *buffer << short_name << "|";
72       }
73       *buffer << long_name << " ";
74     }
75     for (const auto& c : long_name) {
76       *buffer << static_cast<char>(toupper(c));
77     }
78     if (!parser.positional()) {
79       *buffer << "]";
80     }
81   }
82 };
83 
84 // Visitor to `ArgVariant` used by `ResetArg` below.
85 struct ResetVisitor {
86   std::string_view name;
87 
88   template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ResetVisitor89   bool operator()(Parser& parser) {
90     if (parser.short_name() != name && parser.long_name() != name) {
91       return false;
92     }
93     parser.Reset();
94     return true;
95   }
96 };
97 
98 }  // namespace
99 
ArgParserBase(std::string_view name)100 ArgParserBase::ArgParserBase(std::string_view name) : long_name_(name) {
101   PW_CHECK(!name.empty());
102   PW_CHECK(name != "--");
103   positional_ =
104       name[0] != '-' || (name.size() > 2 && name.substr(0, 2) != "--");
105 }
106 
ArgParserBase(std::string_view shortopt,std::string_view longopt)107 ArgParserBase::ArgParserBase(std::string_view shortopt,
108                              std::string_view longopt)
109     : short_name_(shortopt), long_name_(longopt) {
110   PW_CHECK(shortopt.size() == 2);
111   PW_CHECK(shortopt[0] == '-');
112   PW_CHECK(shortopt != "--");
113   PW_CHECK(longopt.size() > 2);
114   PW_CHECK(longopt.substr(0, 2) == "--");
115   positional_ = false;
116 }
117 
Match(std::string_view arg)118 bool ArgParserBase::Match(std::string_view arg) {
119   if (arg.empty()) {
120     return false;
121   }
122   if (!positional_) {
123     return arg == short_name_ || arg == long_name_;
124   }
125   if (!std::holds_alternative<std::monostate>(value_)) {
126     return false;
127   }
128   if ((arg.size() == 2 && arg[0] == '-') ||
129       (arg.size() > 2 && arg.substr(0, 2) == "--")) {
130     PW_LOG_WARN("Argument parsed for '%s' appears to be a flag: '%s'",
131                 long_name_.data(),
132                 arg.data());
133   }
134   return true;
135 }
136 
GetValue() const137 const ArgVariant& ArgParserBase::GetValue() const {
138   return std::holds_alternative<std::monostate>(value_) ? initial_ : value_;
139 }
140 
BoolParser(std::string_view name)141 BoolParser::BoolParser(std::string_view name) : ArgParserBase(name) {}
BoolParser(std::string_view shortopt,std::string_view longopt)142 BoolParser::BoolParser(std::string_view shortopt, std::string_view longopt)
143     : ArgParserBase(shortopt, longopt) {}
144 
set_default(bool value)145 BoolParser& BoolParser::set_default(bool value) {
146   set_initial(value);
147   return *this;
148 }
149 
Parse(std::string_view arg0,std::string_view arg1)150 ParseStatus BoolParser::Parse(std::string_view arg0,
151                               [[maybe_unused]] std::string_view arg1) {
152   if (Match(arg0)) {
153     set_value(true);
154     return kParsedOne;
155   }
156   if (arg0.size() > 5 && arg0.substr(0, 5) == "--no-" &&
157       arg0.substr(5) == long_name().substr(2)) {
158     set_value(false);
159     return kParsedOne;
160   }
161   return kParseMismatch;
162 }
163 
UnsignedParserBase(std::string_view name)164 UnsignedParserBase::UnsignedParserBase(std::string_view name)
165     : ArgParserBase(name) {}
UnsignedParserBase(std::string_view shortopt,std::string_view longopt)166 UnsignedParserBase::UnsignedParserBase(std::string_view shortopt,
167                                        std::string_view longopt)
168     : ArgParserBase(shortopt, longopt) {}
169 
Parse(std::string_view arg0,std::string_view arg1,uint64_t max)170 ParseStatus UnsignedParserBase::Parse(std::string_view arg0,
171                                       std::string_view arg1,
172                                       uint64_t max) {
173   auto result = kParsedOne;
174   if (!Match(arg0)) {
175     return kParseMismatch;
176   }
177   if (!positional()) {
178     if (arg1.empty()) {
179       PW_LOG_ERROR("Missing value for flag '%s'", arg0.data());
180       return kParseFailure;
181     }
182     arg0 = arg1;
183     result = kParsedTwo;
184   }
185   char* endptr;
186   unsigned long long value = strtoull(arg0.data(), &endptr, 0);
187   if (*endptr) {
188     PW_LOG_ERROR("Failed to parse number from '%s'", arg0.data());
189     return kParseFailure;
190   }
191   if (value > max) {
192     PW_LOG_ERROR("Parsed value is too large: %llu", value);
193     return kParseFailure;
194   }
195   set_value(static_cast<uint64_t>(value));
196   return result;
197 }
198 
ParseArgs(Vector<ArgParserVariant> & parsers,int argc,char ** argv)199 Status ParseArgs(Vector<ArgParserVariant>& parsers, int argc, char** argv) {
200   for (int i = 1; i < argc; ++i) {
201     auto arg0 = std::string_view(argv[i]);
202     auto arg1 =
203         i == (argc - 1) ? std::string_view() : std::string_view(argv[i + 1]);
204     bool parsed = false;
205     for (auto& parser : parsers) {
206       switch (std::visit(ParseVisitor{.arg0 = arg0, .arg1 = arg1}, parser)) {
207         case kParsedOne:
208           break;
209         case kParsedTwo:
210           ++i;
211           break;
212         case kParseMismatch:
213           continue;
214         case kParseFailure:
215           PW_LOG_ERROR("Failed to parse '%s'", arg0.data());
216           return Status::InvalidArgument();
217       }
218       parsed = true;
219       break;
220     }
221     if (!parsed) {
222       PW_LOG_ERROR("Unrecognized argument: '%s'", arg0.data());
223       return Status::InvalidArgument();
224     }
225   }
226   return OkStatus();
227 }
228 
PrintUsage(const Vector<ArgParserVariant> & parsers,std::string_view argv0)229 void PrintUsage(const Vector<ArgParserVariant>& parsers,
230                 std::string_view argv0) {
231   StringBuffer<kMaxUsageLen> buffer;
232   buffer << "usage: " << argv0;
233   for (auto& parser : parsers) {
234     std::visit(UsageVisitor{.buffer = &buffer}, parser);
235   }
236   PW_LOG_INFO("%s", buffer.c_str());
237 }
238 
GetArg(const Vector<ArgParserVariant> & parsers,std::string_view name)239 std::optional<ArgVariant> GetArg(const Vector<ArgParserVariant>& parsers,
240                                  std::string_view name) {
241   for (auto& parser : parsers) {
242     if (auto result = std::visit(ValueVisitor{.name = name}, parser);
243         result.has_value()) {
244       return result;
245     }
246   }
247   return std::optional<ArgVariant>();
248 }
249 
ResetArg(Vector<ArgParserVariant> & parsers,std::string_view name)250 Status ResetArg(Vector<ArgParserVariant>& parsers, std::string_view name) {
251   for (auto& parser : parsers) {
252     if (std::visit(ResetVisitor{.name = name}, parser)) {
253       return OkStatus();
254     }
255   }
256   return Status::InvalidArgument();
257 }
258 
259 }  // namespace pw::rpc::fuzz
260