[gidl][hlcpp] Enable more handle tests for HLCPP

This CL enables more GIDL handle tests for HLCPP that were omitted in
Iabf87cf877b31cd33c50622888f098c97fab5449. To make this work:

* fidlgen_hlcpp now emits `#ifdef`s based on the `resource` modifier
  instead of the `is_resource` typeshape field. This is needed because
  the code for `flexible resource union Foo {};` has a method taking
  `zx::handle`, but the typeshape field is false.

* GIDL now emits `#ifdef`s in HLCPP when the top-level type is resource
  (as before) or when handle_defs is nonempty (new). This is needed for
  decode_failure tests on value types rejecting handles.

The first change is accomplished using .IsResourceType from the embedded
Resourceness struct (represents the modifier) instead of the .IsResource
bool copied from the typeshape. This similar naming is confusing, but it
will be resolved soon when the typeshape field is removed in
I2e2ba2035f8e76004c2d4b9e64638501844b2761 (currently blocked).

This also removes xunion_handles_unittest.cc since all its tests are now
covered by union.gidl.

Test: fx test fidl-hlcpp-conformance-test
Test: fx test fidl_cpp_host_conformance_test
Bug: 36441
Bug: 62576
Change-Id: I643537a0b12c2d1480e734f31377c0ac356ee266
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/443451
Commit-Queue: Mitchell Kember <mkember@google.com>
Reviewed-by: Felix Zhu <fcz@google.com>
Testability-Review: Felix Zhu <fcz@google.com>
diff --git a/garnet/go/src/fidl/compiler/backend/cpp/ir.go b/garnet/go/src/fidl/compiler/backend/cpp/ir.go
index 8566329..8ce82a2 100644
--- a/garnet/go/src/fidl/compiler/backend/cpp/ir.go
+++ b/garnet/go/src/fidl/compiler/backend/cpp/ir.go
@@ -257,7 +257,7 @@
 }
 
 type Table struct {
-	types.Attributes
+	types.Table
 	Namespace      string
 	Name           string
 	TableType      string
@@ -302,7 +302,7 @@
 }
 
 type Struct struct {
-	types.Attributes
+	types.Struct
 	Namespace     string
 	Name          string
 	TableType     string
@@ -1314,7 +1314,7 @@
 	name := c.compileCompoundIdentifier(val.Name, "", appendNamespace, false)
 	tableType := c.compileTableType(val.Name)
 	r := Struct{
-		Attributes:   val.Attributes,
+		Struct:       val,
 		Namespace:    c.namespace,
 		Name:         name,
 		TableType:    tableType,
@@ -1409,7 +1409,7 @@
 	name := c.compileCompoundIdentifier(val.Name, "", appendNamespace, false)
 	tableType := c.compileTableType(val.Name)
 	r := Table{
-		Attributes:     val.Attributes,
+		Table:          val,
 		Namespace:      c.namespace,
 		Name:           name,
 		TableType:      tableType,
diff --git a/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.cc.golden b/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.cc.golden
index aa642b6..646e7a7 100644
--- a/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.cc.golden
+++ b/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.cc.golden
@@ -7,6 +7,7 @@
 namespace test {
 namespace error {
 
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t fidl_test_error_Example_foo_ResponseTable;
 const fidl_type_t* Example_foo_Response::FidlType =
     &fidl_test_error_Example_foo_ResponseTable;
@@ -36,6 +37,9 @@
   if (_status != ZX_OK) return _status;
   return ZX_OK;
 }
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t fidl_test_error_Example_foo_ResultTable;
 const fidl_type_t* Example_foo_Result::FidlType =
     &fidl_test_error_Example_foo_ResultTable;
@@ -215,6 +219,8 @@
     }
   }
 }
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 namespace {
 
diff --git a/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.h.golden b/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.h.golden
index f6c28ee..b392745 100644
--- a/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.h.golden
+++ b/garnet/go/src/fidl/compiler/backend/goldens/error.test.json.h.golden
@@ -27,6 +27,7 @@
 }  // namespace internal
 #endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class Example_foo_Response final {
  public:
   static const fidl_type_t* FidlType;
@@ -57,7 +58,9 @@
 }
 
 using Example_foo_ResponsePtr = ::std::unique_ptr<Example_foo_Response>;
+#endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class Example_foo_Result final {
  public:
   static const fidl_type_t* FidlType;
@@ -161,6 +164,8 @@
 }
 
 using Example_foo_ResultPtr = ::std::unique_ptr<Example_foo_Result>;
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 
 class Example {
@@ -335,6 +340,7 @@
 }  // namespace fidl
 namespace fidl {
 
+#ifdef __Fuchsia__
 template <>
 struct CodingTraits<::fidl::test::error::Example_foo_Response>
     : public EncodableCodingTraits<::fidl::test::error::Example_foo_Response,
@@ -361,6 +367,9 @@
     return true;
   }
 };
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 template <>
 struct IsFidlXUnion<::fidl::test::error::Example_foo_Result>
     : public std::true_type {};
@@ -428,4 +437,5 @@
     }
   }
 };
+#endif  // __Fuchsia__
 }  // namespace fidl
