Skip to content

Commit 51889f0

Browse files
committed
Handle incomplete array types for no_preserve_cheri_tags
If we see something like extern struct NoCaps unsized_array[]; we can still assume that we know the effective type since global variables have a defined type. The only exception here are arrays of type (unsigned) char since the C standard allows those to alias any other type.
1 parent b35a6cb commit 51889f0

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

clang/lib/CodeGen/CodeGenTypes.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,9 @@ CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional<CharUnits> Size) {
10841084
findUnderlyingVarDeclForCopy(Context, E, UnderlyingInfo, Size);
10851085
if (UnderlyingDecl) {
10861086
auto UnderlyingTy = UnderlyingDecl->getType();
1087-
assert(!UnderlyingTy->isIncompleteType() && "Unexpected incomplete type");
1087+
assert(!UnderlyingTy->isDependentType() &&
1088+
!UnderlyingTy->containsErrors() &&
1089+
"Unexpected dependent/error type");
10881090
if (UnderlyingTy->isReferenceType()) {
10891091
// If the variable declaration is a C++ reference we can assume that the
10901092
// effective type of the object matches the type of the reference since
@@ -1165,6 +1167,14 @@ llvm::PreserveCheriTags CodeGenTypes::copyShouldPreserveTagsForPointee(
11651167
} else if (Pointee->isIncompleteType()) {
11661168
// We don't know if incomplete types contain capabilities, so be
11671169
// conservative and assume that they might.
1170+
// The only exception here are incomplete array types (e.g. extern int x[])
1171+
// since we don't care about the size of the type, just whether it can
1172+
// contain capabilities.
1173+
if (Pointee->isIncompleteArrayType()) {
1174+
return copyShouldPreserveTagsForPointee(
1175+
Context.getBaseElementType(Pointee), EffectiveTypeKnown, Size,
1176+
CopyOffsetInBits);
1177+
}
11681178
return llvm::PreserveCheriTags::Unknown;
11691179
} else if (auto *RD = Pointee->getAsRecordDecl()) {
11701180
// For C++ classes, there could be a subclass that contains capabilities,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK --implicit-check-not=llvm.memcpy
2+
/// Check that we can add the no_preserve_cheri_tags attribute for copies
3+
/// to/from globals (as those have a fixed effective type).
4+
5+
static __uint128_t u128 = 0;
6+
extern __uint128_t *u128_ptr;
7+
extern __uint128_t u128_array[4];
8+
extern __uint128_t u128_array_unsized[];
9+
10+
void test_u128(void *buf) {
11+
// CHECK-LABEL: @test_u128(
12+
// No need to preserve tags since we can see the definition:
13+
__builtin_memcpy(buf, &u128, sizeof(u128));
14+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
15+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_TAGS:#[0-9]+]]{{$}}
16+
17+
// We can see the variable declaration but it's a pointer so we can't add the attribute
18+
__builtin_memcpy(buf, u128_ptr, sizeof(*u128_ptr));
19+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
20+
// CHECK-SAME: , i64 16, i1 false){{$}}
21+
22+
// Here we can see the array definition, so no need to preserve tags:
23+
__builtin_memcpy(buf, u128_array, sizeof(u128_array));
24+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
25+
// CHECK-SAME: , i64 64, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
26+
// Same for unsized arrays:
27+
__builtin_memcpy(buf, u128_array_unsized, 80);
28+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
29+
// CHECK-SAME: , i64 80, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
30+
}
31+
32+
// CHECK: declare void @llvm.memcpy.p200i8.p200i8.i64(
33+
34+
static unsigned __intcap ucap = 0;
35+
extern unsigned __intcap *ucap_ptr;
36+
extern unsigned __intcap ucap_array[4];
37+
extern unsigned __intcap ucap_array_unsized[];
38+
39+
void test_ucap(void *buf) {
40+
// CHECK-LABEL: @test_ucap(
41+
// In all of these cases we should add the must_preserve_tags attribute since
42+
// we are copying a capability type.
43+
__builtin_memcpy(buf, &ucap, sizeof(ucap));
44+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
45+
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_UINTCAP:#[0-9]+]]{{$}}
46+
__builtin_memcpy(buf, ucap_ptr, sizeof(*ucap_ptr));
47+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
48+
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_UINTCAP]]{{$}}
49+
__builtin_memcpy(buf, ucap_array, sizeof(ucap_array));
50+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
51+
// CHECK-SAME: , i64 64, i1 false) [[MUST_PRESERVE_UINTCAP]]{{$}}
52+
__builtin_memcpy(buf, ucap_array_unsized, 80);
53+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
54+
// CHECK-SAME: , i64 80, i1 false) [[MUST_PRESERVE_UINTCAP]]{{$}}
55+
}
56+
57+
struct NoCaps {
58+
int i;
59+
long l[15];
60+
};
61+
extern struct NoCaps nocap_struct;
62+
extern struct NoCaps *nocap_struct_ptr;
63+
extern struct NoCaps nocap_struct_array[4];
64+
extern struct NoCaps nocap_struct_array_unsized[];
65+
66+
void test_struct_nocaps(void *buf) {
67+
// CHECK-LABEL: @test_struct_nocaps(
68+
// No need to preserve tags since we can see the definition:
69+
__builtin_memcpy(buf, &nocap_struct, sizeof(nocap_struct));
70+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
71+
// CHECK-SAME: , i64 128, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
72+
73+
// We can see the variable declaration but it's a pointer so we can't add the attribute
74+
__builtin_memcpy(buf, nocap_struct_ptr, sizeof(*nocap_struct_ptr));
75+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
76+
// CHECK-SAME: , i64 128, i1 false){{$}}
77+
78+
/// Here we can see the array definition, so no need to preserve tags:
79+
__builtin_memcpy(buf, nocap_struct_array, sizeof(nocap_struct_array));
80+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
81+
// CHECK-SAME: , i64 512, i1 false){{$}}
82+
// TODO: We don't check the size against the array size so we assume this could hold tags
83+
// TODO-CHECK-SAME: , i64 512, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
84+
/// TODO: For unsized global arrays we could assume that the copy stays in-bounds
85+
/// for the tag-preservation analysis, but for now we just don't set the attribute.
86+
__builtin_memcpy(buf, nocap_struct_array_unsized, sizeof(struct NoCaps) * 3);
87+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
88+
// CHECK-SAME: , i64 384, i1 false){{$}}
89+
// TODO-CHECK-SAME: , i64 384, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
90+
}
91+
92+
struct WithCaps {
93+
int i;
94+
union {
95+
long l;
96+
void *p;
97+
} u;
98+
};
99+
extern struct WithCaps withcap_struct;
100+
extern struct WithCaps *withcap_struct_ptr;
101+
extern struct WithCaps withcap_struct_array[4];
102+
extern struct WithCaps withcap_struct_array_unsized[];
103+
104+
void test_struct_with_caps(void *buf) {
105+
// CHECK-LABEL: @test_struct_with_caps(
106+
// In all of these cases we should add the must_preserve_tags attribute since
107+
// we are copying a capability type.
108+
__builtin_memcpy(buf, &withcap_struct, sizeof(withcap_struct));
109+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
110+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP:#[0-9]+]]{{$}}
111+
__builtin_memcpy(buf, withcap_struct_ptr, sizeof(*withcap_struct_ptr));
112+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
113+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP]]{{$}}
114+
__builtin_memcpy(buf, withcap_struct_array, sizeof(withcap_struct_array));
115+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
116+
// CHECK-SAME: , i64 128, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP]]{{$}}
117+
__builtin_memcpy(buf, withcap_struct_array_unsized, 80);
118+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
119+
// CHECK-SAME: , i64 80, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP]]{{$}}
120+
}
121+
122+
extern char global_char_array[32];
123+
extern unsigned char global_uchar_array[32];
124+
extern signed char global_schar_array[32];
125+
126+
void test_char_array(void *buf) {
127+
// CHECK-LABEL: @test_char_array(
128+
// Arrays of type "(unsigned) char" are special and can be assumed to hold any type.
129+
// We extend this to signed char as well to avoid potentially surprising behaviour.
130+
// None of these cases should add the no_preserve_tags attribute:
131+
__builtin_memcpy(buf, &global_char_array, sizeof(global_char_array));
132+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
133+
// CHECK-SAME: , i64 32, i1 false){{$}}
134+
__builtin_memcpy(buf, &global_uchar_array, sizeof(global_uchar_array));
135+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
136+
// CHECK-SAME: , i64 32, i1 false){{$}}
137+
__builtin_memcpy(buf, &global_schar_array, sizeof(global_schar_array));
138+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
139+
// CHECK-SAME: , i64 32, i1 false){{$}}
140+
}
141+
142+
// CHECK: [[NO_PRESERVE_TAGS]] = { no_preserve_cheri_tags }
143+
// CHECK: [[MUST_PRESERVE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" }
144+
// CHECK: [[MUST_PRESERVE_STRUCT_WITHCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct WithCaps'" }

0 commit comments

Comments
 (0)