Add *FreeMembers functions for out structs with non-value members

The function is called automatically in the dtor of the C++ structs.
The C++ structs are also made non-copyable to prevent a double-free.

Bug: dawn:1959
Change-Id: Idfebdc1ea144d69543de8ed7671657a0617e3b82
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/141501
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index 86f8862..53717af 100644
--- a/dawn.json
+++ b/dawn.json
@@ -136,11 +136,11 @@
         "extensible": "out",
         "members": [
             {"name": "vendor ID", "type": "uint32_t"},
-            {"name": "vendor name", "type": "char", "annotation": "const*", "length": "strlen"},
-            {"name": "architecture", "type": "char", "annotation": "const*", "length": "strlen"},
+            {"name": "vendor name", "type": "char", "annotation": "const*", "length": "strlen", "default": "nullptr"},
+            {"name": "architecture", "type": "char", "annotation": "const*", "length": "strlen", "default": "nullptr"},
             {"name": "device ID", "type": "uint32_t"},
-            {"name": "name", "type": "char", "annotation": "const*", "length": "strlen"},
-            {"name": "driver description", "type": "char", "annotation": "const*", "length": "strlen"},
+            {"name": "name", "type": "char", "annotation": "const*", "length": "strlen", "default": "nullptr"},
+            {"name": "driver description", "type": "char", "annotation": "const*", "length": "strlen", "default": "nullptr"},
             {"name": "adapter type", "type": "adapter type"},
             {"name": "backend type", "type": "backend type"},
             {"name": "compatibility mode", "type": "bool", "default": "false", "tags": ["dawn", "emscripten"]}
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index daaec67..91c8ee7 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -273,6 +273,15 @@
     def output(self):
         return self.chained == "out" or self.extensible == "out"
 
+    @property
+    def has_free_members_function(self):
+        if not self.output:
+            return False
+        for m in self.members:
+            if m.annotation != 'value':
+                return True
+        return False
+
 
 class ConstantDefinition():
     def __init__(self, is_enabled, name, json_data):
@@ -283,11 +292,12 @@
 
 
 class FunctionDeclaration():
-    def __init__(self, is_enabled, name, json_data):
+    def __init__(self, is_enabled, name, json_data, no_cpp=False):
         self.return_type = None
         self.arguments = []
         self.json_data = json_data
         self.name = Name(name)
+        self.no_cpp = no_cpp
 
 
 class Command(Record):
@@ -474,6 +484,23 @@
     for struct in by_category['structure']:
         link_structure(struct, types)
 
+        if struct.has_free_members_function:
+            name = struct.name.get() + " free members"
+            func_decl = FunctionDeclaration(
+                True,
+                name, {
+                    "returns":
+                    "void",
+                    "args": [{
+                        "name": "value",
+                        "type": struct.name.get(),
+                        "annotation": "value",
+                    }]
+                },
+                no_cpp=True)
+            types[name] = func_decl
+            by_category['function'].append(func_decl)
+
     for function_pointer in by_category['function pointer']:
         link_function_pointer(function_pointer, types)
 
@@ -753,22 +780,23 @@
                                                     annotation, arg)
 
 
-def decorate(name, typ, arg):
+def decorate(name, typ, arg, make_const=False):
+    maybe_const = ' const ' if make_const else ' '
     if arg.annotation == 'value':
-        return typ + ' ' + name
+        return typ + maybe_const + name
     elif arg.annotation == '*':
-        return typ + ' * ' + name
+        return typ + ' *' + maybe_const + name
     elif arg.annotation == 'const*':
-        return typ + ' const * ' + name
+        return typ + ' const *' + maybe_const + name
     elif arg.annotation == 'const*const*':
-        return 'const ' + typ + '* const * ' + name
+        return 'const ' + typ + '* const *' + maybe_const + name
     else:
         assert False
 
 
-def annotated(typ, arg):
+def annotated(typ, arg, make_const=False):
     name = as_varName(arg.name)
-    return decorate(name, typ, arg)
+    return decorate(name, typ, arg, make_const)
 
 
 def item_is_enabled(enabled_tags, json_data):