diff --git a/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.cc.golden b/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.cc.golden
index 4789665..a5e60b0 100644
--- a/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.cc.golden
+++ b/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.cc.golden
@@ -7,6 +7,7 @@
 namespace test {
 namespace protocols {
 
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ResponseAsStruct_ResponseTable;
 const fidl_type_t* WithErrorSyntax_ResponseAsStruct_Response::FidlType =
@@ -52,6 +53,9 @@
   if (_status != ZX_OK) return _status;
   return ZX_OK;
 }
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ResponseAsStruct_ResultTable;
 const fidl_type_t* WithErrorSyntax_ResponseAsStruct_Result::FidlType =
@@ -255,6 +259,9 @@
     }
   }
 }
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ErrorAsPrimitive_ResponseTable;
 const fidl_type_t* WithErrorSyntax_ErrorAsPrimitive_Response::FidlType =
@@ -292,6 +299,9 @@
   if (_status != ZX_OK) return _status;
   return ZX_OK;
 }
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ErrorAsPrimitive_ResultTable;
 const fidl_type_t* WithErrorSyntax_ErrorAsPrimitive_Result::FidlType =
@@ -495,6 +505,9 @@
     }
   }
 }
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ErrorAsEnum_ResponseTable;
 const fidl_type_t* WithErrorSyntax_ErrorAsEnum_Response::FidlType =
@@ -530,6 +543,8 @@
   if (_status != ZX_OK) return _status;
   return ZX_OK;
 }
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 namespace {
 
@@ -1624,6 +1639,7 @@
 
 #endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 extern "C" const fidl_type_t
     fidl_test_protocols_WithErrorSyntax_ErrorAsEnum_ResultTable;
 const fidl_type_t* WithErrorSyntax_ErrorAsEnum_Result::FidlType =
@@ -1829,6 +1845,8 @@
     }
   }
 }
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 namespace {
 
diff --git a/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.h.golden b/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.h.golden
index 6735ce4..00b6daf 100644
--- a/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.h.golden
+++ b/garnet/go/src/fidl/compiler/backend/goldens/protocols.test.json.h.golden
@@ -138,6 +138,7 @@
 }  // namespace internal
 #endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ResponseAsStruct_Response final {
  public:
   static const fidl_type_t* FidlType;
@@ -178,7 +179,9 @@
 
 using WithErrorSyntax_ResponseAsStruct_ResponsePtr =
     ::std::unique_ptr<WithErrorSyntax_ResponseAsStruct_Response>;
+#endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ResponseAsStruct_Result final {
  public:
   static const fidl_type_t* FidlType;
@@ -299,7 +302,9 @@
 
 using WithErrorSyntax_ResponseAsStruct_ResultPtr =
     ::std::unique_ptr<WithErrorSyntax_ResponseAsStruct_Result>;
+#endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ErrorAsPrimitive_Response final {
  public:
   static const fidl_type_t* FidlType;
@@ -335,7 +340,9 @@
 
 using WithErrorSyntax_ErrorAsPrimitive_ResponsePtr =
     ::std::unique_ptr<WithErrorSyntax_ErrorAsPrimitive_Response>;
+#endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ErrorAsPrimitive_Result final {
  public:
   static const fidl_type_t* FidlType;
@@ -452,7 +459,9 @@
 
 using WithErrorSyntax_ErrorAsPrimitive_ResultPtr =
     ::std::unique_ptr<WithErrorSyntax_ErrorAsPrimitive_Result>;
+#endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ErrorAsEnum_Response final {
  public:
   static const fidl_type_t* FidlType;
@@ -484,6 +493,8 @@
 
 using WithErrorSyntax_ErrorAsEnum_ResponsePtr =
     ::std::unique_ptr<WithErrorSyntax_ErrorAsEnum_Response>;
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 
 class Transitional {
@@ -1402,6 +1413,7 @@
 };
 #endif  // __Fuchsia__
 
+#ifdef __Fuchsia__
 class WithErrorSyntax_ErrorAsEnum_Result final {
  public:
   static const fidl_type_t* FidlType;
@@ -1513,6 +1525,8 @@
 
 using WithErrorSyntax_ErrorAsEnum_ResultPtr =
     ::std::unique_ptr<WithErrorSyntax_ErrorAsEnum_Result>;
+#endif  // __Fuchsia__
+
 #ifdef __Fuchsia__
 
 class WithErrorSyntax {
@@ -1836,6 +1850,7 @@
   }
 };
 
+#ifdef __Fuchsia__
 template <>
 struct CodingTraits<
     ::fidl::test::protocols::WithErrorSyntax_ResponseAsStruct_Response>
@@ -1879,6 +1894,9 @@
     return true;
   }
 };
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 template <>
 struct IsFidlXUnion<
     ::fidl::test::protocols::WithErrorSyntax_ResponseAsStruct_Result>
@@ -1964,6 +1982,9 @@
     }
   }
 };
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 template <>
 struct CodingTraits<
     ::fidl::test::protocols::WithErrorSyntax_ErrorAsPrimitive_Response>
@@ -2001,6 +2022,9 @@
     return true;
   }
 };
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 template <>
 struct IsFidlXUnion<
     ::fidl::test::protocols::WithErrorSyntax_ErrorAsPrimitive_Result>
