diff --git a/clang/lib/AST/Randstruct.cpp b/clang/lib/AST/Randstruct.cpp index f6d7ba1c24164b..477915eade5479 100644 --- a/clang/lib/AST/Randstruct.cpp +++ b/clang/lib/AST/Randstruct.cpp @@ -150,14 +150,14 @@ void randomizeStructureLayoutImpl(const ASTContext &Context, if (CurrentBitfieldRun) Buckets.push_back(std::move(CurrentBitfieldRun)); - std::shuffle(std::begin(Buckets), std::end(Buckets), RNG); + llvm::shuffle(std::begin(Buckets), std::end(Buckets), RNG); // Produce the new ordering of the elements from the Buckets. SmallVector FinalOrder; for (const std::unique_ptr &B : Buckets) { llvm::SmallVectorImpl &RandFields = B->fields(); if (!B->isBitfieldRun()) - std::shuffle(std::begin(RandFields), std::end(RandFields), RNG); + llvm::shuffle(std::begin(RandFields), std::end(RandFields), RNG); FinalOrder.insert(FinalOrder.end(), RandFields.begin(), RandFields.end()); } diff --git a/clang/unittests/AST/CMakeLists.txt b/clang/unittests/AST/CMakeLists.txt index ab718d192024e2..2dcef2d2fca0e4 100644 --- a/clang/unittests/AST/CMakeLists.txt +++ b/clang/unittests/AST/CMakeLists.txt @@ -25,6 +25,7 @@ add_clang_unittest(ASTTests EvaluateAsRValueTest.cpp ExternalASTSourceTest.cpp NamedDeclPrinterTest.cpp + RandstructTest.cpp RecursiveASTVisitorTest.cpp SizelessTypesTest.cpp SourceLocationTest.cpp diff --git a/clang/unittests/AST/RandstructTest.cpp b/clang/unittests/AST/RandstructTest.cpp new file mode 100644 index 00000000000000..932ef7b3c48cc7 --- /dev/null +++ b/clang/unittests/AST/RandstructTest.cpp @@ -0,0 +1,442 @@ +//===- unittest/AST/RandstructTest.cpp ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains tests for Clang's structure field layout randomization. +// +//===----------------------------------------------------------------------===// + +/* + * Build this test suite by running `make ASTTests` in the build folder. + * + * Run this test suite by running the following in the build folder: + * ` ./tools/clang/unittests/AST/ASTTests + * --gtest_filter=StructureLayoutRandomization*` + */ + +#include "clang/AST/Randstruct.h" +#include "gtest/gtest.h" + +#include "DeclMatcher.h" +#include "clang/AST/RecordLayout.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Testing/CommandLineArgs.h" +#include "clang/Tooling/Tooling.h" + +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::randstruct; + +using field_names = std::vector; + +namespace { + +std::unique_ptr makeAST(const std::string &SourceCode) { + std::vector Args = getCommandLineArgsForTesting(Lang_C99); + Args.push_back("-frandomize-layout-seed=1234567890abcdef"); + + IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer(); + + return tooling::buildASTFromCodeWithArgs( + SourceCode, Args, "input.c", "clang-tool", + std::make_shared(), + tooling::getClangStripDependencyFileAdjuster(), + tooling::FileContentMappings(), &IgnoringConsumer); +} + +RecordDecl *getRecordDeclFromAST(const ASTContext &C, const std::string &Name) { + RecordDecl *RD = FirstDeclMatcher().match( + C.getTranslationUnitDecl(), recordDecl(hasName(Name))); + return RD; +} + +std::vector getFieldNamesFromRecord(const RecordDecl *RD) { + std::vector Fields; + + Fields.reserve(8); + for (auto *Field : RD->fields()) + Fields.push_back(Field->getNameAsString()); + + return Fields; +} + +bool isSubsequence(const field_names &Seq, const field_names &Subseq) { + unsigned SeqLen = Seq.size(); + unsigned SubLen = Subseq.size(); + + bool IsSubseq = false; + for (unsigned I = 0; I < SeqLen; ++I) + if (Seq[I] == Subseq[0]) { + IsSubseq = true; + for (unsigned J = 0; J + I < SeqLen && J < SubLen; ++J) { + if (Seq[J + I] != Subseq[J]) { + IsSubseq = false; + break; + } + } + } + + return IsSubseq; +} + +} // end anonymous namespace + +namespace clang { +namespace ast_matchers { + +#define RANDSTRUCT_TEST_SUITE_TEST StructureLayoutRandomizationTestSuiteTest + +TEST(RANDSTRUCT_TEST_SUITE_TEST, CanDetermineIfSubsequenceExists) { + const field_names Seq = {"a", "b", "c", "d"}; + + EXPECT_TRUE(isSubsequence(Seq, {"b", "c"})); + EXPECT_TRUE(isSubsequence(Seq, {"a", "b", "c", "d"})); + EXPECT_TRUE(isSubsequence(Seq, {"b", "c", "d"})); + EXPECT_TRUE(isSubsequence(Seq, {"a"})); + EXPECT_FALSE(isSubsequence(Seq, {"a", "d"})); +} + +#define RANDSTRUCT_TEST StructureLayoutRandomization + +TEST(RANDSTRUCT_TEST, UnmarkedStruct) { + const std::unique_ptr AST = makeAST(R"c( + struct test { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + }; + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"}; + + EXPECT_FALSE(RD->hasAttr()); + EXPECT_FALSE(RD->isRandomized()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, MarkedNoRandomize) { + const std::unique_ptr AST = makeAST(R"c( + struct test { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + } __attribute__((no_randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"}; + + EXPECT_TRUE(RD->hasAttr()); + EXPECT_FALSE(RD->isRandomized()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, MarkedRandomize) { + const std::unique_ptr AST = makeAST(R"c( + struct test { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + const field_names Expected = {"lettuce", "bacon", "mayonnaise", "tomato"}; + + EXPECT_TRUE(RD->hasAttr()); + EXPECT_TRUE(RD->isRandomized()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) { + const std::unique_ptr AST = makeAST(R"c( + struct test __attribute__((randomize_layout)); + struct test { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + } __attribute__((no_randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + DiagnosticsEngine &Diags = AST->getDiagnostics(); + + EXPECT_FALSE(Diags.hasFatalErrorOccurred()); + EXPECT_FALSE(Diags.hasUncompilableErrorOccurred()); + EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred()); + EXPECT_EQ(Diags.getNumWarnings(), 1u); + EXPECT_EQ(Diags.getNumErrors(), 0u); +} + +TEST(RANDSTRUCT_TEST, MismatchedAttrsRandomizeVsNoRandomize) { + const std::unique_ptr AST = makeAST(R"c( + struct test2 { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + } __attribute__((randomize_layout)) __attribute__((no_randomize_layout)); + )c"); + + EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); + + DiagnosticsEngine &Diags = AST->getDiagnostics(); + + EXPECT_TRUE(Diags.hasUncompilableErrorOccurred()); + EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred()); + EXPECT_EQ(Diags.getNumWarnings(), 0u); + EXPECT_EQ(Diags.getNumErrors(), 1u); +} + +TEST(RANDSTRUCT_TEST, MismatchedAttrsNoRandomizeVsRandomize) { + const std::unique_ptr AST = makeAST(R"c( + struct test3 { + int bacon; + long lettuce; + long long tomato; + float mayonnaise; + } __attribute__((no_randomize_layout)) __attribute__((randomize_layout)); + )c"); + + EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); + + DiagnosticsEngine &Diags = AST->getDiagnostics(); + + EXPECT_TRUE(Diags.hasUncompilableErrorOccurred()); + EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred()); + EXPECT_EQ(Diags.getNumWarnings(), 0u); + EXPECT_EQ(Diags.getNumErrors(), 1u); +} + +TEST(RANDSTRUCT_TEST, CheckAdjacentBitfieldsRemainAdjacentAfterRandomization) { + const std::unique_ptr AST = makeAST(R"c( + struct test { + int a; + int b; + int x : 1; + int y : 1; + int z : 1; + int c; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + const field_names Expected = {"a", "b", "c", "x", "y", "z"}; + const field_names Subseq = {"x", "y", "z"}; + const field_names Actual = getFieldNamesFromRecord(RD); + + EXPECT_TRUE(isSubsequence(Actual, Subseq)); + EXPECT_EQ(Expected, Actual); +} + +TEST(RANDSTRUCT_TEST, CheckVariableLengthArrayMemberRemainsAtEndOfStructure) { + const std::unique_ptr AST = makeAST(R"c( + struct test { + int a; + double b; + short c; + char name[]; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + const field_names Expected = {"c", "a", "name", "b"}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) { + const std::unique_ptr AST = makeAST(R"c( + struct test_struct { + char a; + float b[3]; + short c; + int d; + } __attribute__((packed, randomize_layout)); + + struct another_struct { + char a; + char b[5]; + int c; + } __attribute__((packed, randomize_layout)); + + struct last_struct { + char a; + long long b; + int c[]; + } __attribute__((packed, randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that + // Clang's RecordBuilders can actually flesh out the information like + // alignment, etc. + { + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_struct"); + const ASTRecordLayout *Layout = + &AST->getASTContext().getASTRecordLayout(RD); + const field_names Expected = {"b", "a", "c", "d"}; + + EXPECT_EQ(19, Layout->getSize().getQuantity()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + } + + { + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "another_struct"); + const ASTRecordLayout *Layout = + &AST->getASTContext().getASTRecordLayout(RD); + const field_names Expected = {"c", "b", "a"}; + + EXPECT_EQ(10, Layout->getSize().getQuantity()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + } + + { + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "last_struct"); + const ASTRecordLayout *Layout = + &AST->getASTContext().getASTRecordLayout(RD); + const field_names Expected = {"a", "c", "b"}; + + EXPECT_EQ(9, Layout->getSize().getQuantity()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + } +} + +TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) { + const std::unique_ptr AST = makeAST(R"c( + struct test_struct { + int a : 1; + int : 0; + int b : 1; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_struct"); + const field_names Expected = {"b", "a", ""}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) { + const std::unique_ptr AST = makeAST(R"c( + union test_union { + int a; + int b; + int c; + int d; + int e; + int f; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_union"); + const field_names Expected = {"a", "b", "c", "d", "e", "f"}; + + EXPECT_FALSE(RD->isRandomized()); + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); +} + +TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) { + const std::unique_ptr AST = makeAST(R"c( + struct test_struct { + int a; + struct sub_struct { + int b; + int c; + int d; + int e; + int f; + } __attribute__((randomize_layout)) s; + int f; + struct { + int g; + int h; + int i; + int j; + int k; + }; + int l; + union { + int m; + int n; + int o; + int p; + int q; + }; + int r; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_struct"); + const field_names Expected = {"", "l", "r", "f", "a", "s", ""}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + + bool AnonStructTested = false; + bool AnonUnionTested = false; + for (const Decl *D : RD->decls()) + if (const FieldDecl *FD = dyn_cast(D)) { + if (const auto *Record = FD->getType()->getAs()) { + RD = Record->getDecl(); + if (RD->isAnonymousStructOrUnion()) { + if (RD->isUnion()) { + const field_names Expected = {"m", "n", "o", "p", "q"}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + AnonUnionTested = true; + } else { + const field_names Expected = {"g", "h", "i", "j", "k"}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + AnonStructTested = true; + } + } else if (RD->isStruct()) { + const field_names Expected = {"c", "b", "e", "d", "f"}; + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + } + } + } + + EXPECT_TRUE(AnonStructTested); + EXPECT_TRUE(AnonUnionTested); +} + +} // namespace ast_matchers +} // namespace clang