blob: 9012940bd21dbc6d94ed53feebf0fabfdfbd4f2c [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/console/format_name.h"
#include <ctype.h>
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/expr/expr_tokenizer.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/variable.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// Checks if the function is a Clang-style lambda and formats it to the output. Returns true if
// there was a match, false means it wasn't a lambda.
//
// Clang lambdas are implemented as functions called "operator()" as a member of an unnamed class.
// GCC lambdas are also "operator()" but on a structure named like "<lambda(int)>" where the <...>
// lists the parameter types to the functions..
bool FormatClangLambda(const Function* function, OutputBuffer* out) {
if (!function->parent())
return false; // Not a member function.
if (function->GetAssignedName() != "operator()")
return false; // Not the right function name.
// This is currently designed assuming the file/line will be printed separately so aren't useful
// here. The main use of function printing is as part of locations, which will append the
// file/line after the function name.
//
// If this is used in contexts where the file/line isn't shown, we should add a flag and an
// optional_target_symbols parameter to this function so we can print it as:
// out->Append("λ @ " + FormatFileLine(function->decl_line(), optional_target_symbols));
// so users can tell where the lambda function is.
auto parent_ref = function->parent().Get(); // Hold a ref to keep alive.
const Collection* coll = parent_ref->As<Collection>();
if (coll && coll->tag() == DwarfTag::kClassType && coll->GetAssignedName().empty()) {
// Clang-style.
out->Append("λ");
return true;
} else if (coll && coll->tag() == DwarfTag::kStructureType &&
StringStartsWith(coll->GetAssignedName(), "<lambda(")) {
// GCC-style.
out->Append("λ");
return true;
}
return false;
}
bool FormatRustClosure(const Function* function, OutputBuffer* out) {
// Rust closures currently look like
// "fuchsia_async::executor::{{impl}}::run_singlethreaded::{{closure}}<()>"
// The function "assigned name" will be just the last component.
if (!StringStartsWith(function->GetAssignedName(), "{{closure}}"))
return false;
// As with the Clang lambda above, this assumes the file/line or function enclosing the original
// lambda is redundant.
out->Append("λ");
return true;
}
// Escapes the given identifier component. This does not put the "$(...) around it, but handles the
// stuff in the middle. There are two things that need to happen: handling parens and escaping
// backslashes.
//
// If parens are balanced inside the component (there is a closing paren for each opening one), they
// do not need escaping. In most cases, these will be balanced because it's some kind of textual
// representation for a function call or something. The parser can handle this and it's much more
// readable.
//
// The algorithm is to assume the parens are balanced and just escape backslashes. If it finds the
// parens are unbalanced, it recursively calls itself with paren escaping forced on. We don't try
// to be intelligent about only escaping the unbalanced parens, they all get escaped in this mode.
std::string EscapeComponent(const std::string& name, bool force_escape_parens = false) {
std::string output;
int paren_nesting = 0;
for (char c : name) {
if (c == '(') {
if (force_escape_parens) {
output.push_back('\\');
output.push_back('(');
} else {
paren_nesting++;
output.push_back('(');
}
} else if (c == ')') {
if (force_escape_parens) {
output.push_back('\\');
output.push_back(')');
} else if (paren_nesting == 0) {
// Lone ')' without an opening paren.
return EscapeComponent(name, true);
} else {
// Found closing paren for a previous opening one.
paren_nesting--;
output.push_back(')');
}
} else if (c == '$' || c == '\\') {
// Always escape these.
output.push_back('\\');
output.push_back(c);
} else {
// Everything else is a literal.
output.push_back(c);
}
}
if (paren_nesting > 0) {
// There was an opening paren with no closing paren.
return EscapeComponent(name, true);
}
return output;
}
bool NeedsEscaping(const std::string& name) {
// For now assume the language is C. It would be nice if the current language was piped through to
// this spot so we could make language-specific escaping determinations.
if (ExprTokenizer::IsNameToken(ExprLanguage::kC, name))
return false; // Normal simple name.
// Assume anything with "operator" doesn't need escaping. We could actually parse the operator
// declaration to make sure it's valid. But this only needs to deal with names the compiler
// actually generates and not escaping something in the UI isn't a huge deal if we're wrong.
if (StringStartsWith(name, "operator"))
return false;
return true;
}
} // namespace
OutputBuffer FormatFunctionName(const Function* function,
const FormatFunctionNameOptions& options) {
OutputBuffer result;
if (!FormatClangLambda(function, &result) && !FormatRustClosure(function, &result))
result = FormatIdentifier(function->GetIdentifier(), options.name);
const auto& params = function->parameters();
switch (options.params) {
case FormatFunctionNameOptions::kNoParams: {
break;
}
case FormatFunctionNameOptions::kElideParams: {
if (params.empty())
result.Append(Syntax::kComment, "()");
else
result.Append(Syntax::kComment, "(…)");
break;
}
case FormatFunctionNameOptions::kParamTypes: {
std::string params_str;
params_str.push_back('(');
for (size_t i = 0; i < params.size(); i++) {
if (i > 0)
params_str += ", ";
if (const Variable* var = params[i].Get()->As<Variable>())
params_str += var->type().Get()->GetFullName();
}
params_str.push_back(')');
result.Append(Syntax::kComment, std::move(params_str));
break;
}
}
return result;
}
OutputBuffer FormatIdentifier(const Identifier& identifier,
const FormatIdentifierOptions& options) {
return FormatIdentifier(ToParsedIdentifier(identifier), options);
}
// This annoyingly duplicates Identifier::GetName but is required to get syntax highlighting for all
// the components.
OutputBuffer FormatIdentifier(const ParsedIdentifier& identifier,
const FormatIdentifierOptions& options) {
OutputBuffer result;
if (options.show_global_qual && identifier.qualification() == IdentifierQualification::kGlobal)
result.Append(identifier.GetSeparator());
const auto& comps = identifier.components();
for (size_t i = 0; i < comps.size(); i++) {
const auto& comp = comps[i];
if (i > 0)
result.Append(identifier.GetSeparator());
if (comp.special() == SpecialIdentifier::kNone) {
// Name.
std::string name = comp.name();
if (name.empty()) {
// Provide names for anonymous components.
result.Append(Syntax::kComment, kAnonIdentifierComponentName);
} else {
bool needs_escaping = NeedsEscaping(name);
if (needs_escaping)
result.Append(Syntax::kComment, "$(");
if (options.bold_last && i == comps.size() - 1)
result.Append(Syntax::kHeading, needs_escaping ? EscapeComponent(name) : name);
else
result.Append(Syntax::kNormal, needs_escaping ? EscapeComponent(name) : name);
if (needs_escaping)
result.Append(Syntax::kComment, ")");
}
} else if (comp.special() == SpecialIdentifier::kAnon) {
// Always dim anonymous names.
result.Append(Syntax::kComment, std::string(SpecialIdentifierToString(comp.special())));
} else {
// Other special name.
if (SpecialIdentifierHasData(comp.special())) {
// Special identifier has data, dim the tag part to emphasize the contents.
result.Append(Syntax::kComment,
std::string(SpecialIdentifierToString(comp.special())) + "(");
result.Append(comp.name());
result.Append(Syntax::kComment, ")");
} else {
// Standalone special identifier, just append it as normal.
result.Append(Syntax::kNormal, std::string(SpecialIdentifierToString(comp.special())));
}
}
// Template.
if (comp.has_template()) {
std::string t_string("<");
if (!comp.template_contents().empty()) {
if (options.elide_templates) {
t_string += "…";
} else {
for (size_t t_i = 0; t_i < comp.template_contents().size(); t_i++) {
if (t_i > 0)
t_string += ", ";
t_string += comp.template_contents()[t_i];
}
}
}
t_string.push_back('>');
result.Append(Syntax::kComment, std::move(t_string));
}
}
return result;
}
} // namespace zxdb