@@ -2086,6 +2110,9 @@
     }
   }
 };
+#endif  // __Fuchsia__
+
+#ifdef __Fuchsia__
 template <>
 struct CodingTraits<
     ::fidl::test::protocols::WithErrorSyntax_ErrorAsEnum_Response>
@@ -2118,6 +2145,8 @@
     return true;
   }
 };
+#endif  // __Fuchsia__
+
 template <>
 struct CodingTraits<::fidl::test::protocols::ErrorEnun> {
   static constexpr size_t inline_size_old =
@@ -2149,6 +2178,7 @@
   }
 };
 
+#ifdef __Fuchsia__
 template <>
 struct IsFidlXUnion<::fidl::test::protocols::WithErrorSyntax_ErrorAsEnum_Result>
     : public std::true_type {};
@@ -2226,4 +2256,5 @@
     }
   }
 };
+#endif  // __Fuchsia__
 }  // namespace fidl
diff --git a/sdk/lib/fidl/cpp/BUILD.gn b/sdk/lib/fidl/cpp/BUILD.gn
index 184ec48..8b7134b 100644
--- a/sdk/lib/fidl/cpp/BUILD.gn
+++ b/sdk/lib/fidl/cpp/BUILD.gn
@@ -219,7 +219,6 @@
     "internal/stub_controller_unittest.cc",
     "synchronous_interface_ptr_unittest.cc",
     "thread_safe_binding_set_unittest.cc",
-    "xunion_handles_unittest.cc",
   ]
 
   deps = [
diff --git a/sdk/lib/fidl/cpp/test/test_util.h b/sdk/lib/fidl/cpp/test/test_util.h
index 0f7c026..689258b 100644
--- a/sdk/lib/fidl/cpp/test/test_util.h
+++ b/sdk/lib/fidl/cpp/test/test_util.h
@@ -108,8 +108,10 @@
 }
 
 template <class Output>