@@ -876,9 +904,9 @@
     return {
             'Name': lambda name: Name(name),
             'as_annotated_cType': \
-                lambda arg: annotated(as_cTypeEnumSpecialCase(arg.type), arg),
+                lambda arg, make_const=False: annotated(as_cTypeEnumSpecialCase(arg.type), arg, make_const),
             'as_annotated_cppType': \
-                lambda arg: annotated(as_cppType(arg.type.name), arg),
+                lambda arg, make_const=False: annotated(as_cppType(arg.type.name), arg, make_const),
             'as_cEnum': as_cEnum,
             'as_cppEnum': as_cppEnum,
             'as_cMethod': as_cMethod,
diff --git a/generator/templates/api.h b/generator/templates/api.h
index a0f5821..2228f07 100644
--- a/generator/templates/api.h
+++ b/generator/templates/api.h
@@ -178,6 +178,7 @@
     {% endfor %}
 
 {% endfor %}
+
 #endif  // !defined({{API}}_SKIP_PROCS)
 
 #if !defined({{API}}_SKIP_DECLARATIONS)
@@ -204,6 +205,7 @@
     {% endfor %}
 
 {% endfor %}
+
 #endif  // !defined({{API}}_SKIP_DECLARATIONS)
 
 #ifdef __cplusplus
diff --git a/generator/templates/api_cpp.cpp b/generator/templates/api_cpp.cpp
index d540e0b..c326363 100644
--- a/generator/templates/api_cpp.cpp
+++ b/generator/templates/api_cpp.cpp
@@ -11,6 +11,9 @@
 //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 //* See the License for the specific language governing permissions and
 //* limitations under the License.
+
+#include <utility>
+
 {% set api = metadata.api.lower() %}
 {% if 'dawn' in enabled_tags %}
     #include "dawn/{{api}}_cpp.h"
@@ -99,6 +102,55 @@
         {%- endif -%}
     {%- endmacro -%}
 
+    template <typename T>
+    static T& AsNonConstReference(const T& value) {
+        return const_cast<T&>(value);
+    }
+
+    {% for type in by_category["structure"] if type.has_free_members_function %}
+        // {{as_cppType(type.name)}}
+        {{as_cppType(type.name)}}::~{{as_cppType(type.name)}}() {
+            if (
+                {%- for member in type.members if member.annotation != 'value' %}
+                    {% if not loop.first %} || {% endif -%}
+                    this->{{member.name.camelCase()}} != nullptr
+                {%- endfor -%}
+            ) {
+                {{as_cMethod(type.name, Name("free members"))}}(
+                    *reinterpret_cast<{{as_cType(type.name)}}*>(this));
+            }
+        }
+
+        static void Reset({{as_cppType(type.name)}}& value) {
+            {{as_cppType(type.name)}} defaultValue{};
+            {% for member in type.members %}
+                AsNonConstReference(value.{{member.name.camelCase()}}) = defaultValue.{{member.name.camelCase()}};
+            {% endfor %}
+        }
+
+        {{as_cppType(type.name)}}::{{as_cppType(type.name)}}({{as_cppType(type.name)}}&& rhs)
+        : {% for member in type.members %}
+            {%- set memberName = member.name.camelCase() -%}
+            {{memberName}}(rhs.{{memberName}}){% if not loop.last %},{{"\n      "}}{% endif %}
+        {% endfor -%}
+        {
+            Reset(rhs);
+        }
+
+        {{as_cppType(type.name)}}& {{as_cppType(type.name)}}::operator=({{as_cppType(type.name)}}&& rhs) {
+            if (&rhs == this) {
+                return *this;
+            }
+            this->~{{as_cppType(type.name)}}();
+            {% for member in type.members %}
+                AsNonConstReference(this->{{member.name.camelCase()}}) = std::move(rhs.{{member.name.camelCase()}});
+            {% endfor %}
+            Reset(rhs);
+            return *this;
+        }
+
+    {% endfor %}
+
     {% for type in by_category["object"] %}
         {% set CppType = as_cppType(type.name) %}
         {% set CType = as_cType(type.name) %}
@@ -131,6 +183,9 @@
 
         {% for method in type.methods -%}
             {{render_cpp_method_declaration(type, method)}} {
+                {% for arg in method.arguments if arg.type.has_free_members_function and arg.annotation == '*' %}
+                    *{{as_varName(arg.name)}} = {{as_cppType(arg.type.name)}}();
+                {% endfor %}
                 {% if method.return_type.name.concatcase() == "void" %}
                     {{render_cpp_to_c_method_call(type, method)}};
                 {% else %}
@@ -153,7 +208,7 @@
 
     // Function
 
-    {% for function in by_category["function"] %}
+    {% for function in by_category["function"] if not function.no_cpp %}
         {%- macro render_function_call(function) -%}
             {{as_cMethod(None, function.name)}}(
                 {%- for arg in function.arguments -%}
@@ -167,8 +222,12 @@
                 {% if not loop.first %}, {% endif %}{{as_annotated_cppType(arg)}}
             {%- endfor -%}
         ) {
-            auto result = {{render_function_call(function)}};
-            return {{convert_cType_to_cppType(function.return_type, 'value', 'result')}};
+            {% if function.return_type.name.concatcase() == "void" %}
+                {{render_function_call(function)}};
+            {% else %}
+                auto result = {{render_function_call(function)}};
+                return {{convert_cType_to_cppType(function.return_type, 'value', 'result')}};
+            {% endif %}
         }
     {% endfor %}
 
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h
index 1447fe4..c792804 100644
--- a/generator/templates/api_cpp.h
+++ b/generator/templates/api_cpp.h
@@ -175,7 +175,7 @@
         CType mHandle = nullptr;
     };
 
-{% macro render_cpp_default_value(member, is_struct=True) -%}
+{% macro render_cpp_default_value(member, is_struct=True, force_default=False) -%}
     {%- if member.json_data.get("no_default", false) -%}
     {%- elif member.annotation in ["*", "const*"] and member.optional or member.default_value == "nullptr" -%}
         {{" "}}= nullptr
@@ -189,6 +189,9 @@
         {{" "}}= {{member.default_value}}
     {%- else -%}
         {{assert(member.default_value == None)}}
+        {%- if force_default -%}
+            {{" "}}= {}
+        {%- endif -%}
     {%- endif -%}
 {%- endmacro %}
 
@@ -227,7 +230,7 @@
 
     {% endfor %}
 
-    {% for function in by_category["function"] %}
+    {% for function in by_category["function"] if not function.no_cpp %}
         {{as_cppType(function.return_type.name)}} {{as_cppType(function.name)}}(
             {%- for arg in function.arguments -%}
                 {%- if not loop.first %}, {% endif -%}
@@ -250,11 +253,19 @@
         {% else %}
             struct {{as_cppType(type.name)}} {
         {% endif %}
+            {% if type.has_free_members_function %}
+                {{as_cppType(type.name)}}() = default;
+                ~{{as_cppType(type.name)}}();
+                {{as_cppType(type.name)}}(const {{as_cppType(type.name)}}&) = delete;
+                {{as_cppType(type.name)}}& operator=(const {{as_cppType(type.name)}}&) = delete;
+                {{as_cppType(type.name)}}({{as_cppType(type.name)}}&&);
+                {{as_cppType(type.name)}}& operator=({{as_cppType(type.name)}}&&);
+            {% endif %}
             {% if type.extensible %}
                 ChainedStruct{{Out}} {{const}} * nextInChain = nullptr;
             {% endif %}
             {% for member in type.members %}
-                {% set member_declaration = as_annotated_cppType(member) + render_cpp_default_value(member) %}
+                {% set member_declaration = as_annotated_cppType(member, type.has_free_members_function) + render_cpp_default_value(member, False, type.has_free_members_function) %}
                 {% if type.chained and loop.first %}
                     //* Align the first member after ChainedStruct to match the C struct layout.
                     //* It has to be aligned both to its natural and ChainedStruct's alignment.
diff --git a/generator/templates/dawn/native/api_structs.cpp b/generator/templates/dawn/native/api_structs.cpp
index 7e7a0b8..96cc9cf 100644
--- a/generator/templates/dawn/native/api_structs.cpp
+++ b/generator/templates/dawn/native/api_structs.cpp
@@ -72,4 +72,45 @@
         }
 
     {% endfor %}
+
+    {% for type in by_category["structure"] if type.has_free_members_function %}
+        // {{as_cppType(type.name)}}
+        {{as_cppType(type.name)}}::~{{as_cppType(type.name)}}() {
+            if (
+                {%- for member in type.members if member.annotation != 'value' %}
+                    {% if not loop.first %} || {% endif -%}
+                    this->{{member.name.camelCase()}} != nullptr
+                {%- endfor -%}
+            ) {
+                API{{as_MethodSuffix(type.name, Name("free members"))}}(*reinterpret_cast<{{as_cType(type.name)}}*>(this));
+            }
+        }
+
+        {{as_cppType(type.name)}}::{{as_cppType(type.name)}}({{as_cppType(type.name)}}&& rhs)
+        : {% for member in type.members %}
+            {%- set memberName = member.name.camelCase() -%}
+            {{memberName}}(rhs.{{memberName}}){% if not loop.last %},{{"\n      "}}{% endif %}
+        {% endfor -%}
+        {
+            {% for member in type.members %}
+                rhs.{{member.name.camelCase()}} = {};
+            {% endfor %}
+        }
+
+        {{as_cppType(type.name)}}& {{as_cppType(type.name)}}::operator=({{as_cppType(type.name)}}&& rhs) {
+            if (&rhs == this) {
+                return *this;
+            }
+            this->~{{as_cppType(type.name)}}();
+            {% for member in type.members %}
+                this->{{member.name.camelCase()}} = std::move(rhs.{{member.name.camelCase()}});
+            {% endfor %}
+            {% for member in type.members %}
+                rhs.{{member.name.camelCase()}} = {};
+            {% endfor %}
+            return *this;
+        }
+
+    {% endfor %}
+
 } // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/api_structs.h b/generator/templates/dawn/native/api_structs.h
index 2de5805..ed77c4c 100644
--- a/generator/templates/dawn/native/api_structs.h
+++ b/generator/templates/dawn/native/api_structs.h
@@ -57,6 +57,15 @@
         {% else %}
             struct {{as_cppType(type.name)}} {
         {% endif %}
+            {% if type.has_free_members_function %}
+                {{as_cppType(type.name)}}() = default;
+                ~{{as_cppType(type.name)}}();
+                {{as_cppType(type.name)}}(const {{as_cppType(type.name)}}&) = delete;
+                {{as_cppType(type.name)}}& operator=(const {{as_cppType(type.name)}}&) = delete;
+                {{as_cppType(type.name)}}({{as_cppType(type.name)}}&&);
+                {{as_cppType(type.name)}}& operator=({{as_cppType(type.name)}}&&);
+
+            {% endif %}
             {% if type.extensible %}
                 {% set chainedStructType = "ChainedStructOut" if type.output else "ChainedStruct const" %}
                 {{chainedStructType}} * nextInChain = nullptr;
@@ -83,6 +92,11 @@
         using {{as_cppType(typeDef.name)}} = {{as_cppType(typeDef.type.name)}};
     {% endfor %}
 
+    {% for type in by_category["structure"] if type.has_free_members_function %}
+        // {{as_cppType(type.name)}}
+        void API{{as_MethodSuffix(type.name, Name("free members"))}}({{as_cType(type.name)}});
+    {% endfor %}
+
 } // namespace {{native_namespace}}
 
 #endif  // {{DIR}}_{{namespace.upper()}}_STRUCTS_H_
diff --git a/generator/templates/dawn_proc_table.h b/generator/templates/dawn_proc_table.h
index 16f3fc2..10cfe3e 100644
--- a/generator/templates/dawn_proc_table.h
+++ b/generator/templates/dawn_proc_table.h
@@ -30,6 +30,7 @@
         {% endfor %}
 
     {% endfor %}
+
 } {{Prefix}}ProcTable;
 
 #endif  // DAWN_{{Prefix.upper()}}_PROC_TABLE_H_
