| // 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 |