Don't memcpy zero bytes in generated WireCmd.cpp

Due to a C language bug, memcpy is undefined on empty inputs when the
pointers are NULL. See https://davidben.net/2024/01/15/empty-slices.html
for details. This code triggers UBSan bugs like the following:

https://luci-milo.appspot.com/ui/inv/build-8749056521619502353/test-results?q=WebGPUSwapBufferProviderTest.ReuseSwapBuffers&sortby=&groupby=

For most of these in Chromium, I've been fixing them to use spans, or at
least std::copy and std::copy_n, which do not have this language bug and
are specialized as memcpy when the type is trivially copyable. However,
when I tried to do that, I ran into issues with WGPUColor and
WGPUColorTransfer being different types. There's a lot of synthesized
code here I can't follow so, instead, just guard the memcpy with a zero
check to fix the undefined behavior.

Bug: chromium:40248746
Change-Id: I4041bfb750a3e0fcf56353bffefc0a2715603475
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188900
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/generator/templates/dawn/wire/WireCmd.cpp b/generator/templates/dawn/wire/WireCmd.cpp
index 3cadd30..0c122d2 100644
--- a/generator/templates/dawn/wire/WireCmd.cpp
+++ b/generator/templates/dawn/wire/WireCmd.cpp
@@ -308,9 +308,14 @@
                 WIRE_TRY(buffer->NextN(memberLength, &memberBuffer));
 
                 {% if member.type.is_wire_transparent %}
-                    memcpy(
-                        memberBuffer, record.{{memberName}},
-                        {{member_transfer_sizeof(member)}} * memberLength);
+                    //* memcpy is not defined for null pointers, even when the length is zero.
+                    //* conflicts with the common practice to use (nullptr, 0) to represent an
+                    //* span. Guard memcpy with a zero check to work around this language bug.
+                    if (memberLength != 0) {
+                        memcpy(
+                            memberBuffer, record.{{memberName}},
+                            {{member_transfer_sizeof(member)}} * memberLength);
+                    }
                 {% else %}
                     //* This loop cannot overflow because it iterates up to |memberLength|. Even if
                     //* memberLength were the maximum integer value, |i| would become equal to it
@@ -436,15 +441,20 @@
                     record->{{memberName}} = copiedMembers;
 
                     {% if member.type.is_wire_transparent %}
-                        //* memcpy is not allowed to copy from volatile objects. However, these
-                        //* arrays are just used as plain data, and don't impact control flow. So if
-                        //* the underlying data were changed while the copy was still executing, we
-                        //* would get different data - but it wouldn't cause unexpected downstream
-                        //* effects.
-                        memcpy(
-                            copiedMembers,
-                            const_cast<const {{member_transfer_type(member)}}*>(memberBuffer),
-                           {{member_transfer_sizeof(member)}} * memberLength);
+                        //* memcpy is not defined for null pointers, even when the length is zero.
+                        //* conflicts with the common practice to use (nullptr, 0) to represent an
+                        //* span. Guard memcpy with a zero check to work around this language bug.
+                        if (memberLength != 0) {
+                            //* memcpy is not allowed to copy from volatile objects. However, these
+                            //* arrays are just used as plain data, and don't impact control flow.
+                            //* So if the underlying data were changed while the copy was still
+                            //* executing, we would get different data - but it wouldn't cause
+                            //* unexpected downstream effects.
+                            memcpy(
+                                copiedMembers,
+                                const_cast<const {{member_transfer_type(member)}}*>(memberBuffer),
+                              {{member_transfer_sizeof(member)}} * memberLength);
+                        }
                     {% else %}
                         //* This loop cannot overflow because it iterates up to |memberLength|. Even
                         //* if memberLength were the maximum integer value, |i| would become equal