diff --git a/generator/templates/mock_api.cpp b/generator/templates/mock_api.cpp
index bf8f871..91bcfce 100644
--- a/generator/templates/mock_api.cpp
+++ b/generator/templates/mock_api.cpp
@@ -13,6 +13,7 @@
 //* limitations under the License.
 
 {% set api = metadata.api.lower() %}
+#include "dawn/common/Log.h"
 #include "mock_{{api}}.h"
 
 using namespace testing;
@@ -48,6 +49,12 @@
             table->{{as_varName(type.name, method.name)}} = reinterpret_cast<{{as_cProc(type.name, method.name)}}>(Forward{{as_MethodSuffix(type.name, method.name)}});
         {% endfor %}
     {% endfor %}
+
+    {% for type in by_category["structure"] if type.has_free_members_function %}
+        table->{{as_varName(type.name, Name("free members"))}} = []({{as_cType(type.name)}} {{as_varName(type.name)}}) {
+            dawn::WarningLog() << "No mock available for {{as_varName(type.name, Name('free members'))}}";
+        };
+    {% endfor %}
 }
 
 {% for type in by_category["object"] %}
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 52b5dcb..6111f71 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -93,14 +93,40 @@
         powerPreferenceDesc->powerPreference = mPowerPreference;
     }
     properties->vendorID = mPhysicalDevice->GetVendorId();
-    properties->vendorName = mPhysicalDevice->GetVendorName().c_str();
-    properties->architecture = mPhysicalDevice->GetArchitectureName().c_str();
     properties->deviceID = mPhysicalDevice->GetDeviceId();
-    properties->name = mPhysicalDevice->GetName().c_str();
-    properties->driverDescription = mPhysicalDevice->GetDriverDescription().c_str();
     properties->adapterType = mPhysicalDevice->GetAdapterType();
     properties->backendType = mPhysicalDevice->GetBackendType();
     properties->compatibilityMode = mFeatureLevel == FeatureLevel::Compatibility;
+
+    // Get lengths, with null terminators.
+    size_t vendorNameCLen = mPhysicalDevice->GetVendorName().length() + 1;
+    size_t architectureCLen = mPhysicalDevice->GetArchitectureName().length() + 1;
+    size_t nameCLen = mPhysicalDevice->GetName().length() + 1;
+    size_t driverDescriptionCLen = mPhysicalDevice->GetDriverDescription().length() + 1;
+
+    // Allocate space for all strings.
+    char* ptr = new char[vendorNameCLen + architectureCLen + nameCLen + driverDescriptionCLen];
+
+    properties->vendorName = ptr;
+    memcpy(ptr, mPhysicalDevice->GetVendorName().c_str(), vendorNameCLen);
+    ptr += vendorNameCLen;
+
+    properties->architecture = ptr;
+    memcpy(ptr, mPhysicalDevice->GetArchitectureName().c_str(), architectureCLen);
+    ptr += architectureCLen;
+
+    properties->name = ptr;
+    memcpy(ptr, mPhysicalDevice->GetName().c_str(), nameCLen);
+    ptr += nameCLen;
+
+    properties->driverDescription = ptr;
+    memcpy(ptr, mPhysicalDevice->GetDriverDescription().c_str(), driverDescriptionCLen);
+    ptr += driverDescriptionCLen;
+}
+
+void APIAdapterPropertiesFreeMembers(WGPUAdapterProperties properties) {
+    // This single delete is enough because everything is a single allocation.
+    delete[] properties.vendorName;
 }
 
 bool AdapterBase::APIHasFeature(wgpu::FeatureName feature) const {
diff --git a/src/dawn/native/SharedTextureMemory.cpp b/src/dawn/native/SharedTextureMemory.cpp
index b097e64..d044906 100644
--- a/src/dawn/native/SharedTextureMemory.cpp
+++ b/src/dawn/native/SharedTextureMemory.cpp
@@ -55,4 +55,6 @@
     DAWN_UNUSED(GetDevice()->ConsumedError(DAWN_UNIMPLEMENTED_ERROR("Not implemented")));
 }
 
