petpav01 created this revision.
petpav01 added a reviewer: rsmith.
Herald added a reviewer: javed.absar.
Herald added subscribers: cfe-commits, kristof.beyls.

Trying to compile the following example results in a clang crash:

  $ cat char.c
  void *memcpy(void *, const void *, unsigned int);
  extern char array2[];
  extern char array[];
  void test(void) { memcpy(&array, &array2, 9 * sizeof(char)); }
  
  $ ./llvm/build/bin/clang -target armv8a-none-eabi -c char.c
  clang-8: /work/llvm/lib/Support/APInt.cpp:1785: static void 
llvm::APInt::udivrem(const llvm::APInt&, uint64_t, llvm::APInt&, uint64_t&): 
Assertion `RHS != 0 && "Divide by zero?"' failed.
  [...]

The problem occurs since rC338941 <https://reviews.llvm.org/rC338941>. The 
change added support for constant evaluation of `__builtin_memcpy/memmove()` 
but it does not always cope well with incomplete types.

The AST for the `memcpy()` call looks as follows:

  CallExpr 0x7416d0 'void *'
  |-ImplicitCastExpr 0x7416b8 'void *(*)(void *, const void *, unsigned int)' 
<FunctionToPointerDecay>
  | `-DeclRefExpr 0x741518 'void *(void *, const void *, unsigned int)' 
Function 0x741198 'memcpy' 'void *(void *, const void *, unsigned int)'
  |-ImplicitCastExpr 0x741710 'void *' <BitCast>
  | `-UnaryOperator 0x741598 'char (*)[]' prefix '&' cannot overflow
  |   `-DeclRefExpr 0x741540 'char []' lvalue Var 0x741358 'array' 'char []'
  |-ImplicitCastExpr 0x741728 'const void *' <BitCast>
  | `-UnaryOperator 0x7415e0 'char (*)[]' prefix '&' cannot overflow
  |   `-DeclRefExpr 0x7415b8 'char []' lvalue Var 0x741298 'array2' 'char []'
  `-BinaryOperator 0x741668 'unsigned int' '*'
    |-ImplicitCastExpr 0x741650 'unsigned int' <IntegralCast>
    | `-IntegerLiteral 0x741600 'int' 9
    `-UnaryExprOrTypeTraitExpr 0x741630 'unsigned int' sizeof 'char'

The following happens in `PointerExprEvaluator::VisitBuiltinCallExpr()`, label 
`Builtin::BI__builtin_memcpy`:

- Types `T` and `SrcT` are determined as:

  IncompleteArrayType 0x741250 'char []'
  `-BuiltinType 0x6ff430 'char'

- Method `ASTContext::getTypeSizeInChars()` is called to obtain size of type 
`T`. It returns 0 because the type is incomplete. The result is stored in 
variable `TSize`.
- Following call to `llvm::APInt::udivrem(OrigN, TSize, N, Remainder)` fails 
because it attempts a divide by zero.

The proposed patch fixes the problem by adding a check that no incomplete type 
is getting copied prior to the call to `ASTContext::getTypeSizeInChars()`.


Repository:
  rC Clang

https://reviews.llvm.org/D51855

Files:
  include/clang/Basic/DiagnosticASTKinds.td
  lib/AST/ExprConstant.cpp
  test/CodeGen/builtin-memfns.c
  test/SemaCXX/constexpr-string.cpp


Index: test/SemaCXX/constexpr-string.cpp
===================================================================
--- test/SemaCXX/constexpr-string.cpp
+++ test/SemaCXX/constexpr-string.cpp
@@ -370,4 +370,31 @@
   // designators until we have a long enough matching size, if both designators
   // point to the start of their respective final elements.
   static_assert(test_derived_to_base(2) == 3434); // expected-error 
{{constant}} expected-note {{in call}}
+
+  // Check that when address-of an array is passed to a tested function the
+  // array can be fully copied.
+  constexpr int test_address_of_const_array_type() {
+    int arr[4] = {1, 2, 3, 4};
+    __builtin_memmove(&arr, &arr, sizeof(arr));
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_address_of_const_array_type() == 1234);
+
+  // Check that an incomplete array is rejected.
+  constexpr int test_incomplete_array_type() { // expected-error {{never 
produces a constant}}
+    extern int arr[];
+    __builtin_memmove(arr, arr, 4 * sizeof(arr[0]));
+    // expected-note@-1 2{{'memmove' not supported: source is not a contiguous 
array of at least 4 elements of type 'int'}}
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_incomplete_array_type() == 1234); // expected-error 
{{constant}} expected-note {{in call}}
+
+  // Check that a pointer to an incomplete array is rejected.
+  constexpr int test_address_of_incomplete_array_type() { // expected-error 
{{never produces a constant}}
+    extern int arr[];
+    __builtin_memmove(&arr, &arr, 4 * sizeof(arr[0]));
+    // expected-note@-1 2{{cannot constant evaluate 'memmove' between objects 
of incomplete type 'int []'}}
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_address_of_incomplete_array_type() == 1234); // 
expected-error {{constant}} expected-note {{in call}}
 }
Index: test/CodeGen/builtin-memfns.c
===================================================================
--- test/CodeGen/builtin-memfns.c
+++ test/CodeGen/builtin-memfns.c
@@ -111,3 +111,10 @@
   memcpy(&d, (char *)&e.a, sizeof(e));
 }
 
