blob: cdf30b100229f8608387a370bf7a1f995479dcfd [file] [log] [blame]
// Copyright 2022 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/expr/variable_decl.h"
#include "src/developer/debug/zxdb/common/ref_ptr_to.h"
#include "src/developer/debug/zxdb/expr/cast.h"
#include "src/developer/debug/zxdb/expr/eval_context.h"
#include "src/developer/debug/zxdb/expr/expr_node.h"
#include "src/developer/debug/zxdb/expr/vm_op.h"
#include "src/developer/debug/zxdb/expr/vm_stream.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/type.h"
namespace zxdb {
// C++ rules for type deduction and conversions are "complicated." Our goal is to allow simple
// helper code in a natural way that looks like C++ (or Rust) without implementing very much of this
// logic. But we also don't want to subtly diverge from C++ in surprising ways. So we want a clear
// and simple subset of C++ behavior and give clear error messages for anything else.
//
// C++ "auto"
// ----------
//
// The "auto" type is important because the code snippets will often be decoding template data and
// the types won't be known in advance. The following is supported for "auto" (the debugger ignores
// "const"):
//
// - auto : Removes the reference if the right-hand-side expression is a reference.
// - auto& : Keeps the reference if it exits (unlike bare "auto") and makes one if it doesn't.
// - auto* : Like "auto" but verifies that the right-hand-side is a pointer.
//
// No other uses of "auto" in C++ is permitted for local variables. This means we can easily just
// enumerate the cases rather than write a complicated type matcher. Most users never use "auto"
// beyond this, and if the user does something unsupported, we can give a clear error message.
//
// C++ references
// --------------
//
// C++ reference initialization has special rules. When we see something like "Foo f = expr;" we
// would like to default-initialize "f" (in the debugger there are no side-effects so this is OK),
// cast the right-hand-side expression to a "Foo" using the normal casting logic, and then do the
// assignment. But this doesn't work if the left-hand type is a reference because initializers for
// references are different than for other types of assignment (it will implicitly take a pointer
// to the value in the initializer expression).
//
// To avoid this problem, we say you can't have references in the types of local variables that
// are anything other than "auto&". This keeps all of the reference logic in that one place and
// means we never have to convert types when making references. This can be annoying and we can
// enhance in the future, but at least we can give clear actionable error messages.
//
// Rust
// ----
//
// Rust's references are a bit easier. The type of a "let" expression with no explicit type is
// the exact type of the initializer, even if that initializer is a reference.
namespace {
bool IsCAutoType(const Type* type) { return type && type->GetAssignedName() == "auto"; }
// Walks the modified type hierarchy and returns true if any component of it is an "auto".
bool HasAnyCAutoType(const Type* type) {
while (type) {
if (IsCAutoType(type))
return true;
if (const ModifiedType* modified = type->As<ModifiedType>()) {
type = modified->modified().Get()->As<Type>();
} else {
break; // Not a modified type, done.
}
}
return false;
}
// Ensures (if possible) that the given value is a reference. If it's not a reference, attempts to
// take a reference to the value (this is just it's address).
ErrOrValue ConvertToReference(const fxl::RefPtr<EvalContext> eval_context, const ExprValue& value) {
fxl::RefPtr<Type> concrete_type = eval_context->GetConcreteType(value.type());
if (!concrete_type)
return Err("Variable initialization expression produced no results.");
if (concrete_type->tag() == DwarfTag::kReferenceType)
return value; // Already a reference, nothing to do.
// Take the address of the value if possible.
if (value.source().type() != ExprValueSource::Type::kMemory) {
return Err(
"The initialization expression has no address (it's a temporary or optimized out)\n"
"to get a reference to.");
}
TargetPointer value_addr = value.source().address();
// Make the value containing the pointer data.
auto ref_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kReferenceType, concrete_type);
std::vector<uint8_t> ptr_data(sizeof(TargetPointer));
memcpy(ptr_data.data(), &value_addr, sizeof(TargetPointer));
return ExprValue(std::move(ref_type), std::move(ptr_data));
}
// Validates that the result value is a pointer type. Used for initialization to "auto*".
ErrOrValue EnsurePointer(const fxl::RefPtr<EvalContext> eval_context, const ExprValue& value) {
fxl::RefPtr<Type> concrete_type = eval_context->GetConcreteType(value.type());
if (!concrete_type)
return Err("Variable initialization expression produced no results.");
if (concrete_type->tag() != DwarfTag::kPointerType) {
return Err("Can't match non-pointer initization expression of type '" +
value.type()->GetFullName() + "' to 'auto*'.");
}
return value; // Return the same value on success.
}
} // namespace
std::string VariableDeclTypeInfo::ToString() const {
switch (kind) {
case kCAuto:
return "<C++-style auto>";
case kCAutoRef:
return "<C++-style auto&>";
case kCAutoPtr:
return "<C++-style auto*>";
case kRustAuto:
return "<Rust-style auto>";
case kExplicit:
return concrete_type->GetFullName();
}
return "<Error>";
}
// Decodes any auto type specifiers for the variable declaration of the given type.
ErrOr<VariableDeclTypeInfo> GetVariableDeclTypeInfo(ExprLanguage lang,
fxl::RefPtr<Type> concrete_type) {
if (!concrete_type) {
switch (lang) {
case ExprLanguage::kRust:
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kRustAuto};
case ExprLanguage::kC:
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kCAuto};
}
}
if (lang == ExprLanguage::kRust)
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kExplicit,
.concrete_type = std::move(concrete_type)};
// Everything below here is for C which always requires a type name (even if it's "auto").
if (IsCAutoType(concrete_type.get()))
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kCAuto};
// On the concrete type, things like "const" will have been stripped so we can check for pointers
// and references directly.
if (auto modified = concrete_type->As<ModifiedType>()) {
if (modified->tag() == DwarfTag::kPointerType) {
if (IsCAutoType(modified->modified().Get()->As<Type>()))
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kCAutoPtr};
}
if (modified->tag() == DwarfTag::kReferenceType) {
if (IsCAutoType(modified->modified().Get()->As<Type>()))
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kCAutoRef};
}
}
if (HasAnyCAutoType(concrete_type.get()))
return Err("Only 'auto', 'auto*' and 'auto&' variable types are supported in the debugger.");
return VariableDeclTypeInfo{.kind = VariableDeclTypeInfo::kExplicit,
.concrete_type = std::move(concrete_type)};
}
void EmitVariableInitializerOps(const VariableDeclTypeInfo& decl_info, uint32_t local_slot,
fxl::RefPtr<ExprNode> init_expr, VmStream& stream) {
// Evaluate the init expression.
if (init_expr) {
// Have an init expression, evaluate it.
if (decl_info.kind == VariableDeclTypeInfo::kCAuto) {
// In C++, "auto" expands the value of a reference, not the reference type itself.
init_expr->EmitBytecodeExpandRef(stream);
} else {
init_expr->EmitBytecode(stream);
}
} else {
// No init expression, we must have a concrete type.
FX_DCHECK(decl_info.kind == VariableDeclTypeInfo::kExplicit);
// Default-initialize a variable of the requested type. Our default initialization is 0's.
size_t byte_size = decl_info.concrete_type->byte_size();
stream.push_back(
VmOp::MakeLiteral(ExprValue(decl_info.concrete_type, std::vector<uint8_t>(byte_size, 0))));
}
// Convert / valudate the result type.
switch (decl_info.kind) {
case VariableDeclTypeInfo::kCAuto:
case VariableDeclTypeInfo::kRustAuto:
// These get stored directly (any references will have already been stripped for C++).
// Break out to continue storing.
break;
case VariableDeclTypeInfo::kCAutoRef:
stream.push_back(VmOp::MakeCallback1(&ConvertToReference));
break;
case VariableDeclTypeInfo::kCAutoPtr:
stream.push_back(VmOp::MakeCallback1(&EnsurePointer));
break;
case VariableDeclTypeInfo::kExplicit:
// Cast the result of the expression to the desired result type.
stream.push_back(
VmOp::MakeAsyncCallback1([decl_info](const fxl::RefPtr<EvalContext>& eval_context,
ExprValue value, EvalCallback cb) {
CastExprValue(eval_context, CastType::kImplicit, value, decl_info.concrete_type,
ExprValueSource(), std::move(cb));
}));
break;
}
// The variable value is now on the stack. We need one copy to save as a local, the other
// copy to leave on the stack as the "result" of this expression.
stream.push_back(VmOp::MakeDup());
stream.push_back(VmOp::MakeSetLocal(local_slot));
}
} // namespace zxdb