-void CheckDecodeFailure(std::vector<uint8_t> input, const zx_status_t expected_failure_code) {
-  Message message(BytePart(input.data(), input.capacity(), input.size()), HandlePart());
+void CheckDecodeFailure(std::vector<uint8_t> input, std::vector<zx_handle_t> handles,
+                        const zx_status_t expected_failure_code) {
+  Message message(BytePart(input.data(), input.capacity(), input.size()),
+                  HandlePart(handles.data(), handles.capacity(), handles.size()));
 
   const char* error = nullptr;
   EXPECT_EQ(expected_failure_code, message.Decode(Output::FidlType, &error)) << error;
diff --git a/sdk/lib/fidl/cpp/xunion_handles_unittest.cc b/sdk/lib/fidl/cpp/xunion_handles_unittest.cc
deleted file mode 100644
index 32d55316..0000000
--- a/sdk/lib/fidl/cpp/xunion_handles_unittest.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-// 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 <lib/fidl/cpp/comparison.h>
-#include <lib/fidl/cpp/test/test_util.h>
-#include <zircon/status.h>
-#include <zircon/types.h>
-
-#include <vector>
-
-#include <fidl/test/unionmigration/cpp/fidl.h>
-#include <gtest/gtest.h>
-
-namespace {
-
-class HandleChecker {
- public:
-  HandleChecker() = default;
-
-  size_t size() const { return events_.size(); }
-
-  void AddEvent(zx_handle_t event) {
-    zx_handle_t new_event;
-    ASSERT_EQ(zx_handle_duplicate(event, ZX_RIGHT_SAME_RIGHTS, &new_event), ZX_OK);
-    events_.emplace_back(zx::event(new_event));
-  }
-
-  void CheckEvents() {
-    for (size_t i = 0; i < events_.size(); ++i) {
-      zx_info_handle_count_t info = {};
-      auto status =
-          events_[i].get_info(ZX_INFO_HANDLE_COUNT, &info, sizeof(info), nullptr, nullptr);
-      ZX_ASSERT(status == ZX_OK);
-      EXPECT_EQ(info.handle_count, 1U) << "Handle not freed " << (i + 1) << '/' << events_.size();
-    }
-  }
-
- private:
-  std::vector<zx::event> events_;
-};
-
-}  // namespace
-
-// TODO(fxbug.dev/60033): These tests can be moved to GIDL once handles are
-// supported on host
-namespace fidl {
-namespace {
-
-// Tests that decoding unknown handles for a non-resource type fails, and closes
-// handles.
-TEST(XUnion, UnknownHandlesValue) {
-  using fidl::test::unionmigration::BasicXUnionStruct;
-
-  std::vector<uint8_t> bytes = {
-      0x11, 0xba, 0x5e, 0xba, 0x00, 0x00, 0x00, 0x00,  // invalid ordinal + padding
-      0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,  // envelope: 8 bytes, 3 handles
-      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope: data is present
-      0xde, 0xad, 0xbe, 0xef, 0x5c, 0xa1, 0xab, 0x1e,  // fake out-of-line dat
-  };
-
-  zx_handle_t h1, h2, h3;
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h1));
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h2));
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h3));
-  std::vector<zx_handle_t> handles = {h1, h2, h3};
-
-  HandleChecker checker;
-  checker.AddEvent(h1);
-  checker.AddEvent(h2);
-  checker.AddEvent(h3);
-
-  Message message(BytePart(bytes.data(), bytes.size(), bytes.size()),
-                  HandlePart(handles.data(), handles.size(), handles.size()));
-  const char* error;
-  auto status = message.Decode(BasicXUnionStruct::FidlType, &error);
-  ASSERT_EQ(status, ZX_ERR_INVALID_ARGS);
-  ASSERT_STREQ(error, "received unknown handles for a non-resource type");
-
-  checker.CheckEvents();
-}
-
-// Tests that decoding unknown bytes for a resource type succeeds
-TEST(XUnion, UnknownBytesResource) {
-  using fidl::test::unionmigration::BasicResourceXUnion;
-  using fidl::test::unionmigration::BasicResourceXUnionStruct;
-
-  std::vector<uint8_t> bytes = {
-      0x11, 0xba, 0x5e, 0xba, 0x00, 0x00, 0x00, 0x00,  // invalid ordinal + padding
-      0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // envelope: 8 bytes, 0 handles
-      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope: data is present
-      0xde, 0xad, 0xbe, 0xef, 0x5c, 0xa1, 0xab, 0x1e,  // fake out-of-line dat
-  };
-
-  Message message(BytePart(bytes.data(), bytes.size(), bytes.size()), HandlePart());
-  auto status = message.Decode(BasicResourceXUnionStruct::FidlType, nullptr);
-  ASSERT_EQ(status, ZX_OK);
-
-  fidl::Decoder decoder(std::move(message));
-  BasicResourceXUnionStruct result;
-  BasicResourceXUnionStruct::Decode(&decoder, &result, 0);
-
-  const BasicResourceXUnion& xu = result.val;
-  auto actual_bytes = xu.UnknownBytes();
-  auto unknown_data = std::vector<uint8_t>(bytes.cbegin() + sizeof(fidl_xunion_t), bytes.cend());
-  EXPECT_TRUE(fidl::test::util::cmp_payload(actual_bytes->data(), actual_bytes->size(),
-                                            unknown_data.data(), unknown_data.size()));
-}
-
-// Tests that decoding unknown bytes for a value type succeeds
-TEST(XUnion, UnknownBytesValue) {
-  using fidl::test::unionmigration::BasicXUnion;
-  using fidl::test::unionmigration::BasicXUnionStruct;
-
-  std::vector<uint8_t> bytes = {
-      0x11, 0xba, 0x5e, 0xba, 0x00, 0x00, 0x00, 0x00,  // invalid ordinal + padding
-      0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // envelope: 8 bytes, 0 handles
-      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope: data is present
-      0xde, 0xad, 0xbe, 0xef, 0x5c, 0xa1, 0xab, 0x1e,  // fake out-of-line dat
-  };
-
-  Message message(BytePart(bytes.data(), bytes.size(), bytes.size()), HandlePart());
-  auto status = message.Decode(BasicXUnionStruct::FidlType, nullptr);
-  ASSERT_EQ(status, ZX_OK);
-
-  fidl::Decoder decoder(std::move(message));
-  BasicXUnionStruct result;
-  BasicXUnionStruct::Decode(&decoder, &result, 0);
-
-  const BasicXUnion& xu = result.val;
-  auto actual_bytes = xu.UnknownBytes();
-  auto unknown_data = std::vector<uint8_t>(bytes.cbegin() + sizeof(fidl_xunion_t), bytes.cend());
-  EXPECT_TRUE(fidl::test::util::cmp_payload(actual_bytes->data(), actual_bytes->size(),
-                                            unknown_data.data(), unknown_data.size()));
-}
-
-// Tests that decoding and re-encoding unknown handles for a resource type succeeds
-TEST(XUnion, UnknownHandlesResource) {
-  using fidl::test::unionmigration::BasicResourceXUnion;
-  using fidl::test::unionmigration::BasicResourceXUnionStruct;
-
-  std::vector<uint8_t> bytes = {
-      0x11, 0xba, 0x5e, 0xba, 0x00, 0x00, 0x00, 0x00,  // invalid ordinal + padding
-      0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,  // envelope: 8 bytes, 3 handles
-      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope: data is present
-      0xde, 0xad, 0xbe, 0xef, 0x5c, 0xa1, 0xab, 0x1e,  // fake out-of-line dat
-  };
-  auto unknown_data = std::vector<uint8_t>(bytes.cbegin() + sizeof(fidl_xunion_t), bytes.cend());
-
-  zx_handle_t h1, h2, h3;
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h1));
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h2));
-  ASSERT_EQ(ZX_OK, zx_event_create(0, &h3));
-  std::vector<zx_handle_t> handles = {h1, h2, h3};
-
-  auto result = test::util::DecodedBytes<BasicResourceXUnionStruct>(bytes, handles);
-  const BasicResourceXUnion& xu = result.val;
-
-  // compare
-  auto actual_bytes = xu.UnknownBytes();
-  EXPECT_TRUE(test::util::cmp_payload(actual_bytes->data(), actual_bytes->size(),
-                                      unknown_data.data(), unknown_data.size()));
-
-  auto actual_handles = xu.UnknownHandles();
-  ASSERT_EQ(actual_handles->size(), 3ul);
-  for (int i = 0; i < 3; ++i) {
-    EXPECT_EQ(handles[i], (*actual_handles)[i].get());
-  }
-
-  EXPECT_TRUE(test::util::ValueToBytes(std::move(result), bytes, handles));
-}
-
-}  // namespace
-}  // namespace fidl
diff --git a/src/tests/fidl/conformance_suite/conformance.gidl b/src/tests/fidl/conformance_suite/conformance.gidl
index ab8007b..3008ddb 100644
--- a/src/tests/fidl/conformance_suite/conformance.gidl
+++ b/src/tests/fidl/conformance_suite/conformance.gidl
@@ -256,7 +256,7 @@
 decode_success("ResourceTableUnknownReservedDropped") {
     // TODO(fxbug.dev/61848): Move these to ResourceTableUnknownReservedStored
     // and delete this test.
-    bindings_allowlist = [llcpp,rust],
+    bindings_allowlist = [hlcpp,llcpp,rust],
     bytes = {
         v1 = [
             5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
@@ -386,7 +386,7 @@
 decode_success("ResourceTableUnknownTrailingDropped") {
     // TODO(fxbug.dev/61848): Move these to ResourceTableUnknownTrailingStored
     // and delete this test.
-    bindings_allowlist = [llcpp,rust],
+    bindings_allowlist = [hlcpp,llcpp,rust],
     bytes = {
         v1 = [
             6, 0, 0, 0, 0, 0, 0, 0, // max ordinal
@@ -489,7 +489,7 @@
 decode_success("ValueTableUnknownTrailingHandlesDropped") {
     // TODO(fxbug.dev/61848): Move these to ValueTableUnknownTrailingHandlesRejected
     // and delete this test.
-    bindings_allowlist = [rust],
+    bindings_allowlist = [hlcpp,rust],
     handle_defs = {
         #0 = event(),
         #1 = event(),
@@ -524,7 +524,7 @@
 decode_success("ResourceTableUnknownTrailingHandlesDropped") {
     // TODO(fxbug.dev/61848): Move these to ResourceTableUnknownTrailingHandlesStored
     // and delete this test.
-    bindings_allowlist = [rust],
+    bindings_allowlist = [hlcpp,rust],
     handle_defs = {
         #0 = event(),
         #1 = event(),
@@ -1344,7 +1344,7 @@
 // https://fuchsia.googlesource.com/fidl-misc/+/HEAD/fidlviz/
 success("FidlvizDemo") {
     // TODO(fxbug.dev/36441): Implement handles in all backends.
-    bindings_allowlist = [dart,go,rust],
+    bindings_denylist = [llcpp],
     handle_defs = {
         #0 = event(),
     },
diff --git a/src/tests/fidl/conformance_suite/mix_and_match.test.fidl b/src/tests/fidl/conformance_suite/mix_and_match.test.fidl
index 8af1723..3b8c0cd 100644
--- a/src/tests/fidl/conformance_suite/mix_and_match.test.fidl
+++ b/src/tests/fidl/conformance_suite/mix_and_match.test.fidl
@@ -65,7 +65,6 @@
     3: SimpleTable st;
 };
 
-[BindingsDenylist = "hlcpp"]
 flexible resource union SampleResourceXUnion {
     1: uint32 u;
     2: SimpleUnion su;
@@ -98,13 +97,12 @@
     SampleXUnion xu;
 };
 
-[BindingsDenylist = "hlcpp"]
 resource struct TestFlexibleResourceXUnionInStruct {
     SampleResourceXUnion xu;
 };
 
 // TODO(fxbug.dev/36441): Allow bindings to compile on host with handles.
-[BindingsDenylist = "hlcpp, llcpp"]
+[BindingsDenylist = "llcpp"]
 resource struct FidlvizDemo {
     uint8 f1;
     int8 f2;
diff --git a/src/tests/fidl/conformance_suite/tables.test.fidl b/src/tests/fidl/conformance_suite/tables.test.fidl
index c25d9ec..debb1f5 100644
--- a/src/tests/fidl/conformance_suite/tables.test.fidl
+++ b/src/tests/fidl/conformance_suite/tables.test.fidl
@@ -16,7 +16,6 @@
     SimpleTable table;
 };
 
-[BindingsDenylist = "hlcpp"]
 resource table SimpleResourceTable {
     1: int64 x;
     2: reserved;
@@ -25,7 +24,6 @@
     5: int64 y;
 };
 
-[BindingsDenylist = "hlcpp"]
 resource struct StructOfSimpleResourceTable {
     SimpleResourceTable table;
 };
diff --git a/src/tests/fidl/conformance_suite/union.gidl b/src/tests/fidl/conformance_suite/union.gidl
index 51393c1..078bdf0 100644
--- a/src/tests/fidl/conformance_suite/union.gidl
+++ b/src/tests/fidl/conformance_suite/union.gidl
@@ -96,6 +96,7 @@
 success("FlexibleResourceUnionUnknownValue") {
     // llcpp is tested by hand since it cannot construct unknown unions (and
     // they do not store bytes & handles), see fxr/423117
+    // TODO(fxbug.dev/62819): Fix crash in HLCPP.
     bindings_denylist = [llcpp,hlcpp],
     value = TestFlexibleResourceXUnionInStruct {
         xu: SampleResourceXUnion{
@@ -160,7 +161,8 @@
 }
 
 decode_failure("StrictUnionUnknownValueWithHandles") {
-    bindings_allowlist = [dart,go,rust],
+    // TODO(fxbug.dev/36441): Implement handles in all backends.
+    bindings_denylist = [llcpp],
     handle_defs = {
         #0 = event(),
         #1 = event(),
@@ -186,7 +188,7 @@
 decode_failure("FlexibleUnionUnknownValueWithHandles") {
     // TODO(fxbug.dev/62772): Add bindings here as they properly implement the
     // value/resource distinction for flexible types.
-    bindings_allowlist = [],
+    bindings_allowlist = [hlcpp],
     handle_defs = {
         #0 = event(),
         #1 = event(),
diff --git a/tools/fidl/fidlgen_hlcpp/codegen/struct.tmpl.go b/tools/fidl/fidlgen_hlcpp/codegen/struct.tmpl.go
index 860a99f..3f4d84f 100644
--- a/tools/fidl/fidlgen_hlcpp/codegen/struct.tmpl.go
+++ b/tools/fidl/fidlgen_hlcpp/codegen/struct.tmpl.go
@@ -12,7 +12,7 @@
 {{/* TODO(fxbug.dev/36441): Remove __Fuchsia__ ifdefs once we have non-Fuchsia
      emulated handles for C++. */}}
 {{- define "StructDeclaration" }}
-{{ if .IsResource }}
+{{ if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 {{- range .DocComments }}
@@ -71,14 +71,14 @@
 }
 
 using {{ .Name }}Ptr = ::std::unique_ptr<{{ .Name }}>;
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "StructDefinition" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 extern "C" const fidl_type_t {{ .TableType }};
@@ -113,14 +113,14 @@
   {{- end }}
   return ZX_OK;
 }
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "StructTraits" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 template <>
@@ -158,7 +158,7 @@
     return true;
   }
 };
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
diff --git a/tools/fidl/fidlgen_hlcpp/codegen/table.tmpl.go b/tools/fidl/fidlgen_hlcpp/codegen/table.tmpl.go
index c4e33d4..6d381c2 100644
--- a/tools/fidl/fidlgen_hlcpp/codegen/table.tmpl.go
+++ b/tools/fidl/fidlgen_hlcpp/codegen/table.tmpl.go
@@ -12,7 +12,7 @@
 {{/* TODO(fxbug.dev/36441): Remove __Fuchsia__ ifdefs once we have non-Fuchsia
      emulated handles for C++. */}}
 {{- define "TableDeclaration" }}
-{{ if .IsResource }}
+{{ if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 {{- range .DocComments }}
@@ -101,14 +101,14 @@
 };
 
 using {{ .Name }}Ptr = ::std::unique_ptr<{{ .Name }}>;
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "TableDefinition" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 extern "C" const fidl_type_t {{ .TableType }};
@@ -228,14 +228,14 @@
   {{- end }}
   return ZX_OK;
 }
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "TableTraits" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 template <>
@@ -264,7 +264,7 @@
     return true;
   }
 };
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
diff --git a/tools/fidl/fidlgen_hlcpp/codegen/union.tmpl.go b/tools/fidl/fidlgen_hlcpp/codegen/union.tmpl.go
index b563c03..4f85309 100644
--- a/tools/fidl/fidlgen_hlcpp/codegen/union.tmpl.go
+++ b/tools/fidl/fidlgen_hlcpp/codegen/union.tmpl.go
@@ -12,7 +12,7 @@
 {{/* TODO(fxbug.dev/36441): Remove __Fuchsia__ ifdefs once we have non-Fuchsia
      emulated handles for C++. */}}
 {{- define "UnionDeclaration" }}
-{{ if .IsResource }}
+{{ if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 {{- range .DocComments }}
@@ -198,14 +198,14 @@
 }
 
 using {{ .Name }}Ptr = ::std::unique_ptr<{{ .Name }}>;
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "UnionDefinition" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 extern "C" const fidl_type_t {{ .TableType }};
@@ -432,14 +432,14 @@
     }
   }
 }
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
 {{- end }}
 
 {{- define "UnionTraits" }}
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #ifdef __Fuchsia__
 {{- end }}
 template <>
@@ -507,7 +507,7 @@
     {{end -}}
   }
 };
-{{- if .IsResource }}
+{{- if .IsResourceType }}
 #endif  // __Fuchsia__
 {{ end }}
 
diff --git a/tools/fidl/gidl/hlcpp/builder.go b/tools/fidl/gidl/hlcpp/builder.go
index ceee476..20c10dd 100644
--- a/tools/fidl/gidl/hlcpp/builder.go
+++ b/tools/fidl/gidl/hlcpp/builder.go
@@ -161,9 +161,16 @@
 
 		if field.Key.IsUnknown() {
 			unknownData := field.Value.(gidlir.UnknownData)
-			b.Builder.WriteString(fmt.Sprintf(
-				"%s%s_experimental_set_unknown_data(static_cast<fidl_xunion_tag_t>(%dlu), %s);\n",
-				containerVar, accessor, field.Key.UnknownOrdinal, buildBytes(unknownData.Bytes)))
+			if decl.IsResourceType() {
+				b.Builder.WriteString(fmt.Sprintf(
+					"%s%s_experimental_set_unknown_data(static_cast<fidl_xunion_tag_t>(%dlu), %s, %s);\n",
+					containerVar, accessor, field.Key.UnknownOrdinal, buildBytes(unknownData.Bytes),
+					buildHandles(unknownData.Handles)))
+			} else {
+				b.Builder.WriteString(fmt.Sprintf(
+					"%s%s_experimental_set_unknown_data(static_cast<fidl_xunion_tag_t>(%dlu), %s);\n",
+					containerVar, accessor, field.Key.UnknownOrdinal, buildBytes(unknownData.Bytes)))
+			}
 			continue
 		}
 
@@ -274,3 +281,49 @@
 		panic(fmt.Sprintf("unexpected subtype %s", subtype))
 	}
 }
+
+func buildBytes(bytes []byte) string {
+	var builder strings.Builder
+	builder.WriteString("std::vector<uint8_t>{")
+	for i, b := range bytes {
+		builder.WriteString(fmt.Sprintf("0x%02x,", b))
+		if i%8 == 7 {
+			builder.WriteString("\n")
+		}
+	}
+	builder.WriteString("}")
+	return builder.String()
+}
+
+func buildRawHandles(handles []gidlir.Handle) string {
+	if len(handles) == 0 {
+		return "std::vector<zx_handle_t>{}"
+	}
+	var builder strings.Builder
+	builder.WriteString("std::vector<zx_handle_t>{\n")
+	for i, h := range handles {
+		builder.WriteString(fmt.Sprintf("handle_defs[%d],", h))
+		if i%8 == 7 {
+			builder.WriteString("\n")
+		}
+	}
+	builder.WriteString("}")
+	return builder.String()
+}
+
+func buildHandles(handles []gidlir.Handle) string {
+	if len(handles) == 0 {
+		return "std::vector<zx::handle>{}"
+	}
+	var builder strings.Builder
+	// Initializer-list vectors only work for copyable types. zx::handle has no
+	// copy constructor, so we use an immediately-invoked lambda instead.
+	builder.WriteString("([&handle_defs] {\n")
+	builder.WriteString("std::vector<zx::handle> v;\n")
+	for _, h := range handles {
+		builder.WriteString(fmt.Sprintf("v.emplace_back(handle_defs[%d]);\n", h))
+	}
+	builder.WriteString("return v;\n")
+	builder.WriteString("})()")
+	return builder.String()
+}
diff --git a/tools/fidl/gidl/hlcpp/conformance.go b/tools/fidl/gidl/hlcpp/conformance.go
index f3f87042..a03658c 100644
--- a/tools/fidl/gidl/hlcpp/conformance.go
+++ b/tools/fidl/gidl/hlcpp/conformance.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"fmt"
-	"strings"
 	"text/template"
 
 	fidlcommon "go.fuchsia.dev/fuchsia/garnet/go/src/fidl/compiler/backend/common"
@@ -63,7 +62,7 @@
 	auto bytes = {{ .Bytes }};
 	auto handles = {{ .Handles }};
 	EXPECT_TRUE(fidl::Equals(
-		fidl::test::util::DecodedBytes<{{ .ValueType }}>(bytes, handles),
+		fidl::test::util::DecodedBytes<{{ .ValueType }}>(std::move(bytes), std::move(handles)),
 		{{ .ValueVar }}));
 }
 {{- if .FuchsiaOnly }}
@@ -102,7 +101,8 @@
 	const auto handle_defs = {{ .HandleDefs }};
 	{{- end }}
 	auto bytes = {{ .Bytes }};
-	fidl::test::util::CheckDecodeFailure<{{ .ValueType }}>(bytes, {{ .ErrorCode }});
+	auto handles = {{ .Handles }};
+	fidl::test::util::CheckDecodeFailure<{{ .ValueType }}>(std::move(bytes), std::move(handles), {{ .ErrorCode }});
 	{{- if .HandleDefs }}
 	for (const auto handle : handle_defs) {
 		EXPECT_EQ(ZX_ERR_BAD_HANDLE, zx_object_get_info(handle, ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
@@ -182,6 +182,7 @@
 		valueBuilder := newCppValueBuilder()
 		valueVar := valueBuilder.visit(encodeSuccess.Value, decl)
 		valueBuild := valueBuilder.String()
+		fuchsiaOnly := decl.IsResourceType() || len(encodeSuccess.HandleDefs) > 0
 		for _, encoding := range encodeSuccess.Encodings {
 			if !wireFormatSupported(encoding.WireFormat) {
 				continue
@@ -193,8 +194,8 @@
 				ValueVar:    valueVar,
 				ValueType:   declName(decl),
 				Bytes:       buildBytes(encoding.Bytes),
-				Handles:     buildHandles(encoding.Handles),
-				FuchsiaOnly: decl.IsResource(),
+				Handles:     buildRawHandles(encoding.Handles),
+				FuchsiaOnly: fuchsiaOnly,
 			})
 		}
 	}
@@ -211,6 +212,7 @@
 		valueBuilder := newCppValueBuilder()
 		valueVar := valueBuilder.visit(decodeSuccess.Value, decl)
 		valueBuild := valueBuilder.String()
+		fuchsiaOnly := decl.IsResourceType() || len(decodeSuccess.HandleDefs) > 0
 		for _, encoding := range decodeSuccess.Encodings {
 			if !wireFormatSupported(encoding.WireFormat) {
 				continue
@@ -222,8 +224,8 @@
 				ValueVar:    valueVar,
 				ValueType:   declName(decl),
 				Bytes:       buildBytes(encoding.Bytes),
-				Handles:     buildHandles(encoding.Handles),
-				FuchsiaOnly: decl.IsResource(),
+				Handles:     buildRawHandles(encoding.Handles),
+				FuchsiaOnly: fuchsiaOnly,
 			})
 		}
 	}
@@ -242,6 +244,7 @@
 		valueVar := valueBuilder.visit(encodeFailure.Value, decl)
 		valueBuild := valueBuilder.String()
 		errorCode := cppErrorCode(encodeFailure.Err)
+		fuchsiaOnly := decl.IsResourceType() || len(encodeFailure.HandleDefs) > 0
 		for _, wireFormat := range encodeFailure.WireFormats {
 			if !wireFormatSupported(wireFormat) {
 				continue
@@ -253,7 +256,7 @@
 				ValueVar:    valueVar,
 				ValueType:   declName(decl),
 				ErrorCode:   errorCode,
-				FuchsiaOnly: decl.IsResource(),
+				FuchsiaOnly: fuchsiaOnly,
 			})
 		}
 	}
@@ -269,6 +272,7 @@
 		}
 		valueType := cppConformanceType(decodeFailure.Type)
 		errorCode := cppErrorCode(decodeFailure.Err)
+		fuchsiaOnly := decl.IsResourceType() || len(decodeFailure.HandleDefs) > 0
 		for _, encoding := range decodeFailure.Encodings {
 			if !wireFormatSupported(encoding.WireFormat) {
 				continue
@@ -278,9 +282,9 @@
 				HandleDefs:  BuildHandleDefs(decodeFailure.HandleDefs),
 				ValueType:   valueType,
 				Bytes:       buildBytes(encoding.Bytes),
-				Handles:     buildHandles(encoding.Handles),
+				Handles:     buildRawHandles(encoding.Handles),
 				ErrorCode:   errorCode,
-				FuchsiaOnly: decl.IsResource(),
+				FuchsiaOnly: fuchsiaOnly,
 			})
 		}
 	}