+// CHECK-LABEL: @test12
+extern char dest_array[];
+extern char src_array[];
+void test12() {
+  // CHECK: call void @llvm.memcpy{{.*}}(
+  memcpy(&dest_array, &dest_array, 2);
+}
Index: lib/AST/ExprConstant.cpp
===================================================================
--- lib/AST/ExprConstant.cpp
+++ lib/AST/ExprConstant.cpp
@@ -6222,6 +6222,10 @@
       Info.FFDiag(E, diag::note_constexpr_memcpy_nontrivial) << Move << T;
       return false;
     }
+    if (T->isIncompleteType()) {
+      Info.FFDiag(E, diag::note_constexpr_memcpy_incompletetype) << Move << T;
+      return false;
+    }
 
     // Figure out how many T's we're copying.
     uint64_t TSize = Info.Ctx.getTypeSizeInChars(T).getQuantity();
Index: include/clang/Basic/DiagnosticASTKinds.td
===================================================================
--- include/clang/Basic/DiagnosticASTKinds.td
+++ include/clang/Basic/DiagnosticASTKinds.td
@@ -169,6 +169,9 @@
 def note_constexpr_memcpy_nontrivial : Note<
   "cannot constant evaluate '%select{memcpy|memmove}0' between objects of "
   "non-trivially-copyable type %1">;
+def note_constexpr_memcpy_incompletetype : Note<
+  "cannot constant evaluate '%select{memcpy|memmove}0' between objects of "
+  "incomplete type %1">;
 def note_constexpr_memcpy_overlap : Note<
   "'%select{memcpy|wmemcpy}0' between overlapping memory regions">;
 def note_constexpr_memcpy_unsupported : Note<


Index: test/SemaCXX/constexpr-string.cpp
===================================================================
--- test/SemaCXX/constexpr-string.cpp
+++ test/SemaCXX/constexpr-string.cpp
@@ -370,4 +370,31 @@
   // designators until we have a long enough matching size, if both designators
   // point to the start of their respective final elements.
   static_assert(test_derived_to_base(2) == 3434); // expected-error {{constant}} expected-note {{in call}}
+
+  // Check that when address-of an array is passed to a tested function the
+  // array can be fully copied.
+  constexpr int test_address_of_const_array_type() {
+    int arr[4] = {1, 2, 3, 4};
+    __builtin_memmove(&arr, &arr, sizeof(arr));
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_address_of_const_array_type() == 1234);
+
+  // Check that an incomplete array is rejected.
+  constexpr int test_incomplete_array_type() { // expected-error {{never produces a constant}}
+    extern int arr[];
+    __builtin_memmove(arr, arr, 4 * sizeof(arr[0]));
+    // expected-note@-1 2{{'memmove' not supported: source is not a contiguous array of at least 4 elements of type 'int'}}
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_incomplete_array_type() == 1234); // expected-error {{constant}} expected-note {{in call}}
+
+  // Check that a pointer to an incomplete array is rejected.
+  constexpr int test_address_of_incomplete_array_type() { // expected-error {{never produces a constant}}
+    extern int arr[];
+    __builtin_memmove(&arr, &arr, 4 * sizeof(arr[0]));
+    // expected-note@-1 2{{cannot constant evaluate 'memmove' between objects of incomplete type 'int []'}}
+    return arr[0] * 1000 + arr[1] * 100 + arr[2] * 10 + arr[3];
+  }
+  static_assert(test_address_of_incomplete_array_type() == 1234); // expected-error {{constant}} expected-note {{in call}}
 }
Index: test/CodeGen/builtin-memfns.c
===================================================================
--- test/CodeGen/builtin-memfns.c
+++ test/CodeGen/builtin-memfns.c
@@ -111,3 +111,10 @@
   memcpy(&d, (char *)&e.a, sizeof(e));
 }
 
+// CHECK-LABEL: @test12
+extern char dest_array[];
+extern char src_array[];
+void test12() {
+  // CHECK: call void @llvm.memcpy{{.*}}(
+  memcpy(&dest_array, &dest_array, 2);
+}
Index: lib/AST/ExprConstant.cpp
===================================================================
--- lib/AST/ExprConstant.cpp
+++ lib/AST/ExprConstant.cpp
@@ -6222,6 +6222,10 @@
       Info.FFDiag(E, diag::note_constexpr_memcpy_nontrivial) << Move << T;
       return false;
     }
+    if (T->isIncompleteType()) {
+      Info.FFDiag(E, diag::note_constexpr_memcpy_incompletetype) << Move << T;
+      return false;
+    }
 
     // Figure out how many T's we're copying.
     uint64_t TSize = Info.Ctx.getTypeSizeInChars(T).getQuantity();
Index: include/clang/Basic/DiagnosticASTKinds.td
===================================================================
--- include/clang/Basic/DiagnosticASTKinds.td
+++ include/clang/Basic/DiagnosticASTKinds.td
@@ -169,6 +169,9 @@
 def note_constexpr_memcpy_nontrivial : Note<
   "cannot constant evaluate '%select{memcpy|memmove}0' between objects of "
   "non-trivially-copyable type %1">;
+def note_constexpr_memcpy_incompletetype : Note<
+  "cannot constant evaluate '%select{memcpy|memmove}0' between objects of "
+  "incomplete type %1">;
 def note_constexpr_memcpy_overlap : Note<
   "'%select{memcpy|wmemcpy}0' between overlapping memory regions">;
 def note_constexpr_memcpy_unsupported : Note<
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D51855: [... Petr Pavlu via Phabricator via cfe-commits

Reply via email to