267 lines · cpp
1//===-- CommandObjectDWIMPrint.cpp ------------------------------*- C++ -*-===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "CommandObjectDWIMPrint.h"10 11#include "lldb/DataFormatters/DumpValueObjectOptions.h"12#include "lldb/Expression/ExpressionVariable.h"13#include "lldb/Expression/UserExpression.h"14#include "lldb/Interpreter/CommandInterpreter.h"15#include "lldb/Interpreter/CommandObject.h"16#include "lldb/Interpreter/CommandReturnObject.h"17#include "lldb/Interpreter/OptionGroupFormat.h"18#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h"19#include "lldb/Target/StackFrame.h"20#include "lldb/Utility/ConstString.h"21#include "lldb/ValueObject/ValueObject.h"22#include "lldb/lldb-defines.h"23#include "lldb/lldb-enumerations.h"24#include "lldb/lldb-forward.h"25#include "llvm/ADT/StringRef.h"26 27#include <regex>28 29using namespace llvm;30using namespace lldb;31using namespace lldb_private;32 33CommandObjectDWIMPrint::CommandObjectDWIMPrint(CommandInterpreter &interpreter)34 : CommandObjectRaw(interpreter, "dwim-print",35 "Print a variable or expression.",36 "dwim-print [<variable-name> | <expression>]",37 eCommandProcessMustBePaused | eCommandTryTargetAPILock) {38 39 AddSimpleArgumentList(eArgTypeVarName);40 41 m_option_group.Append(&m_format_options,42 OptionGroupFormat::OPTION_GROUP_FORMAT |43 OptionGroupFormat::OPTION_GROUP_GDB_FMT,44 LLDB_OPT_SET_1);45 StringRef exclude_expr_options[] = {"debug", "top-level"};46 m_option_group.Append(&m_expr_options, exclude_expr_options);47 m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);48 m_option_group.Finalize();49}50 51Options *CommandObjectDWIMPrint::GetOptions() { return &m_option_group; }52 53void CommandObjectDWIMPrint::DoExecute(StringRef command,54 CommandReturnObject &result) {55 m_option_group.NotifyOptionParsingStarting(&m_exe_ctx);56 OptionsWithRaw args{command};57 StringRef expr = args.GetRawPart();58 59 if (expr.empty()) {60 result.AppendErrorWithFormatv("'{0}' takes a variable or expression",61 m_cmd_name);62 return;63 }64 65 if (args.HasArgs()) {66 if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group,67 m_exe_ctx))68 return;69 }70 71 // If the user has not specified, default to disabling persistent results.72 if (m_expr_options.suppress_persistent_result == eLazyBoolCalculate)73 m_expr_options.suppress_persistent_result = eLazyBoolYes;74 bool suppress_result = m_expr_options.ShouldSuppressResult(m_varobj_options);75 76 auto verbosity = GetDebugger().GetDWIMPrintVerbosity();77 78 Target *target_ptr = m_exe_ctx.GetTargetPtr();79 // Fallback to the dummy target, which can allow for expression evaluation.80 Target &target = target_ptr ? *target_ptr : GetDummyTarget();81 82 EvaluateExpressionOptions eval_options =83 m_expr_options.GetEvaluateExpressionOptions(target, m_varobj_options);84 // This command manually removes the result variable, make sure expression85 // evaluation doesn't do it first.86 eval_options.SetSuppressPersistentResult(false);87 88 DumpValueObjectOptions dump_options = m_varobj_options.GetAsDumpOptions(89 m_expr_options.m_verbosity, m_format_options.GetFormat());90 dump_options.SetHideRootName(suppress_result)91 .SetExpandPointerTypeFlags(lldb::eTypeIsObjC);92 93 bool is_po = m_varobj_options.use_object_desc;94 95 StackFrame *frame = m_exe_ctx.GetFramePtr();96 97 // Either the language was explicitly specified, or we check the frame.98 SourceLanguage language{m_expr_options.language};99 if (!language && frame)100 language = frame->GuessLanguage();101 102 // Add a hint if object description was requested, but no description103 // function was implemented.104 auto maybe_add_hint = [&](llvm::StringRef output) {105 static bool note_shown = false;106 if (note_shown)107 return;108 109 // Identify the default output of object description for Swift and110 // Objective-C111 // "<Name: 0x...>. The regex is:112 // - Start with "<".113 // - Followed by 1 or more non-whitespace characters.114 // - Followed by ": 0x".115 // - Followed by 5 or more hex digits.116 // - Followed by ">".117 // - End with zero or more whitespace characters.118 static const std::regex swift_class_regex(119 "^<\\S+: 0x[[:xdigit:]]{5,}>\\s*$");120 121 if (GetDebugger().GetShowDontUsePoHint() && target_ptr &&122 (language.AsLanguageType() == lldb::eLanguageTypeSwift ||123 language.IsObjC()) &&124 std::regex_match(output.data(), swift_class_regex)) {125 126 result.AppendNote(127 "object description requested, but type doesn't implement "128 "a custom object description. Consider using \"p\" instead of "129 "\"po\" (this note will only be shown once per debug session).\n");130 note_shown = true;131 }132 };133 134 // Dump `valobj` according to whether `po` was requested or not.135 auto dump_val_object = [&](ValueObject &valobj) {136 if (is_po) {137 StreamString temp_result_stream;138 if (llvm::Error error = valobj.Dump(temp_result_stream, dump_options)) {139 result.AppendError(toString(std::move(error)));140 return;141 }142 llvm::StringRef output = temp_result_stream.GetString();143 maybe_add_hint(output);144 result.GetOutputStream() << output;145 } else {146 llvm::Error error =147 valobj.Dump(result.GetOutputStream(), dump_options);148 if (error) {149 result.AppendError(toString(std::move(error)));150 return;151 }152 }153 m_interpreter.PrintWarningsIfNecessary(result.GetOutputStream(),154 m_cmd_name);155 result.SetStatus(eReturnStatusSuccessFinishResult);156 };157 158 // First, try `expr` as a _limited_ frame variable expression path: only the159 // dot operator (`.`) is permitted for this case.160 //161 // This is limited to support only unambiguous expression paths. Of note,162 // expression paths are not attempted if the expression contain either the163 // arrow operator (`->`) or the subscript operator (`[]`). This is because164 // both operators can be overloaded in C++, and could result in ambiguity in165 // how the expression is handled. Additionally, `*` and `&` are not supported.166 const bool try_variable_path =167 expr.find_first_of("*&->[]") == StringRef::npos;168 if (frame && try_variable_path) {169 VariableSP var_sp;170 Status status;171 auto valobj_sp = frame->GetValueForVariableExpressionPath(172 expr, eval_options.GetUseDynamic(),173 StackFrame::eExpressionPathOptionsAllowDirectIVarAccess, var_sp,174 status);175 if (valobj_sp && status.Success() && valobj_sp->GetError().Success()) {176 if (!suppress_result) {177 if (auto persisted_valobj = valobj_sp->Persist())178 valobj_sp = persisted_valobj;179 }180 181 if (verbosity == eDWIMPrintVerbosityFull) {182 StringRef flags;183 if (args.HasArgs())184 flags = args.GetArgString();185 result.AppendNoteWithFormatv("ran `frame variable {0}{1}`", flags,186 expr);187 }188 189 dump_val_object(*valobj_sp);190 return;191 }192 }193 194 // Second, try `expr` as a persistent variable.195 if (expr.starts_with("$"))196 if (auto *state = target.GetPersistentExpressionStateForLanguage(197 language.AsLanguageType()))198 if (auto var_sp = state->GetVariable(expr))199 if (auto valobj_sp = var_sp->GetValueObject()) {200 dump_val_object(*valobj_sp);201 return;202 }203 204 // Third, and lastly, try `expr` as a source expression to evaluate.205 {206 auto *exe_scope = m_exe_ctx.GetBestExecutionContextScope();207 ValueObjectSP valobj_sp;208 std::string fixed_expression;209 210 ExpressionResults expr_result = target.EvaluateExpression(211 expr, exe_scope, valobj_sp, eval_options, &fixed_expression);212 213 if (valobj_sp)214 result.GetValueObjectList().Append(valobj_sp);215 216 // Record the position of the expression in the command.217 std::optional<uint16_t> indent;218 if (fixed_expression.empty()) {219 size_t pos = m_original_command.rfind(expr);220 if (pos != llvm::StringRef::npos)221 indent = pos;222 }223 // Previously the indent was set up for diagnosing command line224 // parsing errors. Now point it to the expression.225 result.SetDiagnosticIndent(indent);226 227 // Only mention Fix-Its if the expression evaluator applied them.228 // Compiler errors refer to the final expression after applying Fix-It(s).229 if (!fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) {230 Stream &error_stream = result.GetErrorStream();231 error_stream << " Evaluated this expression after applying Fix-It(s):\n";232 error_stream << " " << fixed_expression << "\n";233 }234 235 // If the expression failed, return an error.236 if (expr_result != eExpressionCompleted) {237 if (valobj_sp)238 result.SetError(valobj_sp->GetError().Clone());239 else240 result.AppendErrorWithFormatv(241 "unknown error evaluating expression `{0}`", expr);242 return;243 }244 245 if (verbosity != eDWIMPrintVerbosityNone) {246 StringRef flags;247 if (args.HasArgs())248 flags = args.GetArgStringWithDelimiter();249 result.AppendNoteWithFormatv("ran `expression {0}{1}`", flags, expr);250 }251 252 if (valobj_sp->GetError().GetError() != UserExpression::kNoResult)253 dump_val_object(*valobj_sp);254 else255 result.SetStatus(eReturnStatusSuccessFinishNoResult);256 257 if (suppress_result)258 if (auto result_var_sp =259 target.GetPersistentVariable(valobj_sp->GetName())) {260 auto language = valobj_sp->GetPreferredDisplayLanguage();261 if (auto *persistent_state =262 target.GetPersistentExpressionStateForLanguage(language))263 persistent_state->RemovePersistentVariable(result_var_sp);264 }265 }266}267