Fix undefined behavior in BitStruct.

Fixes broken bit_struct_test and subtype_check_test.

Test: m test-art-host-gtest
      (tested on top of tree sync'd when they were broken).
Bug: 148125232

Change-Id: Iceee3ffc762ab28ea403d835b062766e5cbb088f
diff --git a/libartbase/base/bit_struct.h b/libartbase/base/bit_struct.h
index 292eca0..277e4b5 100644
--- a/libartbase/base/bit_struct.h
+++ b/libartbase/base/bit_struct.h
@@ -17,6 +17,9 @@
 #ifndef ART_LIBARTBASE_BASE_BIT_STRUCT_H_
 #define ART_LIBARTBASE_BASE_BIT_STRUCT_H_
 
+#include <type_traits>
+
+#include "base/casts.h"
 #include "bit_struct_detail.h"
 #include "bit_utils.h"
 
@@ -119,7 +122,7 @@
   template <typename _ = void,
             typename = std::enable_if_t<std::is_same<T, StorageType>::value, _>>
   explicit operator StorageType() const {
-    return GetStorage();
+    return BitFieldExtract(storage_, kBitOffset, kBitWidth);
   }
 
   BitStructField& operator=(T value) {
@@ -154,46 +157,27 @@
   }
 
   T Get() const {
-    ValueStorage vs;
-    vs.pod_.val_ = GetStorage();
-    return vs.value_;
+    ExtractionType storage = static_cast<ExtractionType>(storage_);
+    ExtractionType extracted = BitFieldExtract(storage, kBitOffset, kBitWidth);
+    ConversionType to_convert = dchecked_integral_cast<ConversionType>(extracted);
+    return ValueConverter::FromUnderlyingStorage(to_convert);
   }
 
   void Set(T value) {
-    ValueStorage value_as_storage;
-    value_as_storage.value_ = value;
-
-    storage_.pod_.val_ = BitFieldInsert(storage_.pod_.val_,
-                                        value_as_storage.pod_.val_,
-                                        kBitOffset,
-                                        kBitWidth);
+    ConversionType converted = ValueConverter::ToUnderlyingStorage(value);
+    ExtractionType extracted = dchecked_integral_cast<ExtractionType>(converted);
+    storage_ = BitFieldInsert(storage_, extracted, kBitOffset, kBitWidth);
   }
 
  private:
-  StorageType GetStorage() const {
-    return BitFieldExtract(storage_.pod_.val_, kBitOffset, kBitWidth);
-  }
+  using ValueConverter = detail::ValueConverter<T>;
+  using ConversionType = typename ValueConverter::StorageType;
+  using ExtractionType =
+      typename std::conditional<std::is_signed_v<ConversionType>,
+                                std::make_signed_t<StorageType>,
+                                StorageType>::type;
 
-  // Underlying value must be wrapped in a separate standard-layout struct.
-  // See below for more details.
-  struct PodWrapper {
-    StorageType val_;
-  };
-
-  union ValueStorage {
-    // Safely alias pod_ and value_ together.
-    //
-    // See C++ 9.5.1 [class.union]:
-    // If a standard-layout union contains several standard-layout structs that share a common
-    // initial sequence ... it is permitted to inspect the common initial sequence of any of
-    // standard-layout struct members.
-    PodWrapper pod_;
-    T value_;
-  } storage_;
-
-  // Future work: In theory almost non-standard layout can be supported here,
-  // assuming they don't rely on the address of (this).
-  // We just have to use memcpy since the union-aliasing would not work.
+  StorageType storage_;
 };
 
 // Base class for number-like BitStruct fields.
diff --git a/libartbase/base/bit_struct_detail.h b/libartbase/base/bit_struct_detail.h
index 60de1b6..380daa6 100644
--- a/libartbase/base/bit_struct_detail.h
+++ b/libartbase/base/bit_struct_detail.h
@@ -56,6 +56,50 @@
                               /* else */ type_unsigned>::type;
 };
 
+// Helper for converting to and from T to an integral type.
+template <typename T>
+union ValueConverter {
+  using StorageType = typename MinimumTypeHelper<T, sizeof(T) * kBitsPerByte>::type;
+
+  static constexpr StorageType ToUnderlyingStorage(T value) {
+    ValueConverter converter;
+    converter.value_.val_ = value;
+    return converter.storage_.val_;
+  }
+
+  static constexpr T FromUnderlyingStorage(StorageType storage) {
+    ValueConverter converter;
+    converter.storage_.val_ = storage;
+    return converter.value_.val_;
+  }
+
+  // Underlying values must be wrapped in separate standard-layout structs.
+  // See below for more details.
+  struct StorageWrapper {
+    StorageType val_;
+  };
+  struct ValueWrapper {
+    T val_;
+  };
+
+  // Safely alias pod_ and value_ together.
+  //
+  // See C++ 9.5.1 [class.union]:
+  // If a standard-layout union contains several standard-layout structs that share a common
+  // initial sequence ... it is permitted to inspect the common initial sequence of any of
+  // standard-layout struct members.
+  StorageWrapper storage_;
+  ValueWrapper value_;
+#if __cplusplus >= 202000L
+#error "When upgrading to C++20, remove this error and check that this is OK for all use cases."
+  static_assert(std::is_layout_compatible_v<StorageWrapper, ValueWrapper>);
+#endif
+
+  // Future work: In theory almost non-standard layout can be supported here,
+  // assuming they don't rely on the address of (this).
+  // We just have to use memcpy since the union-aliasing would not work.
+};
+
 // Denotes the beginning of a bit struct.
 //
 // This marker is required by the C++ standard in order to