@@ -304,32 +308,3 @@
 func cppConformanceType(gidlTypeString string) string {
 	return "conformance::" + gidlTypeString
 }
-
-func buildBytes(bytes []byte) string {
-	var builder strings.Builder
-	builder.WriteString("std::vector<uint8_t>{")
-	for i, b := range bytes {
-		builder.WriteString(fmt.Sprintf("0x%02x,", b))
-		if i%8 == 7 {
-			builder.WriteString("\n")
-		}
-	}
-	builder.WriteString("}")
-	return builder.String()
-}
-
-func buildHandles(handles []gidlir.Handle) string {
-	if len(handles) == 0 {
-		return "std::vector<zx_handle_t>{}"
-	}
-	var builder strings.Builder
-	builder.WriteString("std::vector<zx_handle_t>{\n")
-	for i, h := range handles {
-		builder.WriteString(fmt.Sprintf("handle_defs[%d],", h))
-		if i%8 == 7 {
-			builder.WriteString("\n")
-		}
-	}
-	builder.WriteString("}")
-	return builder.String()
-}
diff --git a/tools/fidl/gidl/mixer/mixer.go b/tools/fidl/gidl/mixer/mixer.go
index 437b95c9..2a0238b 100644
--- a/tools/fidl/gidl/mixer/mixer.go
+++ b/tools/fidl/gidl/mixer/mixer.go
@@ -174,6 +174,10 @@
 type RecordDeclaration interface {
 	NamedDeclaration
 
+	// IsResourceType returns true if the type is marked as a resource, meaning
+	// it may contain handles.
+	IsResourceType() bool
+
 	// AllFields returns the names of all fields in the type.
 	FieldNames() []string
 
@@ -407,7 +411,7 @@
 	return decl.nullable
 }
 
-func (decl *StructDecl) IsResource() bool {
+func (decl *StructDecl) IsResourceType() bool {
 	return decl.structDecl.IsResourceType()
 }
 
@@ -490,6 +494,10 @@
 	schema    Schema
 }
 
+func (decl *TableDecl) IsResourceType() bool {
+	return decl.tableDecl.IsResourceType()
+}
+
 func (decl *TableDecl) Name() string {
 	return string(decl.tableDecl.Name)
 }
@@ -567,6 +575,10 @@
 	return decl.nullable
 }
 
+func (decl *UnionDecl) IsResourceType() bool {
+	return decl.unionDecl.IsResourceType()
+}
+
 func (decl *UnionDecl) Name() string {
 	return string(decl.unionDecl.Name)
 }