+void APISharedTextureMemoryEndAccessStateFreeMembers(WGPUSharedTextureMemoryEndAccessState state) {}
+
 }  // namespace dawn::native
diff --git a/src/dawn/tests/AdapterTestConfig.cpp b/src/dawn/tests/AdapterTestConfig.cpp
index 476e01a..13f1f15 100644
--- a/src/dawn/tests/AdapterTestConfig.cpp
+++ b/src/dawn/tests/AdapterTestConfig.cpp
@@ -73,7 +73,16 @@
 
 TestAdapterProperties::TestAdapterProperties(const wgpu::AdapterProperties& properties,
                                              bool selected)
-    : wgpu::AdapterProperties(properties), adapterName(properties.name), selected(selected) {}
+    : vendorID(properties.vendorID),
+      vendorName(properties.vendorName),
+      architecture(properties.architecture),
+      deviceID(properties.deviceID),
+      name(properties.name),
+      driverDescription(properties.driverDescription),
+      adapterType(properties.adapterType),
+      backendType(properties.backendType),
+      compatibilityMode(properties.compatibilityMode),
+      selected(selected) {}
 
 std::string TestAdapterProperties::ParamName() const {
     switch (backendType) {
@@ -119,7 +128,7 @@
       forceDisabledWorkarounds(config.forceDisabledWorkarounds) {}
 
 std::ostream& operator<<(std::ostream& os, const AdapterTestParam& param) {
-    os << param.adapterProperties.ParamName() << " " << param.adapterProperties.adapterName;
+    os << param.adapterProperties.ParamName() << " " << param.adapterProperties.name;
 
     // In a Windows Remote Desktop session there are two adapters named "Microsoft Basic Render
     // Driver" with different adapter types. We must differentiate them to avoid any tests using the
diff --git a/src/dawn/tests/AdapterTestConfig.h b/src/dawn/tests/AdapterTestConfig.h
index 2273559..7ce824c 100644
--- a/src/dawn/tests/AdapterTestConfig.h
+++ b/src/dawn/tests/AdapterTestConfig.h
@@ -35,17 +35,21 @@
     std::vector<const char*> forceDisabledWorkarounds;
 };
 
-struct TestAdapterProperties : wgpu::AdapterProperties {
+struct TestAdapterProperties {
     TestAdapterProperties(const wgpu::AdapterProperties& properties, bool selected);
-    std::string adapterName;
+    uint32_t vendorID;
+    std::string vendorName;
+    std::string architecture;
+    uint32_t deviceID;
+    std::string name;
+    std::string driverDescription;
+    wgpu::AdapterType adapterType;
+    wgpu::BackendType backendType;
+    bool compatibilityMode;
     bool selected;
 
     std::string ParamName() const;
     std::string AdapterTypeName() const;
-
-  private:
-    // This may be temporary, so it is copied into |adapterName| and made private.
-    using wgpu::AdapterProperties::name;
 };
 
 struct AdapterTestParam {
diff --git a/src/dawn/tests/DawnTest.cpp b/src/dawn/tests/DawnTest.cpp
index 07eff47..f33f41f 100644
--- a/src/dawn/tests/DawnTest.cpp
+++ b/src/dawn/tests/DawnTest.cpp
@@ -130,7 +130,7 @@
 
 std::string DawnTestBase::PrintToStringParamName::SanitizeParamName(
     std::string paramName,
-    const wgpu::AdapterProperties& properties,
+    const TestAdapterProperties& properties,
     size_t index) const {
     // Sanitize the adapter name for GoogleTest
     std::string sanitizedName = std::regex_replace(paramName, std::regex("[^a-zA-Z0-9]+"), "_") +
@@ -423,6 +423,8 @@
 }
 
 void DawnTestEnvironment::SelectPreferredAdapterProperties(const native::Instance* instance) {
+    dawnProcSetProcs(&dawn::native::GetProcs());
+
     // Get the first available preferred device type.
     std::optional<wgpu::AdapterType> preferredDeviceType;
     [&] {
@@ -610,14 +612,14 @@
 
         // Preparing for outputting hex numbers
         log << std::showbase << std::hex << std::setfill('0') << std::setw(4) << " - \""
-            << properties.adapterName << "\" - \"" << properties.driverDescription
+            << properties.name << "\" - \"" << properties.driverDescription
             << (properties.selected ? " [Selected]" : "") << "\"\n"
             << "   type: " << properties.AdapterTypeName()
             << ", backend: " << properties.ParamName()
             << ", compatibilityMode: " << (properties.compatibilityMode ? "true" : "false") << "\n"
             << "   vendorId: 0x" << vendorId.str() << ", deviceId: 0x" << deviceId.str() << "\n";
 
-        if (strlen(properties.vendorName) || strlen(properties.architecture)) {
+        if (!properties.vendorName.empty() || !properties.architecture.empty()) {
             log << "   vendorName: " << properties.vendorName
                 << ", architecture: " << properties.architecture << "\n";
         }
@@ -725,7 +727,7 @@
                         properties.deviceID == param.adapterProperties.deviceID &&
                         properties.vendorID == param.adapterProperties.vendorID &&
                         properties.adapterType == param.adapterProperties.adapterType &&
-                        strcmp(properties.name, param.adapterProperties.adapterName.c_str()) == 0);
+                        strcmp(properties.name, param.adapterProperties.name.c_str()) == 0);
             });
         ASSERT(it != adapters.end());
         gCurrentTest->mBackendAdapter = *it;
@@ -843,12 +845,12 @@
 }
 
 bool DawnTestBase::IsANGLE() const {
-    return !mParam.adapterProperties.adapterName.find("ANGLE");
+    return !mParam.adapterProperties.name.find("ANGLE");
 }
 
 bool DawnTestBase::IsANGLESwiftShader() const {
-    return !mParam.adapterProperties.adapterName.find("ANGLE") &&
-           (mParam.adapterProperties.adapterName.find("SwiftShader") != std::string::npos);
+    return !mParam.adapterProperties.name.find("ANGLE") &&
+           (mParam.adapterProperties.name.find("SwiftShader") != std::string::npos);
 }
 
 bool DawnTestBase::IsWARP() const {
@@ -989,7 +991,7 @@
     return {};
 }
 
-const wgpu::AdapterProperties& DawnTestBase::GetAdapterProperties() const {
+const TestAdapterProperties& DawnTestBase::GetAdapterProperties() const {
     return mParam.adapterProperties;
 }
 
diff --git a/src/dawn/tests/DawnTest.h b/src/dawn/tests/DawnTest.h
index 8bdbe0a..4ba3f61 100644
--- a/src/dawn/tests/DawnTest.h
+++ b/src/dawn/tests/DawnTest.h
@@ -288,7 +288,7 @@
     struct PrintToStringParamName {
         explicit PrintToStringParamName(const char* test);
         std::string SanitizeParamName(std::string paramName,
-                                      const wgpu::AdapterProperties& properties,
+                                      const TestAdapterProperties& properties,
                                       size_t index) const;
 
         template <class ParamType>
@@ -582,7 +582,7 @@
 
     virtual wgpu::RequiredLimits GetRequiredLimits(const wgpu::SupportedLimits&);
 
-    const wgpu::AdapterProperties& GetAdapterProperties() const;
+    const TestAdapterProperties& GetAdapterProperties() const;
 
     wgpu::SupportedLimits GetAdapterLimits();
     wgpu::SupportedLimits GetSupportedLimits();
diff --git a/src/dawn/tests/end2end/AdapterCreationTests.cpp b/src/dawn/tests/end2end/AdapterCreationTests.cpp
index f12370e..a756248 100644
--- a/src/dawn/tests/end2end/AdapterCreationTests.cpp
+++ b/src/dawn/tests/end2end/AdapterCreationTests.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "dawn/common/GPUInfo.h"
@@ -246,5 +247,180 @@
     EXPECT_EQ(adapter.GetInstance().Get(), instance.Get());
 }
 
+// Test that calling AdapterGetProperties returns separate allocations for strings.
+// However, the string contents are equivalent.
+TEST_F(AdapterCreationTest, PropertiesUnique) {
+    wgpu::RequestAdapterOptions options = {};
+
+    MockCallback<WGPURequestAdapterCallback> cb;
+
+    WGPUAdapter cAdapter = nullptr;
+    EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, nullptr, this))
+        .WillOnce(SaveArg<1>(&cAdapter));
+    instance.RequestAdapter(&options, cb.Callback(), cb.MakeUserdata(this));
+
+    wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
+    EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
+    if (!adapter) {
+        return;
+    }
+
+    wgpu::AdapterProperties properties1;
+    wgpu::AdapterProperties properties2;
+    adapter.GetProperties(&properties1);
+    adapter.GetProperties(&properties2);
+
+    EXPECT_NE(properties1.vendorName, properties2.vendorName);
+    EXPECT_STREQ(properties1.vendorName, properties2.vendorName);
+    EXPECT_NE(properties1.architecture, properties2.architecture);
+    EXPECT_STREQ(properties1.architecture, properties2.architecture);
+    EXPECT_NE(properties1.name, properties2.name);
+    EXPECT_STREQ(properties1.name, properties2.name);
+    EXPECT_NE(properties1.driverDescription, properties2.driverDescription);
+    EXPECT_STREQ(properties1.driverDescription, properties2.driverDescription);
+}
+
+// Test move assignment of the adapter properties.
+TEST_F(AdapterCreationTest, PropertiesMoveAssign) {
+    wgpu::RequestAdapterOptions options = {};
+
+    MockCallback<WGPURequestAdapterCallback> cb;
+
+    WGPUAdapter cAdapter = nullptr;
+    EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, nullptr, this))
+        .WillOnce(SaveArg<1>(&cAdapter));
+    instance.RequestAdapter(&options, cb.Callback(), cb.MakeUserdata(this));
+
+    wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
+    EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
+    if (!adapter) {
+        return;
+    }
+
+    wgpu::AdapterProperties properties1;
+    wgpu::AdapterProperties properties2;
+    adapter.GetProperties(&properties1);
+    adapter.GetProperties(&properties2);
+
+    uint32_t vendorID = properties1.vendorID;
+    std::string vendorName = properties1.vendorName;
+    std::string architecture = properties1.architecture;
+    uint32_t deviceID = properties1.deviceID;
+    std::string name = properties1.name;
+    std::string driverDescription = properties1.driverDescription;
+    wgpu::AdapterType adapterType = properties1.adapterType;
+    wgpu::BackendType backendType = properties1.backendType;
+    bool compatibilityMode = properties1.compatibilityMode;
+
+    properties2 = std::move(properties1);
+
+    // Expect properties2 to have properties1's old contents.
+    EXPECT_EQ(properties2.vendorID, vendorID);
+    EXPECT_STREQ(properties2.vendorName, vendorName.c_str());
+    EXPECT_STREQ(properties2.architecture, architecture.c_str());
+    EXPECT_EQ(properties2.deviceID, deviceID);
+    EXPECT_STREQ(properties2.name, name.c_str());
+    EXPECT_STREQ(properties2.driverDescription, driverDescription.c_str());
+    EXPECT_EQ(properties2.adapterType, adapterType);
+    EXPECT_EQ(properties2.backendType, backendType);
+    EXPECT_EQ(properties2.compatibilityMode, compatibilityMode);
+
+    // Expect properties1 to be empty.
+    EXPECT_EQ(properties1.vendorID, 0u);
+    EXPECT_EQ(properties1.vendorName, nullptr);
+    EXPECT_EQ(properties1.architecture, nullptr);
+    EXPECT_EQ(properties1.deviceID, 0u);
+    EXPECT_EQ(properties1.name, nullptr);
+    EXPECT_EQ(properties1.driverDescription, nullptr);
+    EXPECT_EQ(properties1.adapterType, static_cast<wgpu::AdapterType>(0));
+    EXPECT_EQ(properties1.backendType, static_cast<wgpu::BackendType>(0));
+    EXPECT_EQ(properties1.compatibilityMode, false);
+}
+
+// Test move construction of the adapter properties.
+TEST_F(AdapterCreationTest, PropertiesMoveConstruct) {
+    wgpu::RequestAdapterOptions options = {};
+
+    MockCallback<WGPURequestAdapterCallback> cb;
+
+    WGPUAdapter cAdapter = nullptr;
+    EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, nullptr, this))
+        .WillOnce(SaveArg<1>(&cAdapter));
+    instance.RequestAdapter(&options, cb.Callback(), cb.MakeUserdata(this));
+
+    wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
+    EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
+    if (!adapter) {
+        return;
+    }
+
+    wgpu::AdapterProperties properties1;
+    adapter.GetProperties(&properties1);
+
+    uint32_t vendorID = properties1.vendorID;
+    std::string vendorName = properties1.vendorName;
+    std::string architecture = properties1.architecture;
+    uint32_t deviceID = properties1.deviceID;
+    std::string name = properties1.name;
+    std::string driverDescription = properties1.driverDescription;
+    wgpu::AdapterType adapterType = properties1.adapterType;
+    wgpu::BackendType backendType = properties1.backendType;
+    bool compatibilityMode = properties1.compatibilityMode;
+
+    wgpu::AdapterProperties properties2(std::move(properties1));
+
+    // Expect properties2 to have properties1's old contents.
+    EXPECT_EQ(properties2.vendorID, vendorID);
+    EXPECT_STREQ(properties2.vendorName, vendorName.c_str());
+    EXPECT_STREQ(properties2.architecture, architecture.c_str());
+    EXPECT_EQ(properties2.deviceID, deviceID);
+    EXPECT_STREQ(properties2.name, name.c_str());
+    EXPECT_STREQ(properties2.driverDescription, driverDescription.c_str());
+    EXPECT_EQ(properties2.adapterType, adapterType);
+    EXPECT_EQ(properties2.backendType, backendType);
+    EXPECT_EQ(properties2.compatibilityMode, compatibilityMode);
+
+    // Expect properties1 to be empty.
+    EXPECT_EQ(properties1.vendorID, 0u);
+    EXPECT_EQ(properties1.vendorName, nullptr);
+    EXPECT_EQ(properties1.architecture, nullptr);
+    EXPECT_EQ(properties1.deviceID, 0u);
+    EXPECT_EQ(properties1.name, nullptr);
+    EXPECT_EQ(properties1.driverDescription, nullptr);
+    EXPECT_EQ(properties1.adapterType, static_cast<wgpu::AdapterType>(0));
+    EXPECT_EQ(properties1.backendType, static_cast<wgpu::BackendType>(0));
+    EXPECT_EQ(properties1.compatibilityMode, false);
+}
+
+// Test that the adapter properties can outlive the adapter.
+TEST_F(AdapterCreationTest, PropertiesOutliveAdapter) {
+    wgpu::RequestAdapterOptions options = {};
+
+    MockCallback<WGPURequestAdapterCallback> cb;
+
+    WGPUAdapter cAdapter = nullptr;
+    EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, nullptr, this))
+        .WillOnce(SaveArg<1>(&cAdapter));
+    instance.RequestAdapter(&options, cb.Callback(), cb.MakeUserdata(this));
+
+    wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
+    EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
+    if (!adapter) {
+        return;
+    }
+
+    wgpu::AdapterProperties properties;
+    adapter.GetProperties(&properties);
+
+    // Release the adapter.
+    adapter = nullptr;
+
+    // Copy the properties to std::string, this should not be a use-after-free.
+    std::string vendorName = properties.vendorName;
+    std::string architecture = properties.architecture;
+    std::string name = properties.name;
+    std::string driverDescription = properties.driverDescription;
+}
+
 }  // anonymous namespace
 }  // namespace dawn
