@@ -1152,6 +1152,248 @@ TEST(ScudoCombinedTest, QuarantineDisabled) {
11521152 EXPECT_EQ (Stats.find (" Stats: Quarantine" ), std::string::npos);
11531153}
11541154
1155+ struct UsableSizeClassConfig {
1156+ static const scudo::uptr NumBits = 1 ;
1157+ static const scudo::uptr MinSizeLog = 10 ;
1158+ static const scudo::uptr MidSizeLog = 10 ;
1159+ static const scudo::uptr MaxSizeLog = 13 ;
1160+ static const scudo::u16 MaxNumCachedHint = 8 ;
1161+ static const scudo::uptr MaxBytesCachedLog = 12 ;
1162+ static const scudo::uptr SizeDelta = 0 ;
1163+ };
1164+
1165+ struct TestExactUsableSizeConfig {
1166+ static const bool MaySupportMemoryTagging = false ;
1167+ static const bool QuarantineDisabled = true ;
1168+
1169+ template <class A > using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U , 1U >;
1170+
1171+ struct Primary {
1172+ // In order to properly test the usable size, this Primary config has
1173+ // four real size classes: 1024, 2048, 4096, 8192.
1174+ using SizeClassMap = scudo::FixedSizeClassMap<UsableSizeClassConfig>;
1175+ static const scudo::uptr RegionSizeLog = 21U ;
1176+ static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;
1177+ static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
1178+ typedef scudo::uptr CompactPtrT;
1179+ static const scudo::uptr CompactPtrScale = 0 ;
1180+ static const bool EnableRandomOffset = true ;
1181+ static const scudo::uptr MapSizeIncrement = 1UL << 18 ;
1182+ static const scudo::uptr GroupSizeLog = 18 ;
1183+ };
1184+ template <typename Config>
1185+ using PrimaryT = scudo::SizeClassAllocator64<Config>;
1186+
1187+ struct Secondary {
1188+ template <typename Config>
1189+ using CacheT = scudo::MapAllocatorNoCache<Config>;
1190+ };
1191+
1192+ template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;
1193+ };
1194+
1195+ template <class AllocatorT > void VerifyExactUsableSize (AllocatorT &Allocator) {
1196+ // Scan through all sizes up to 10000 then some larger sizes.
1197+ for (scudo::uptr Size = 1 ; Size < 10000 ; Size++) {
1198+ void *P = Allocator.allocate (Size, Origin);
1199+ EXPECT_EQ (Size, Allocator.getUsableSize (P))
1200+ << " Failed usable size at allocation size " << Size;
1201+ Allocator.deallocate (P, Origin);
1202+ }
1203+
1204+ // Verify that aligned allocations also return the exact size allocated.
1205+ const scudo::uptr AllocSize = 313 ;
1206+ for (scudo::uptr Align = 1 ; Align <= 8 ; Align++) {
1207+ void *P = Allocator.allocate (AllocSize, Origin, 1U << Align);
1208+ EXPECT_EQ (AllocSize, Allocator.getUsableSize (P))
1209+ << " Failed usable size at allocation size " << AllocSize << " at align "
1210+ << 1 << Align;
1211+ Allocator.deallocate (P, Origin);
1212+ }
1213+
1214+ // Verify an explicitly large allocations.
1215+ const scudo::uptr LargeAllocSize = 1000000 ;
1216+ void *P = Allocator.allocate (LargeAllocSize, Origin);
1217+ EXPECT_EQ (LargeAllocSize, Allocator.getUsableSize (P));
1218+ Allocator.deallocate (P, Origin);
1219+
1220+ // Now do it for aligned allocations for large allocations.
1221+ for (scudo::uptr Align = 1 ; Align <= 8 ; Align++) {
1222+ void *P = Allocator.allocate (LargeAllocSize, Origin, 1U << Align);
1223+ EXPECT_EQ (LargeAllocSize, Allocator.getUsableSize (P))
1224+ << " Failed usable size at allocation size " << AllocSize << " at align "
1225+ << 1 << Align;
1226+ Allocator.deallocate (P, Origin);
1227+ }
1228+ }
1229+
1230+ template <class AllocatorT >
1231+ void VerifyIterateOverUsableSize (AllocatorT &Allocator) {
1232+ // This will not verify if the size is the exact size or the size of the
1233+ // size class. Instead verify that the size matches the usable size and
1234+ // assume the other tests have verified getUsableSize.
1235+ std::unordered_map<void *, size_t > Pointers;
1236+ Pointers.insert ({Allocator.allocate (128 , Origin), 0U });
1237+ Pointers.insert ({Allocator.allocate (128 , Origin, 32 ), 0U });
1238+ Pointers.insert ({Allocator.allocate (2000 , Origin), 0U });
1239+ Pointers.insert ({Allocator.allocate (2000 , Origin, 64 ), 0U });
1240+ Pointers.insert ({Allocator.allocate (8000 , Origin), 0U });
1241+ Pointers.insert ({Allocator.allocate (8000 , Origin, 128 ), 0U });
1242+ Pointers.insert ({Allocator.allocate (2000205 , Origin), 0U });
1243+ Pointers.insert ({Allocator.allocate (2000205 , Origin, 128 ), 0U });
1244+ Pointers.insert ({Allocator.allocate (2000205 , Origin, 256 ), 0U });
1245+
1246+ Allocator.disable ();
1247+ Allocator.iterateOverChunks (
1248+ 0 , static_cast <scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1 ),
1249+ [](uintptr_t Base, size_t Size, void *Arg) {
1250+ std::unordered_map<void *, size_t > *Pointers =
1251+ reinterpret_cast <std::unordered_map<void *, size_t > *>(Arg);
1252+ (*Pointers)[reinterpret_cast <void *>(Base)] = Size;
1253+ },
1254+ reinterpret_cast <void *>(&Pointers));
1255+ Allocator.enable ();
1256+
1257+ for (auto [Ptr, IterateSize] : Pointers) {
1258+ EXPECT_NE (0U , IterateSize)
1259+ << " Pointer " << Ptr << " not found in iterateOverChunks call." ;
1260+ EXPECT_EQ (IterateSize, Allocator.getUsableSize (Ptr))
1261+ << " Pointer " << Ptr
1262+ << " mismatch between iterate size and usable size." ;
1263+ Allocator.deallocate (Ptr, Origin);
1264+ }
1265+ }
1266+
1267+ TEST (ScudoCombinedTest, ExactUsableSize) {
1268+ using AllocatorT = scudo::Allocator<TestExactUsableSizeConfig>;
1269+ auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT ());
1270+
1271+ VerifyExactUsableSize<AllocatorT>(*Allocator);
1272+ VerifyIterateOverUsableSize<AllocatorT>(*Allocator);
1273+ }
1274+
1275+ struct TestExactUsableSizeMTEConfig : TestExactUsableSizeConfig {
1276+ static const bool MaySupportMemoryTagging = true ;
1277+ };
1278+
1279+ TEST (ScudoCombinedTest, ExactUsableSizeMTE) {
1280+ if (!scudo::archSupportsMemoryTagging () ||
1281+ !scudo::systemDetectsMemoryTagFaultsTestOnly ())
1282+ TEST_SKIP (" Only supported on systems that can enable MTE." );
1283+
1284+ scudo::enableSystemMemoryTaggingTestOnly ();
1285+
1286+ using AllocatorT = scudo::Allocator<TestExactUsableSizeMTEConfig>;
1287+ auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT ());
1288+
1289+ VerifyExactUsableSize<AllocatorT>(*Allocator);
1290+ VerifyIterateOverUsableSize<AllocatorT>(*Allocator);
1291+ }
1292+
1293+ template <class AllocatorT >
1294+ void VerifyUsableSizePrimary (AllocatorT &Allocator) {
1295+ std::vector<scudo::uptr> SizeClasses = {1024U , 2048U , 4096U , 8192U };
1296+ for (size_t I = 0 ; I < SizeClasses.size (); I++) {
1297+ scudo::uptr SizeClass = SizeClasses[I];
1298+ scudo::uptr StartSize;
1299+ if (I == 0 )
1300+ StartSize = 1 ;
1301+ else
1302+ StartSize = SizeClasses[I - 1 ];
1303+ scudo::uptr UsableSize = SizeClass - scudo::Chunk::getHeaderSize ();
1304+ for (scudo::uptr Size = StartSize; Size < UsableSize; Size++) {
1305+ void *P = Allocator.allocate (Size, Origin);
1306+ EXPECT_EQ (UsableSize, Allocator.getUsableSize (P))
1307+ << " Failed usable size at allocation size " << Size
1308+ << " for size class " << SizeClass;
1309+ memset (P, 0xff , UsableSize);
1310+ EXPECT_EQ (Allocator.getBlockBeginTestOnly (P) + SizeClass,
1311+ reinterpret_cast <scudo::uptr>(P) + UsableSize);
1312+ Allocator.deallocate (P, Origin);
1313+ }
1314+
1315+ StartSize = UsableSize + 1 ;
1316+ }
1317+
1318+ std::vector<scudo::uptr> Alignments = {32U , 128U };
1319+ for (size_t I = 0 ; I < SizeClasses.size (); I++) {
1320+ scudo::uptr SizeClass = SizeClasses[I];
1321+ scudo::uptr AllocSize;
1322+ if (I == 0 )
1323+ AllocSize = 1 ;
1324+ else
1325+ AllocSize = SizeClasses[I - 1 ] + 1 ;
1326+
1327+ for (auto Alignment : Alignments) {
1328+ void *P = Allocator.allocate (AllocSize, Origin, Alignment);
1329+ scudo::uptr UsableSize = Allocator.getUsableSize (P);
1330+ memset (P, 0xff , UsableSize);
1331+ EXPECT_EQ (Allocator.getBlockBeginTestOnly (P) + SizeClass,
1332+ reinterpret_cast <scudo::uptr>(P) + UsableSize)
1333+ << " Failed usable size at allocation size " << AllocSize
1334+ << " for size class " << SizeClass << " at alignment " << Alignment;
1335+ Allocator.deallocate (P, Origin);
1336+ }
1337+ }
1338+ }
1339+
1340+ template <class AllocatorT >
1341+ void VerifyUsableSizeSecondary (AllocatorT &Allocator) {
1342+ const scudo::uptr LargeAllocSize = 996780 ;
1343+ const scudo::uptr PageSize = scudo::getPageSizeCached ();
1344+ void *P = Allocator.allocate (LargeAllocSize, Origin);
1345+ scudo::uptr UsableSize = Allocator.getUsableSize (P);
1346+ memset (P, 0xff , UsableSize);
1347+ // Assumes that the secondary always rounds up allocations to a page boundary.
1348+ EXPECT_EQ (scudo::roundUp (reinterpret_cast <scudo::uptr>(P) + LargeAllocSize,
1349+ PageSize),
1350+ reinterpret_cast <scudo::uptr>(P) + UsableSize);
1351+ Allocator.deallocate (P, Origin);
1352+
1353+ // Check aligned allocations now.
1354+ for (scudo::uptr Alignment = 1 ; Alignment <= 8 ; Alignment++) {
1355+ void *P = Allocator.allocate (LargeAllocSize, Origin, 1U << Alignment);
1356+ scudo::uptr UsableSize = Allocator.getUsableSize (P);
1357+ EXPECT_EQ (scudo::roundUp (reinterpret_cast <scudo::uptr>(P) + LargeAllocSize,
1358+ PageSize),
1359+ reinterpret_cast <scudo::uptr>(P) + UsableSize)
1360+ << " Failed usable size at allocation size " << LargeAllocSize
1361+ << " at alignment " << Alignment;
1362+ Allocator.deallocate (P, Origin);
1363+ }
1364+ }
1365+
1366+ struct TestFullUsableSizeConfig : TestExactUsableSizeConfig {
1367+ static const bool ExactUsableSize = false ;
1368+ };
1369+
1370+ TEST (ScudoCombinedTest, FullUsableSize) {
1371+ using AllocatorT = scudo::Allocator<TestFullUsableSizeConfig>;
1372+ auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT ());
1373+
1374+ VerifyUsableSizePrimary<AllocatorT>(*Allocator);
1375+ VerifyUsableSizeSecondary<AllocatorT>(*Allocator);
1376+ VerifyIterateOverUsableSize<AllocatorT>(*Allocator);
1377+ }
1378+
1379+ struct TestFullUsableSizeMTEConfig : TestFullUsableSizeConfig {
1380+ static const bool MaySupportMemoryTagging = true ;
1381+ };
1382+
1383+ TEST (ScudoCombinedTest, FullUsableSizeMTE) {
1384+ if (!scudo::archSupportsMemoryTagging () ||
1385+ !scudo::systemDetectsMemoryTagFaultsTestOnly ())
1386+ TEST_SKIP (" Only supported on systems that can enable MTE." );
1387+
1388+ scudo::enableSystemMemoryTaggingTestOnly ();
1389+
1390+ using AllocatorT = scudo::Allocator<TestFullUsableSizeMTEConfig>;
1391+ auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT ());
1392+
1393+ // When MTE is enabled, you get exact sizes.
1394+ VerifyExactUsableSize<AllocatorT>(*Allocator);
1395+ VerifyIterateOverUsableSize<AllocatorT>(*Allocator);
1396+ }
11551397// Verify that no special quarantine blocks appear in iterateOverChunks.
11561398TEST (ScudoCombinedTest, QuarantineIterateOverChunks) {
11571399 using AllocatorT = TestAllocator<TestQuarantineConfig>;
0 commit comments