Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 1 | //===- unittests/Lex/HeaderMapTest.cpp - HeaderMap tests ----------===// |
| 2 | // |
Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 6 | // |
| 7 | //===--------------------------------------------------------------===// |
| 8 | |
Duncan P. N. Exon Smith | 08fd70f | 2016-02-20 22:53:22 +0000 | [diff] [blame] | 9 | #include "clang/Basic/CharInfo.h" |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 10 | #include "clang/Lex/HeaderMap.h" |
| 11 | #include "clang/Lex/HeaderMapTypes.h" |
Duncan P. N. Exon Smith | 08fd70f | 2016-02-20 22:53:22 +0000 | [diff] [blame] | 12 | #include "llvm/ADT/SmallString.h" |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 13 | #include "llvm/Support/SwapByteOrder.h" |
| 14 | #include "gtest/gtest.h" |
Duncan P. N. Exon Smith | 08fd70f | 2016-02-20 22:53:22 +0000 | [diff] [blame] | 15 | #include <cassert> |
Duncan P. N. Exon Smith | 96a01fa | 2016-02-21 00:14:36 +0000 | [diff] [blame] | 16 | #include <type_traits> |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 17 | |
| 18 | using namespace clang; |
| 19 | using namespace llvm; |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | // Lay out a header file for testing. |
| 24 | template <unsigned NumBuckets, unsigned NumBytes> struct MapFile { |
| 25 | HMapHeader Header; |
| 26 | HMapBucket Buckets[NumBuckets]; |
| 27 | unsigned char Bytes[NumBytes]; |
| 28 | |
| 29 | void init() { |
| 30 | memset(this, 0, sizeof(MapFile)); |
| 31 | Header.Magic = HMAP_HeaderMagicNumber; |
| 32 | Header.Version = HMAP_HeaderVersion; |
| 33 | Header.NumBuckets = NumBuckets; |
| 34 | Header.StringsOffset = sizeof(Header) + sizeof(Buckets); |
| 35 | } |
| 36 | |
| 37 | void swapBytes() { |
| 38 | using llvm::sys::getSwappedBytes; |
| 39 | Header.Magic = getSwappedBytes(Header.Magic); |
| 40 | Header.Version = getSwappedBytes(Header.Version); |
| 41 | Header.NumBuckets = getSwappedBytes(Header.NumBuckets); |
| 42 | Header.StringsOffset = getSwappedBytes(Header.StringsOffset); |
| 43 | } |
| 44 | |
| 45 | std::unique_ptr<const MemoryBuffer> getBuffer() const { |
| 46 | return MemoryBuffer::getMemBuffer( |
| 47 | StringRef(reinterpret_cast<const char *>(this), sizeof(MapFile)), |
| 48 | "header", |
| 49 | /* RequresNullTerminator */ false); |
| 50 | } |
| 51 | }; |
| 52 | |
Duncan P. N. Exon Smith | 08fd70f | 2016-02-20 22:53:22 +0000 | [diff] [blame] | 53 | // The header map hash function. |
| 54 | static inline unsigned getHash(StringRef Str) { |
| 55 | unsigned Result = 0; |
| 56 | for (char C : Str) |
| 57 | Result += toLowercase(C) * 13; |
| 58 | return Result; |
| 59 | } |
| 60 | |
| 61 | template <class FileTy> struct FileMaker { |
| 62 | FileTy &File; |
| 63 | unsigned SI = 1; |
| 64 | unsigned BI = 0; |
| 65 | FileMaker(FileTy &File) : File(File) {} |
| 66 | |
| 67 | unsigned addString(StringRef S) { |
| 68 | assert(SI + S.size() + 1 <= sizeof(File.Bytes)); |
| 69 | std::copy(S.begin(), S.end(), File.Bytes + SI); |
| 70 | auto OldSI = SI; |
| 71 | SI += S.size() + 1; |
| 72 | return OldSI; |
| 73 | } |
| 74 | void addBucket(unsigned Hash, unsigned Key, unsigned Prefix, unsigned Suffix) { |
| 75 | assert(!(File.Header.NumBuckets & (File.Header.NumBuckets - 1))); |
| 76 | unsigned I = Hash & (File.Header.NumBuckets - 1); |
| 77 | do { |
| 78 | if (!File.Buckets[I].Key) { |
| 79 | File.Buckets[I].Key = Key; |
| 80 | File.Buckets[I].Prefix = Prefix; |
| 81 | File.Buckets[I].Suffix = Suffix; |
| 82 | ++File.Header.NumEntries; |
| 83 | return; |
| 84 | } |
| 85 | ++I; |
| 86 | I &= File.Header.NumBuckets - 1; |
| 87 | } while (I != (Hash & (File.Header.NumBuckets - 1))); |
| 88 | llvm_unreachable("no empty buckets"); |
| 89 | } |
| 90 | }; |
| 91 | |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 92 | TEST(HeaderMapTest, checkHeaderEmpty) { |
| 93 | bool NeedsSwap; |
| 94 | ASSERT_FALSE(HeaderMapImpl::checkHeader( |
| 95 | *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap)); |
| 96 | ASSERT_FALSE(HeaderMapImpl::checkHeader( |
| 97 | *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap)); |
| 98 | } |
| 99 | |
| 100 | TEST(HeaderMapTest, checkHeaderMagic) { |
| 101 | MapFile<1, 1> File; |
| 102 | File.init(); |
| 103 | File.Header.Magic = 0; |
| 104 | bool NeedsSwap; |
| 105 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 106 | } |
| 107 | |
| 108 | TEST(HeaderMapTest, checkHeaderReserved) { |
| 109 | MapFile<1, 1> File; |
| 110 | File.init(); |
| 111 | File.Header.Reserved = 1; |
| 112 | bool NeedsSwap; |
| 113 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 114 | } |
| 115 | |
| 116 | TEST(HeaderMapTest, checkHeaderVersion) { |
| 117 | MapFile<1, 1> File; |
| 118 | File.init(); |
| 119 | ++File.Header.Version; |
| 120 | bool NeedsSwap; |
| 121 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 122 | } |
| 123 | |
| 124 | TEST(HeaderMapTest, checkHeaderValidButEmpty) { |
| 125 | MapFile<1, 1> File; |
| 126 | File.init(); |
| 127 | bool NeedsSwap; |
| 128 | ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 129 | ASSERT_FALSE(NeedsSwap); |
| 130 | |
| 131 | File.swapBytes(); |
| 132 | ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 133 | ASSERT_TRUE(NeedsSwap); |
| 134 | } |
| 135 | |
Duncan P. N. Exon Smith | dfe8530 | 2016-02-20 21:00:58 +0000 | [diff] [blame] | 136 | TEST(HeaderMapTest, checkHeader3Buckets) { |
| 137 | MapFile<3, 1> File; |
| 138 | ASSERT_EQ(3 * sizeof(HMapBucket), sizeof(File.Buckets)); |
| 139 | |
| 140 | File.init(); |
| 141 | bool NeedsSwap; |
| 142 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 143 | } |
| 144 | |
Duncan P. N. Exon Smith | 3a7def0 | 2016-02-22 22:24:22 +0000 | [diff] [blame] | 145 | TEST(HeaderMapTest, checkHeader0Buckets) { |
| 146 | // Create with 1 bucket to avoid 0-sized arrays. |
| 147 | MapFile<1, 1> File; |
| 148 | File.init(); |
| 149 | File.Header.NumBuckets = 0; |
| 150 | bool NeedsSwap; |
| 151 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 152 | } |
| 153 | |
Duncan P. N. Exon Smith | 8d6a31c02 | 2016-02-20 21:24:31 +0000 | [diff] [blame] | 154 | TEST(HeaderMapTest, checkHeaderNotEnoughBuckets) { |
| 155 | MapFile<1, 1> File; |
| 156 | File.init(); |
| 157 | File.Header.NumBuckets = 8; |
| 158 | bool NeedsSwap; |
| 159 | ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 160 | } |
| 161 | |
Duncan P. N. Exon Smith | 08fd70f | 2016-02-20 22:53:22 +0000 | [diff] [blame] | 162 | TEST(HeaderMapTest, lookupFilename) { |
| 163 | typedef MapFile<2, 7> FileTy; |
| 164 | FileTy File; |
| 165 | File.init(); |
| 166 | |
| 167 | FileMaker<FileTy> Maker(File); |
| 168 | auto a = Maker.addString("a"); |
| 169 | auto b = Maker.addString("b"); |
| 170 | auto c = Maker.addString("c"); |
| 171 | Maker.addBucket(getHash("a"), a, b, c); |
| 172 | |
| 173 | bool NeedsSwap; |
| 174 | ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 175 | ASSERT_FALSE(NeedsSwap); |
| 176 | HeaderMapImpl Map(File.getBuffer(), NeedsSwap); |
| 177 | |
| 178 | SmallString<8> DestPath; |
| 179 | ASSERT_EQ("bc", Map.lookupFilename("a", DestPath)); |
| 180 | } |
| 181 | |
Duncan P. N. Exon Smith | 96a01fa | 2016-02-21 00:14:36 +0000 | [diff] [blame] | 182 | template <class FileTy, class PaddingTy> struct PaddedFile { |
| 183 | FileTy File; |
| 184 | PaddingTy Padding; |
| 185 | }; |
| 186 | |
Duncan P. N. Exon Smith | b5ce11f | 2016-02-23 00:48:16 +0000 | [diff] [blame] | 187 | TEST(HeaderMapTest, lookupFilenameTruncatedSuffix) { |
Duncan P. N. Exon Smith | 96a01fa | 2016-02-21 00:14:36 +0000 | [diff] [blame] | 188 | typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy; |
| 189 | static_assert(std::is_standard_layout<FileTy>::value, |
| 190 | "Expected standard layout"); |
| 191 | static_assert(sizeof(FileTy) == 64, "check the math"); |
| 192 | PaddedFile<FileTy, uint64_t> P; |
| 193 | auto &File = P.File; |
| 194 | auto &Padding = P.Padding; |
| 195 | File.init(); |
| 196 | |
| 197 | FileMaker<FileTy> Maker(File); |
| 198 | auto a = Maker.addString("a"); |
| 199 | auto b = Maker.addString("b"); |
| 200 | auto c = Maker.addString("c"); |
| 201 | Maker.addBucket(getHash("a"), a, b, c); |
| 202 | |
| 203 | // Add 'x' characters to cause an overflow into Padding. |
| 204 | ASSERT_EQ('c', File.Bytes[5]); |
| 205 | for (unsigned I = 6; I < sizeof(File.Bytes); ++I) { |
| 206 | ASSERT_EQ(0, File.Bytes[I]); |
| 207 | File.Bytes[I] = 'x'; |
| 208 | } |
| 209 | Padding = 0xffffffff; // Padding won't stop it either. |
| 210 | |
| 211 | bool NeedsSwap; |
| 212 | ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 213 | ASSERT_FALSE(NeedsSwap); |
| 214 | HeaderMapImpl Map(File.getBuffer(), NeedsSwap); |
| 215 | |
| 216 | // The string for "c" runs to the end of File. Check that the suffix |
Duncan P. N. Exon Smith | b5ce11f | 2016-02-23 00:48:16 +0000 | [diff] [blame] | 217 | // ("cxxxx...") is detected as truncated, and an empty string is returned. |
Duncan P. N. Exon Smith | 96a01fa | 2016-02-21 00:14:36 +0000 | [diff] [blame] | 218 | SmallString<24> DestPath; |
Duncan P. N. Exon Smith | b5ce11f | 2016-02-23 00:48:16 +0000 | [diff] [blame] | 219 | ASSERT_EQ("", Map.lookupFilename("a", DestPath)); |
| 220 | } |
| 221 | |
| 222 | TEST(HeaderMapTest, lookupFilenameTruncatedPrefix) { |
| 223 | typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy; |
| 224 | static_assert(std::is_standard_layout<FileTy>::value, |
| 225 | "Expected standard layout"); |
| 226 | static_assert(sizeof(FileTy) == 64, "check the math"); |
| 227 | PaddedFile<FileTy, uint64_t> P; |
| 228 | auto &File = P.File; |
| 229 | auto &Padding = P.Padding; |
| 230 | File.init(); |
| 231 | |
| 232 | FileMaker<FileTy> Maker(File); |
| 233 | auto a = Maker.addString("a"); |
| 234 | auto c = Maker.addString("c"); |
| 235 | auto b = Maker.addString("b"); // Store the prefix last. |
| 236 | Maker.addBucket(getHash("a"), a, b, c); |
| 237 | |
| 238 | // Add 'x' characters to cause an overflow into Padding. |
| 239 | ASSERT_EQ('b', File.Bytes[5]); |
| 240 | for (unsigned I = 6; I < sizeof(File.Bytes); ++I) { |
| 241 | ASSERT_EQ(0, File.Bytes[I]); |
| 242 | File.Bytes[I] = 'x'; |
| 243 | } |
| 244 | Padding = 0xffffffff; // Padding won't stop it either. |
| 245 | |
| 246 | bool NeedsSwap; |
| 247 | ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); |
| 248 | ASSERT_FALSE(NeedsSwap); |
| 249 | HeaderMapImpl Map(File.getBuffer(), NeedsSwap); |
| 250 | |
| 251 | // The string for "b" runs to the end of File. Check that the prefix |
| 252 | // ("bxxxx...") is detected as truncated, and an empty string is returned. |
| 253 | SmallString<24> DestPath; |
| 254 | ASSERT_EQ("", Map.lookupFilename("a", DestPath)); |
Duncan P. N. Exon Smith | 96a01fa | 2016-02-21 00:14:36 +0000 | [diff] [blame] | 255 | } |
| 256 | |
Duncan P. N. Exon Smith | 9ab99ee | 2016-02-20 20:39:51 +0000 | [diff] [blame] | 257 | } // end namespace |