diff --git a/src/dawn/tests/end2end/DeviceInitializationTests.cpp b/src/dawn/tests/end2end/DeviceInitializationTests.cpp
index a79f0e3..9f86a94 100644
--- a/src/dawn/tests/end2end/DeviceInitializationTests.cpp
+++ b/src/dawn/tests/end2end/DeviceInitializationTests.cpp
@@ -86,7 +86,7 @@
                 continue;
             }
 
-            availableAdapterProperties.push_back(properties);
+            availableAdapterProperties.push_back(std::move(properties));
         }
     }
 
@@ -136,7 +136,7 @@
             if (properties.backendType == wgpu::BackendType::D3D11) {
                 continue;
             }
-            availableAdapterProperties.push_back(properties);
+            availableAdapterProperties.push_back(std::move(properties));
         }
     }
 
diff --git a/src/dawn/tests/end2end/PhysicalDeviceDiscoveryTests.cpp b/src/dawn/tests/end2end/PhysicalDeviceDiscoveryTests.cpp
index af0288a..25271ea 100644
--- a/src/dawn/tests/end2end/PhysicalDeviceDiscoveryTests.cpp
+++ b/src/dawn/tests/end2end/PhysicalDeviceDiscoveryTests.cpp
@@ -16,6 +16,7 @@
 #include <utility>
 
 #include "dawn/common/GPUInfo.h"
