1 | //===-- OptionValueProperties.cpp -----------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "lldb/Interpreter/OptionValueProperties.h" |
10 | |
11 | #include "lldb/Utility/Flags.h" |
12 | |
13 | #include "lldb/Core/UserSettingsController.h" |
14 | #include "lldb/Interpreter/OptionValues.h" |
15 | #include "lldb/Interpreter/Property.h" |
16 | #include "lldb/Utility/Args.h" |
17 | #include "lldb/Utility/Stream.h" |
18 | #include "lldb/Utility/StringList.h" |
19 | |
20 | using namespace lldb; |
21 | using namespace lldb_private; |
22 | |
23 | OptionValueProperties::OptionValueProperties(llvm::StringRef name) |
24 | : m_name(name.str()) {} |
25 | |
26 | void OptionValueProperties::Initialize(const PropertyDefinitions &defs) { |
27 | for (const auto &definition : defs) { |
28 | Property property(definition); |
29 | assert(property.IsValid()); |
30 | m_name_to_index.insert(KV: {property.GetName(), m_properties.size()}); |
31 | property.GetValue()->SetParent(shared_from_this()); |
32 | m_properties.push_back(x: property); |
33 | } |
34 | } |
35 | |
36 | void OptionValueProperties::SetValueChangedCallback( |
37 | size_t property_idx, std::function<void()> callback) { |
38 | Property *property = ProtectedGetPropertyAtIndex(idx: property_idx); |
39 | if (property) |
40 | property->SetValueChangedCallback(std::move(callback)); |
41 | } |
42 | |
43 | void OptionValueProperties::AppendProperty(llvm::StringRef name, |
44 | llvm::StringRef desc, bool is_global, |
45 | const OptionValueSP &value_sp) { |
46 | Property property(name, desc, is_global, value_sp); |
47 | m_name_to_index.insert(KV: {name, m_properties.size()}); |
48 | m_properties.push_back(x: property); |
49 | value_sp->SetParent(shared_from_this()); |
50 | } |
51 | |
52 | lldb::OptionValueSP |
53 | OptionValueProperties::GetValueForKey(const ExecutionContext *exe_ctx, |
54 | llvm::StringRef key) const { |
55 | auto iter = m_name_to_index.find(Key: key); |
56 | if (iter == m_name_to_index.end()) |
57 | return OptionValueSP(); |
58 | const size_t idx = iter->second; |
59 | if (idx >= m_properties.size()) |
60 | return OptionValueSP(); |
61 | return GetPropertyAtIndex(idx, exe_ctx)->GetValue(); |
62 | } |
63 | |
64 | lldb::OptionValueSP |
65 | OptionValueProperties::GetSubValue(const ExecutionContext *exe_ctx, |
66 | llvm::StringRef name, Status &error) const { |
67 | lldb::OptionValueSP value_sp; |
68 | if (name.empty()) |
69 | return OptionValueSP(); |
70 | |
71 | llvm::StringRef sub_name; |
72 | llvm::StringRef key; |
73 | size_t key_len = name.find_first_of(Chars: ".[{" ); |
74 | if (key_len != llvm::StringRef::npos) { |
75 | key = name.take_front(N: key_len); |
76 | sub_name = name.drop_front(N: key_len); |
77 | } else |
78 | key = name; |
79 | |
80 | value_sp = GetValueForKey(exe_ctx, key); |
81 | if (sub_name.empty() || !value_sp) |
82 | return value_sp; |
83 | |
84 | switch (sub_name[0]) { |
85 | case '.': { |
86 | lldb::OptionValueSP return_val_sp; |
87 | return_val_sp = |
88 | value_sp->GetSubValue(exe_ctx, name: sub_name.drop_front(), error); |
89 | if (!return_val_sp) { |
90 | if (Properties::IsSettingExperimental(setting: sub_name.drop_front())) { |
91 | const size_t experimental_len = |
92 | Properties::GetExperimentalSettingsName().size(); |
93 | if (sub_name[experimental_len + 1] == '.') |
94 | return_val_sp = value_sp->GetSubValue( |
95 | exe_ctx, name: sub_name.drop_front(N: experimental_len + 2), error); |
96 | // It isn't an error if an experimental setting is not present. |
97 | if (!return_val_sp) |
98 | error.Clear(); |
99 | } |
100 | } |
101 | return return_val_sp; |
102 | } |
103 | case '[': |
104 | // Array or dictionary access for subvalues like: "[12]" -- access |
105 | // 12th array element "['hello']" -- dictionary access of key named hello |
106 | return value_sp->GetSubValue(exe_ctx, name: sub_name, error); |
107 | |
108 | default: |
109 | value_sp.reset(); |
110 | break; |
111 | } |
112 | return value_sp; |
113 | } |
114 | |
115 | Status OptionValueProperties::SetSubValue(const ExecutionContext *exe_ctx, |
116 | VarSetOperationType op, |
117 | llvm::StringRef name, |
118 | llvm::StringRef value) { |
119 | Status error; |
120 | llvm::SmallVector<llvm::StringRef, 8> components; |
121 | name.split(A&: components, Separator: '.'); |
122 | bool name_contains_experimental = false; |
123 | for (const auto &part : components) |
124 | if (Properties::IsSettingExperimental(setting: part)) |
125 | name_contains_experimental = true; |
126 | |
127 | lldb::OptionValueSP value_sp(GetSubValue(exe_ctx, name, error)); |
128 | if (value_sp) |
129 | error = value_sp->SetValueFromString(value, op); |
130 | else { |
131 | // Don't set an error if the path contained .experimental. - those are |
132 | // allowed to be missing and should silently fail. |
133 | if (!name_contains_experimental && error.AsCString() == nullptr) { |
134 | error.SetErrorStringWithFormat("invalid value path '%s'" , |
135 | name.str().c_str()); |
136 | } |
137 | } |
138 | return error; |
139 | } |
140 | |
141 | size_t OptionValueProperties::GetPropertyIndex(llvm::StringRef name) const { |
142 | auto iter = m_name_to_index.find(Key: name); |
143 | if (iter == m_name_to_index.end()) |
144 | return SIZE_MAX; |
145 | return iter->second; |
146 | } |
147 | |
148 | const Property * |
149 | OptionValueProperties::GetProperty(llvm::StringRef name, |
150 | const ExecutionContext *exe_ctx) const { |
151 | auto iter = m_name_to_index.find(Key: name); |
152 | if (iter == m_name_to_index.end()) |
153 | return nullptr; |
154 | return GetPropertyAtIndex(idx: iter->second, exe_ctx); |
155 | } |
156 | |
157 | lldb::OptionValueSP OptionValueProperties::GetPropertyValueAtIndex( |
158 | size_t idx, const ExecutionContext *exe_ctx) const { |
159 | const Property *setting = GetPropertyAtIndex(idx, exe_ctx); |
160 | if (setting) |
161 | return setting->GetValue(); |
162 | return OptionValueSP(); |
163 | } |
164 | |
165 | OptionValuePathMappings * |
166 | OptionValueProperties::GetPropertyAtIndexAsOptionValuePathMappings( |
167 | size_t idx, const ExecutionContext *exe_ctx) const { |
168 | OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); |
169 | if (value_sp) |
170 | return value_sp->GetAsPathMappings(); |
171 | return nullptr; |
172 | } |
173 | |
174 | OptionValueFileSpecList * |
175 | OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpecList( |
176 | size_t idx, const ExecutionContext *exe_ctx) const { |
177 | OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); |
178 | if (value_sp) |
179 | return value_sp->GetAsFileSpecList(); |
180 | return nullptr; |
181 | } |
182 | |
183 | bool OptionValueProperties::GetPropertyAtIndexAsArgs( |
184 | size_t idx, Args &args, const ExecutionContext *exe_ctx) const { |
185 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
186 | if (!property) |
187 | return false; |
188 | |
189 | OptionValue *value = property->GetValue().get(); |
190 | if (!value) |
191 | return false; |
192 | |
193 | const OptionValueArgs *arguments = value->GetAsArgs(); |
194 | if (arguments) { |
195 | arguments->GetArgs(args); |
196 | return true; |
197 | } |
198 | |
199 | const OptionValueArray *array = value->GetAsArray(); |
200 | if (array) { |
201 | array->GetArgs(args); |
202 | return true; |
203 | } |
204 | |
205 | const OptionValueDictionary *dict = value->GetAsDictionary(); |
206 | if (dict) { |
207 | dict->GetArgs(args); |
208 | return true; |
209 | } |
210 | |
211 | return false; |
212 | } |
213 | |
214 | bool OptionValueProperties::SetPropertyAtIndexFromArgs( |
215 | size_t idx, const Args &args, const ExecutionContext *exe_ctx) { |
216 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
217 | if (!property) |
218 | return false; |
219 | |
220 | OptionValue *value = property->GetValue().get(); |
221 | if (!value) |
222 | return false; |
223 | |
224 | OptionValueArgs *arguments = value->GetAsArgs(); |
225 | if (arguments) |
226 | return arguments->SetArgs(args, op: eVarSetOperationAssign).Success(); |
227 | |
228 | OptionValueArray *array = value->GetAsArray(); |
229 | if (array) |
230 | return array->SetArgs(args, op: eVarSetOperationAssign).Success(); |
231 | |
232 | OptionValueDictionary *dict = value->GetAsDictionary(); |
233 | if (dict) |
234 | return dict->SetArgs(args, op: eVarSetOperationAssign).Success(); |
235 | |
236 | return false; |
237 | } |
238 | |
239 | OptionValueDictionary * |
240 | OptionValueProperties::GetPropertyAtIndexAsOptionValueDictionary( |
241 | size_t idx, const ExecutionContext *exe_ctx) const { |
242 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
243 | if (property) |
244 | return property->GetValue()->GetAsDictionary(); |
245 | return nullptr; |
246 | } |
247 | |
248 | OptionValueFileSpec * |
249 | OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpec( |
250 | size_t idx, const ExecutionContext *exe_ctx) const { |
251 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
252 | if (property) { |
253 | OptionValue *value = property->GetValue().get(); |
254 | if (value) |
255 | return value->GetAsFileSpec(); |
256 | } |
257 | return nullptr; |
258 | } |
259 | |
260 | OptionValueSInt64 *OptionValueProperties::GetPropertyAtIndexAsOptionValueSInt64( |
261 | size_t idx, const ExecutionContext *exe_ctx) const { |
262 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
263 | if (property) { |
264 | OptionValue *value = property->GetValue().get(); |
265 | if (value) |
266 | return value->GetAsSInt64(); |
267 | } |
268 | return nullptr; |
269 | } |
270 | |
271 | OptionValueUInt64 *OptionValueProperties::GetPropertyAtIndexAsOptionValueUInt64( |
272 | size_t idx, const ExecutionContext *exe_ctx) const { |
273 | const Property *property = GetPropertyAtIndex(idx, exe_ctx); |
274 | if (property) { |
275 | OptionValue *value = property->GetValue().get(); |
276 | if (value) |
277 | return value->GetAsUInt64(); |
278 | } |
279 | return nullptr; |
280 | } |
281 | |
282 | OptionValueString *OptionValueProperties::GetPropertyAtIndexAsOptionValueString( |
283 | size_t idx, const ExecutionContext *exe_ctx) const { |
284 | OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); |
285 | if (value_sp) |
286 | return value_sp->GetAsString(); |
287 | return nullptr; |
288 | } |
289 | |
290 | void OptionValueProperties::Clear() { |
291 | const size_t num_properties = m_properties.size(); |
292 | for (size_t i = 0; i < num_properties; ++i) |
293 | m_properties[i].GetValue()->Clear(); |
294 | } |
295 | |
296 | Status OptionValueProperties::SetValueFromString(llvm::StringRef value, |
297 | VarSetOperationType op) { |
298 | Status error; |
299 | |
300 | // Args args(value_cstr); |
301 | // const size_t argc = args.GetArgumentCount(); |
302 | switch (op) { |
303 | case eVarSetOperationClear: |
304 | Clear(); |
305 | break; |
306 | |
307 | case eVarSetOperationReplace: |
308 | case eVarSetOperationAssign: |
309 | case eVarSetOperationRemove: |
310 | case eVarSetOperationInsertBefore: |
311 | case eVarSetOperationInsertAfter: |
312 | case eVarSetOperationAppend: |
313 | case eVarSetOperationInvalid: |
314 | error = OptionValue::SetValueFromString(value, op); |
315 | break; |
316 | } |
317 | |
318 | return error; |
319 | } |
320 | |
321 | void OptionValueProperties::DumpValue(const ExecutionContext *exe_ctx, |
322 | Stream &strm, uint32_t dump_mask) { |
323 | const size_t num_properties = m_properties.size(); |
324 | for (size_t i = 0; i < num_properties; ++i) { |
325 | const Property *property = GetPropertyAtIndex(idx: i, exe_ctx); |
326 | if (property) { |
327 | OptionValue *option_value = property->GetValue().get(); |
328 | assert(option_value); |
329 | const bool transparent_value = option_value->ValueIsTransparent(); |
330 | property->Dump(exe_ctx, strm, dump_mask); |
331 | if (!transparent_value) |
332 | strm.EOL(); |
333 | } |
334 | } |
335 | } |
336 | |
337 | llvm::json::Value |
338 | OptionValueProperties::ToJSON(const ExecutionContext *exe_ctx) { |
339 | llvm::json::Object json_properties; |
340 | const size_t num_properties = m_properties.size(); |
341 | for (size_t i = 0; i < num_properties; ++i) { |
342 | const Property *property = GetPropertyAtIndex(idx: i, exe_ctx); |
343 | if (property) { |
344 | OptionValue *option_value = property->GetValue().get(); |
345 | assert(option_value); |
346 | json_properties.try_emplace(K: property->GetName(), |
347 | Args: option_value->ToJSON(exe_ctx)); |
348 | } |
349 | } |
350 | return json_properties; |
351 | } |
352 | |
353 | Status OptionValueProperties::DumpPropertyValue(const ExecutionContext *exe_ctx, |
354 | Stream &strm, |
355 | llvm::StringRef property_path, |
356 | uint32_t dump_mask, |
357 | bool is_json) { |
358 | Status error; |
359 | lldb::OptionValueSP value_sp(GetSubValue(exe_ctx, name: property_path, error)); |
360 | if (value_sp) { |
361 | if (!value_sp->ValueIsTransparent()) { |
362 | if (dump_mask & eDumpOptionName) |
363 | strm.PutCString(cstr: property_path); |
364 | if (dump_mask & ~eDumpOptionName) |
365 | strm.PutChar(ch: ' '); |
366 | } |
367 | if (is_json) { |
368 | strm.Printf( |
369 | format: "%s" , |
370 | llvm::formatv(Fmt: "{0:2}" , Vals: value_sp->ToJSON(exe_ctx)).str().c_str()); |
371 | } else |
372 | value_sp->DumpValue(exe_ctx, strm, dump_mask); |
373 | } |
374 | return error; |
375 | } |
376 | |
377 | OptionValuePropertiesSP |
378 | OptionValueProperties::CreateLocalCopy(const Properties &global_properties) { |
379 | auto global_props_sp = global_properties.GetValueProperties(); |
380 | lldbassert(global_props_sp); |
381 | |
382 | auto copy_sp = global_props_sp->DeepCopy(new_parent: global_props_sp->GetParent()); |
383 | return std::static_pointer_cast<OptionValueProperties>(r: copy_sp); |
384 | } |
385 | |
386 | OptionValueSP |
387 | OptionValueProperties::DeepCopy(const OptionValueSP &new_parent) const { |
388 | auto copy_sp = OptionValue::DeepCopy(new_parent); |
389 | // copy_sp->GetAsProperties cannot be used here as it doesn't work for derived |
390 | // types that override GetType returning a different value. |
391 | auto *props_value_ptr = static_cast<OptionValueProperties *>(copy_sp.get()); |
392 | lldbassert(props_value_ptr); |
393 | |
394 | for (auto &property : props_value_ptr->m_properties) { |
395 | // Duplicate any values that are not global when constructing properties |
396 | // from a global copy. |
397 | if (!property.IsGlobal()) { |
398 | auto value_sp = property.GetValue()->DeepCopy(new_parent: copy_sp); |
399 | property.SetOptionValue(value_sp); |
400 | } |
401 | } |
402 | return copy_sp; |
403 | } |
404 | |
405 | const Property * |
406 | OptionValueProperties::GetPropertyAtPath(const ExecutionContext *exe_ctx, |
407 | llvm::StringRef name) const { |
408 | if (name.empty()) |
409 | return nullptr; |
410 | |
411 | const Property *property = nullptr; |
412 | llvm::StringRef sub_name; |
413 | llvm::StringRef key; |
414 | size_t key_len = name.find_first_of(Chars: ".[{" ); |
415 | |
416 | if (key_len != llvm::StringRef::npos) { |
417 | key = name.take_front(N: key_len); |
418 | sub_name = name.drop_front(N: key_len); |
419 | } else |
420 | key = name; |
421 | |
422 | property = GetProperty(name: key, exe_ctx); |
423 | if (sub_name.empty() || !property) |
424 | return property; |
425 | |
426 | if (sub_name[0] == '.') { |
427 | OptionValueProperties *sub_properties = |
428 | property->GetValue()->GetAsProperties(); |
429 | if (sub_properties) |
430 | return sub_properties->GetPropertyAtPath(exe_ctx, name: sub_name.drop_front()); |
431 | } |
432 | return nullptr; |
433 | } |
434 | |
435 | void OptionValueProperties::DumpAllDescriptions(CommandInterpreter &interpreter, |
436 | Stream &strm) const { |
437 | size_t max_name_len = 0; |
438 | const size_t num_properties = m_properties.size(); |
439 | for (size_t i = 0; i < num_properties; ++i) { |
440 | const Property *property = ProtectedGetPropertyAtIndex(idx: i); |
441 | if (property) |
442 | max_name_len = std::max<size_t>(a: property->GetName().size(), b: max_name_len); |
443 | } |
444 | for (size_t i = 0; i < num_properties; ++i) { |
445 | const Property *property = ProtectedGetPropertyAtIndex(idx: i); |
446 | if (property) |
447 | property->DumpDescription(interpreter, strm, output_width: max_name_len, display_qualified_name: false); |
448 | } |
449 | } |
450 | |
451 | void OptionValueProperties::Apropos( |
452 | llvm::StringRef keyword, |
453 | std::vector<const Property *> &matching_properties) const { |
454 | const size_t num_properties = m_properties.size(); |
455 | StreamString strm; |
456 | for (size_t i = 0; i < num_properties; ++i) { |
457 | const Property *property = ProtectedGetPropertyAtIndex(idx: i); |
458 | if (property) { |
459 | const OptionValueProperties *properties = |
460 | property->GetValue()->GetAsProperties(); |
461 | if (properties) { |
462 | properties->Apropos(keyword, matching_properties); |
463 | } else { |
464 | bool match = false; |
465 | llvm::StringRef name = property->GetName(); |
466 | if (name.contains_insensitive(Other: keyword)) |
467 | match = true; |
468 | else { |
469 | llvm::StringRef desc = property->GetDescription(); |
470 | if (desc.contains_insensitive(Other: keyword)) |
471 | match = true; |
472 | } |
473 | if (match) { |
474 | matching_properties.push_back(x: property); |
475 | } |
476 | } |
477 | } |
478 | } |
479 | } |
480 | |
481 | lldb::OptionValuePropertiesSP |
482 | OptionValueProperties::GetSubProperty(const ExecutionContext *exe_ctx, |
483 | llvm::StringRef name) { |
484 | lldb::OptionValueSP option_value_sp(GetValueForKey(exe_ctx, key: name)); |
485 | if (option_value_sp) { |
486 | OptionValueProperties *ov_properties = option_value_sp->GetAsProperties(); |
487 | if (ov_properties) |
488 | return ov_properties->shared_from_this(); |
489 | } |
490 | return lldb::OptionValuePropertiesSP(); |
491 | } |
492 | |