+#include "dawn/dawn_proc.h"
 #include "dawn/native/DawnNative.h"
 #include "dawn/webgpu_cpp.h"
 
@@ -45,11 +46,13 @@
 namespace dawn {
 namespace {
 
-class PhysicalDeviceDiscoveryTests : public ::testing::Test {};
+class PhysicalDeviceDiscoveryTests : public ::testing::Test {
+    void SetUp() override { dawnProcSetProcs(&dawn::native::GetProcs()); }
+};
 
 #if defined(DAWN_ENABLE_BACKEND_VULKAN)
 // Test only discovering the SwiftShader physical devices
-TEST(PhysicalDeviceDiscoveryTests, OnlySwiftShader) {
+TEST_F(PhysicalDeviceDiscoveryTests, OnlySwiftShader) {
     native::Instance instance;
 
     native::vulkan::PhysicalDeviceDiscoveryOptions options;
@@ -69,7 +72,7 @@
 }
 
 // Test discovering only Vulkan physical devices
-TEST(PhysicalDeviceDiscoveryTests, OnlyVulkan) {
+TEST_F(PhysicalDeviceDiscoveryTests, OnlyVulkan) {
     native::Instance instance;
 
     native::vulkan::PhysicalDeviceDiscoveryOptions options;
@@ -87,7 +90,7 @@
 
 #if defined(DAWN_ENABLE_BACKEND_D3D11)
 // Test discovering only D3D11 physical devices
-TEST(PhysicalDeviceDiscoveryTests, OnlyD3D11) {
+TEST_F(PhysicalDeviceDiscoveryTests, OnlyD3D11) {
     native::Instance instance;
 
     native::d3d11::PhysicalDeviceDiscoveryOptions options;
@@ -103,7 +106,7 @@
 }
 
 // Test discovering a D3D11 physical device from a prexisting DXGI adapter
-TEST(PhysicalDeviceDiscoveryTests, MatchingDXGIAdapterD3D11) {
+TEST_F(PhysicalDeviceDiscoveryTests, MatchingDXGIAdapterD3D11) {
     using Microsoft::WRL::ComPtr;
 
     ComPtr<IDXGIFactory4> dxgiFactory;
@@ -135,7 +138,7 @@
 
 #if defined(DAWN_ENABLE_BACKEND_D3D12)
 // Test discovering only D3D12 physical devices
-TEST(PhysicalDeviceDiscoveryTests, OnlyD3D12) {
+TEST_F(PhysicalDeviceDiscoveryTests, OnlyD3D12) {
     native::Instance instance;
 
     native::d3d12::PhysicalDeviceDiscoveryOptions options;
@@ -151,7 +154,7 @@
 }
 
 // Test discovering a D3D12 physical device from a prexisting DXGI adapter
-TEST(PhysicalDeviceDiscoveryTests, MatchingDXGIAdapterD3D12) {
+TEST_F(PhysicalDeviceDiscoveryTests, MatchingDXGIAdapterD3D12) {
     using Microsoft::WRL::ComPtr;
 
     ComPtr<IDXGIFactory4> dxgiFactory;
@@ -183,7 +186,7 @@
 
 #if defined(DAWN_ENABLE_BACKEND_METAL)
 // Test discovering only Metal physical devices
-TEST(PhysicalDeviceDiscoveryTests, OnlyMetal) {
+TEST_F(PhysicalDeviceDiscoveryTests, OnlyMetal) {
     native::Instance instance;
 
     native::metal::PhysicalDeviceDiscoveryOptions options;
@@ -202,7 +205,7 @@
 #if defined(DAWN_ENABLE_BACKEND_METAL) && defined(DAWN_ENABLE_BACKEND_VULKAN)
 // Test discovering the Metal backend, then the Vulkan backend
 // does not duplicate physical devices.
-TEST(PhysicalDeviceDiscoveryTests, OneBackendThenTheOther) {
+TEST_F(PhysicalDeviceDiscoveryTests, OneBackendThenTheOther) {
     native::Instance instance;
     uint32_t metalAdapterCount = 0;
     {
@@ -239,10 +242,12 @@
 }
 #endif  // defined(DAWN_ENABLE_BACKEND_VULKAN) && defined(DAWN_ENABLE_BACKEND_METAL)
 
-class AdapterEnumerationTests : public ::testing::Test {};
+class AdapterEnumerationTests : public ::testing::Test {
+    void SetUp() override { dawnProcSetProcs(&dawn::native::GetProcs()); }
+};
 
 // Test only enumerating the fallback adapters
-TEST(AdapterEnumerationTests, OnlyFallback) {
+TEST_F(AdapterEnumerationTests, OnlyFallback) {
     native::Instance instance;
 
     wgpu::RequestAdapterOptions adapterOptions = {};
@@ -260,7 +265,7 @@
 }
 
 // Test enumerating only Vulkan physical devices
-TEST(AdapterEnumerationTests, OnlyVulkan) {
+TEST_F(AdapterEnumerationTests, OnlyVulkan) {
     native::Instance instance;
 
     wgpu::RequestAdapterOptions adapterOptions = {};
@@ -276,7 +281,7 @@
 }
 
 // Test enumerating only D3D11 physical devices
-TEST(AdapterEnumerationTests, OnlyD3D11) {
+TEST_F(AdapterEnumerationTests, OnlyD3D11) {
     native::Instance instance;
 
     wgpu::RequestAdapterOptions adapterOptions = {};
@@ -293,7 +298,7 @@
 
 #if defined(DAWN_ENABLE_BACKEND_D3D11)
 // Test enumerating a D3D11 physical device from a prexisting DXGI adapter
-TEST(AdapterEnumerationTests, MatchingDXGIAdapterD3D11) {
+TEST_F(AdapterEnumerationTests, MatchingDXGIAdapterD3D11) {
     using Microsoft::WRL::ComPtr;
 
     ComPtr<IDXGIFactory4> dxgiFactory;
@@ -337,11 +342,11 @@
         adaptersAgain[0].GetProperties(&propertiesAgain);
 
         EXPECT_EQ(properties.vendorID, propertiesAgain.vendorID);
-        EXPECT_EQ(properties.vendorName, propertiesAgain.vendorName);
-        EXPECT_EQ(properties.architecture, propertiesAgain.architecture);
+        EXPECT_STREQ(properties.vendorName, propertiesAgain.vendorName);
+        EXPECT_STREQ(properties.architecture, propertiesAgain.architecture);
         EXPECT_EQ(properties.deviceID, propertiesAgain.deviceID);
-        EXPECT_EQ(properties.name, propertiesAgain.name);
-        EXPECT_EQ(properties.driverDescription, propertiesAgain.driverDescription);
+        EXPECT_STREQ(properties.name, propertiesAgain.name);
+        EXPECT_STREQ(properties.driverDescription, propertiesAgain.driverDescription);
         EXPECT_EQ(properties.adapterType, propertiesAgain.adapterType);
         EXPECT_EQ(properties.backendType, propertiesAgain.backendType);
         EXPECT_EQ(properties.compatibilityMode, propertiesAgain.compatibilityMode);
@@ -350,7 +355,7 @@
 #endif  // defined(DAWN_ENABLE_BACKEND_D3D11)
 
 // Test enumerating only D3D12 physical devices
-TEST(AdapterEnumerationTests, OnlyD3D12) {
+TEST_F(AdapterEnumerationTests, OnlyD3D12) {
     native::Instance instance;
 
     wgpu::RequestAdapterOptions adapterOptions = {};
@@ -367,7 +372,7 @@
 
 #if defined(DAWN_ENABLE_BACKEND_D3D12)
 // Test enumerating a D3D12 physical device from a prexisting DXGI adapter
-TEST(AdapterEnumerationTests, MatchingDXGIAdapterD3D12) {
+TEST_F(AdapterEnumerationTests, MatchingDXGIAdapterD3D12) {
     using Microsoft::WRL::ComPtr;
 
     ComPtr<IDXGIFactory4> dxgiFactory;
@@ -411,11 +416,11 @@
         adaptersAgain[0].GetProperties(&propertiesAgain);
 
         EXPECT_EQ(properties.vendorID, propertiesAgain.vendorID);
-        EXPECT_EQ(properties.vendorName, propertiesAgain.vendorName);
-        EXPECT_EQ(properties.architecture, propertiesAgain.architecture);
+        EXPECT_STREQ(properties.vendorName, propertiesAgain.vendorName);
+        EXPECT_STREQ(properties.architecture, propertiesAgain.architecture);
         EXPECT_EQ(properties.deviceID, propertiesAgain.deviceID);
-        EXPECT_EQ(properties.name, propertiesAgain.name);
-        EXPECT_EQ(properties.driverDescription, propertiesAgain.driverDescription);
+        EXPECT_STREQ(properties.name, propertiesAgain.name);
+        EXPECT_STREQ(properties.driverDescription, propertiesAgain.driverDescription);
         EXPECT_EQ(properties.adapterType, propertiesAgain.adapterType);
         EXPECT_EQ(properties.backendType, propertiesAgain.backendType);
         EXPECT_EQ(properties.compatibilityMode, propertiesAgain.compatibilityMode);
@@ -424,7 +429,7 @@
 #endif  // defined(DAWN_ENABLE_BACKEND_D3D12)
 
 // Test enumerating only Metal physical devices
-TEST(AdapterEnumerationTests, OnlyMetal) {
+TEST_F(AdapterEnumerationTests, OnlyMetal) {
     native::Instance instance;
 
     wgpu::RequestAdapterOptions adapterOptions = {};
@@ -441,7 +446,7 @@
 
 // Test enumerating the Metal backend, then the Vulkan backend
 // does not duplicate physical devices.
-TEST(AdapterEnumerationTests, OneBackendThenTheOther) {
+TEST_F(AdapterEnumerationTests, OneBackendThenTheOther) {
     wgpu::RequestAdapterOptions adapterOptions = {};
     adapterOptions.backendType = wgpu::BackendType::Metal;
 
diff --git a/src/dawn/tests/unittests/wire/WireInstanceTests.cpp b/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
index b583648..69c3b15 100644
--- a/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
@@ -107,15 +107,15 @@
     auto* userdata = cb.MakeUserdata(this);
     instance.RequestAdapter(&options, cb.Callback(), userdata);
 
-    wgpu::AdapterProperties fakeProperties = {};
+    WGPUAdapterProperties fakeProperties = {};
     fakeProperties.vendorID = 0x134;
     fakeProperties.vendorName = "fake-vendor";
     fakeProperties.architecture = "fake-architecture";
     fakeProperties.deviceID = 0x918;
     fakeProperties.name = "fake adapter";
     fakeProperties.driverDescription = "hello world";
-    fakeProperties.backendType = wgpu::BackendType::D3D12;
-    fakeProperties.adapterType = wgpu::AdapterType::IntegratedGPU;
+    fakeProperties.backendType = WGPUBackendType_D3D12;
+    fakeProperties.adapterType = WGPUAdapterType_IntegratedGPU;
 
     wgpu::SupportedLimits fakeLimits = {};
     fakeLimits.limits.maxTextureDimension1D = 433;
@@ -131,8 +131,7 @@
     EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
         .WillOnce(InvokeWithoutArgs([&] {
             EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
-                .WillOnce(
-                    SetArgPointee<1>(*reinterpret_cast<WGPUAdapterProperties*>(&fakeProperties)));
+                .WillOnce(SetArgPointee<1>(fakeProperties));
 
             EXPECT_CALL(api, AdapterGetLimits(apiAdapter, NotNull()))
                 .WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) {
@@ -162,14 +161,15 @@
 
             wgpu::AdapterProperties properties;
             adapter.GetProperties(&properties);
-            EXPECT_EQ(properties.vendorID, fakeProperties.vendorID);
-            EXPECT_STREQ(properties.vendorName, fakeProperties.vendorName);
-            EXPECT_STREQ(properties.architecture, fakeProperties.architecture);
-            EXPECT_EQ(properties.deviceID, fakeProperties.deviceID);
-            EXPECT_STREQ(properties.name, fakeProperties.name);
-            EXPECT_STREQ(properties.driverDescription, fakeProperties.driverDescription);
-            EXPECT_EQ(properties.backendType, fakeProperties.backendType);
-            EXPECT_EQ(properties.adapterType, fakeProperties.adapterType);
+            const auto& rhs = *reinterpret_cast<wgpu::AdapterProperties*>(&fakeProperties);
+            EXPECT_EQ(properties.vendorID, rhs.vendorID);
+            EXPECT_STREQ(properties.vendorName, rhs.vendorName);
+            EXPECT_STREQ(properties.architecture, rhs.architecture);
+            EXPECT_EQ(properties.deviceID, rhs.deviceID);
+            EXPECT_STREQ(properties.name, rhs.name);
+            EXPECT_STREQ(properties.driverDescription, rhs.driverDescription);
+            EXPECT_EQ(properties.backendType, rhs.backendType);
+            EXPECT_EQ(properties.adapterType, rhs.adapterType);
 
             wgpu::SupportedLimits limits;
             EXPECT_TRUE(adapter.GetLimits(&limits));
diff --git a/src/dawn/wire/client/Adapter.cpp b/src/dawn/wire/client/Adapter.cpp
index 2d60b7e..d1052c4 100644
--- a/src/dawn/wire/client/Adapter.cpp
+++ b/src/dawn/wire/client/Adapter.cpp
@@ -60,6 +60,36 @@
 
 void Adapter::GetProperties(WGPUAdapterProperties* properties) const {
     *properties = mProperties;
+
+    // Get lengths, with null terminators.
+    size_t vendorNameCLen = strlen(mProperties.vendorName) + 1;
+    size_t architectureCLen = strlen(mProperties.architecture) + 1;
+    size_t nameCLen = strlen(mProperties.name) + 1;
+    size_t driverDescriptionCLen = strlen(mProperties.driverDescription) + 1;
+
+    // Allocate space for all strings.
+    char* ptr = new char[vendorNameCLen + architectureCLen + nameCLen + driverDescriptionCLen];
+
+    properties->vendorName = ptr;
+    memcpy(ptr, mProperties.vendorName, vendorNameCLen);
+    ptr += vendorNameCLen;
+
+    properties->architecture = ptr;
+    memcpy(ptr, mProperties.architecture, architectureCLen);
+    ptr += architectureCLen;
+
+    properties->name = ptr;
+    memcpy(ptr, mProperties.name, nameCLen);
+    ptr += nameCLen;
+
+    properties->driverDescription = ptr;
+    memcpy(ptr, mProperties.driverDescription, driverDescriptionCLen);
+    ptr += driverDescriptionCLen;
+}
+
+void ClientAdapterPropertiesFreeMembers(WGPUAdapterProperties properties) {
+    // This single delete is enough because everything is a single allocation.
+    delete[] properties.vendorName;
 }
 
 void Adapter::RequestDevice(const WGPUDeviceDescriptor* descriptor,
diff --git a/src/dawn/wire/client/Adapter.h b/src/dawn/wire/client/Adapter.h
index 69e8fff..ec8bb57 100644
--- a/src/dawn/wire/client/Adapter.h
+++ b/src/dawn/wire/client/Adapter.h
@@ -66,6 +66,8 @@
     RequestTracker<RequestDeviceData> mRequestDeviceRequests;
 };
 
+void ClientAdapterPropertiesFreeMembers(WGPUAdapterProperties);
+
 }  // namespace dawn::wire::client
 
 #endif  // SRC_DAWN_WIRE_CLIENT_ADAPTER_H_
diff --git a/src/dawn/wire/server/ServerInstance.cpp b/src/dawn/wire/server/ServerInstance.cpp
index 7fa4b17..e235a5f 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -79,6 +79,7 @@
     cmd.limits = &limits;
 
     SerializeCommand(cmd);
+    mProcs.adapterPropertiesFreeMembers(properties);
 }
 
 }  // namespace dawn::wire::server