diff options
Diffstat (limited to 'clang/unittests')
43 files changed, 1638 insertions, 115 deletions
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp index 4c7ea5e..1647906 100644 --- a/clang/unittests/AST/ASTImporterTest.cpp +++ b/clang/unittests/AST/ASTImporterTest.cpp @@ -3204,6 +3204,57 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) { compoundStmt(has(callExpr(has(unresolvedMemberExpr()))))))))); } +TEST_P(ImportDecl, CycleInAutoTemplateSpec) { + MatchVerifier<Decl> Verifier; + const char *Code = R"( + template <class _CharT> + struct basic_string { + using value_type = _CharT; + }; + + template<typename T> + struct basic_string_view { + using value_type = T; + }; + + using string_view = basic_string_view<char>; + using string = basic_string<char>; + + template<typename T> + struct span { + }; + + template <typename StringT> + auto StrCatT(span<const StringT> pieces) { + basic_string<typename StringT::value_type> result; + return result; + } + + string StrCat(span<const string_view> pieces) { + return StrCatT(pieces); + } + + string StrCat(span<const string> pieces) { + return StrCatT(pieces); + } + + template <typename T> + auto declToImport(T pieces) { + return StrCat(pieces); + } + + void test() { + span<const string> pieces; + auto result = declToImport(pieces); + } +)"; + // This test reproduces the StrCatT recursion pattern with concepts and span + // that may cause infinite recursion during AST import due to circular + // dependencies + testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier, + functionTemplateDecl(hasName("declToImport"))); +} + TEST_P(ImportExpr, ConceptNoRequirement) { MatchVerifier<Decl> Verifier; const char *Code = R"( @@ -3300,6 +3351,72 @@ TEST_P(ImportExpr, ConceptNestedNonInstantiationDependentRequirement) { conceptDecl(has(requiresExpr(has(requiresExprBodyDecl()))))); } +TEST_P(ImportExpr, ImportSubstNonTypeTemplateParmPackExpr) { + MatchVerifier<Decl> Verifier; + const char *Code = R"( + template<auto ...> struct X {}; + template<typename ...> struct Z {}; + + template<int ...N> struct E { + template<int ...M> using B = Z<X<N, M>...>; + template<int M1, int M2> E(B<M1, M2>); + }; + using declToImport = E<1, 3>; + )"; + testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier, + typedefNameDecl(hasName("declToImport"))); +} + +TEST_P(ImportExpr, ImportCXXParenListInitExpr) { + MatchVerifier<Decl> Verifier; + const char *Code = R"( + struct Node { + int val; + double d; + }; + Node* declToImport() { return new Node(2, 3.14); } + )"; + testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier, + functionDecl(hasName("declToImport"))); +} + +TEST_P(ImportExpr, ImportPseudoObjectExpr) { + MatchVerifier<Decl> Verifier; + const char *Code = R"( + namespace std { + struct strong_ordering { + int n; + constexpr operator int() const { return n; } + static const strong_ordering less, equal, greater; + }; + constexpr strong_ordering strong_ordering::less{-1}, + strong_ordering::equal{0}, strong_ordering::greater{1}; + } + + struct A { + std::strong_ordering operator<=>(const A&) const; + }; + struct B { + bool operator==(const B&) const; + bool operator<(const B&) const; + }; + + template<typename T> struct Cmp : T { + std::strong_ordering operator<=>(const Cmp&) const = default; + }; + + void use(...); + void declToImport() { + use( + Cmp<A>() <=> Cmp<A>(), + Cmp<B>() <=> Cmp<B>() + ); + } + )"; + testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier, + functionDecl(hasName("declToImport"))); +} + class ImportImplicitMethods : public ASTImporterOptionSpecificTestBase { public: static constexpr auto DefaultCode = R"( diff --git a/clang/unittests/AST/TypePrinterTest.cpp b/clang/unittests/AST/TypePrinterTest.cpp index ca0380b..3cadf9b 100644 --- a/clang/unittests/AST/TypePrinterTest.cpp +++ b/clang/unittests/AST/TypePrinterTest.cpp @@ -295,3 +295,68 @@ TEST(TypePrinter, TemplateArgumentsSubstitution_Expressions) { Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); } } + +TEST(TypePrinter, NestedNameSpecifiers) { + constexpr char Code[] = R"cpp( + void level1() { + struct Inner { + Inner(int) { + struct { + union {} u; + } imem; + } + }; + } + )cpp"; + + // Types scoped immediately inside a function don't print the function name in + // their scope. + ASSERT_TRUE(PrintedTypeMatches( + Code, {}, varDecl(hasName("imem"), hasType(qualType().bind("id"))), + "struct (unnamed)", [](PrintingPolicy &Policy) { + Policy.FullyQualifiedName = true; + Policy.AnonymousTagLocations = false; + })); + + ASSERT_TRUE(PrintedTypeMatches( + Code, {}, varDecl(hasName("imem"), hasType(qualType().bind("id"))), + "struct (unnamed)", [](PrintingPolicy &Policy) { + Policy.FullyQualifiedName = false; + Policy.AnonymousTagLocations = false; + })); + + // Further levels of nesting print the entire scope. + ASSERT_TRUE(PrintedTypeMatches( + Code, {}, fieldDecl(hasName("u"), hasType(qualType().bind("id"))), + "union level1()::Inner::Inner(int)::(anonymous struct)::(unnamed)", + [](PrintingPolicy &Policy) { + Policy.FullyQualifiedName = true; + Policy.AnonymousTagLocations = false; + })); + + ASSERT_TRUE(PrintedTypeMatches( + Code, {}, fieldDecl(hasName("u"), hasType(qualType().bind("id"))), + "union (unnamed)", [](PrintingPolicy &Policy) { + Policy.FullyQualifiedName = false; + Policy.AnonymousTagLocations = false; + })); +} + +TEST(TypePrinter, NestedNameSpecifiersTypedef) { + constexpr char Code[] = R"cpp( + typedef union { + struct { + struct { + unsigned int baz; + } bar; + }; + } foo; + )cpp"; + + ASSERT_TRUE(PrintedTypeMatches( + Code, {}, fieldDecl(hasName("bar"), hasType(qualType().bind("id"))), + "struct foo::(anonymous struct)::(unnamed)", [](PrintingPolicy &Policy) { + Policy.FullyQualifiedName = true; + Policy.AnonymousTagLocations = false; + })); +} diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp index 8a957864..5d45235 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp @@ -26,6 +26,19 @@ TEST_P(ASTMatchersTest, IsExpandedFromMacro_MatchesInFile) { EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("MY_MACRO")))); } +static std::string constructMacroName(llvm::StringRef A, llvm::StringRef B) { + return (A + "_" + B).str(); +} + +TEST_P(ASTMatchersTest, IsExpandedFromMacro_ConstructedMacroName) { + StringRef input = R"cc( +#define MY_MACRO(a) (4 + (a)) + void Test() { MY_MACRO(4); } + )cc"; + auto matcher = isExpandedFromMacro(constructMacroName("MY", "MACRO")); + EXPECT_TRUE(matches(input, binaryOperator(matcher))); +} + TEST_P(ASTMatchersTest, IsExpandedFromMacro_MatchesNested) { StringRef input = R"cc( #define MY_MACRO(a) (4 + (a)) diff --git a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp index 3fcb558..108b32e 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp @@ -2353,6 +2353,26 @@ TEST_P(ASTMatchersTest, ReferenceTypeLocTest_BindsToAnyRvalueReferenceTypeLoc) { EXPECT_TRUE(matches("float&& r = 3.0;", matcher)); } +TEST_P(ASTMatchersTest, ArrayTypeLocTest_BindsToAnyArrayTypeLoc) { + auto matcher = varDecl(hasName("x"), hasTypeLoc(arrayTypeLoc())); + EXPECT_TRUE(matches("int x[3];", matcher)); + EXPECT_TRUE(matches("float x[3];", matcher)); + EXPECT_TRUE(matches("char x[3];", matcher)); + EXPECT_TRUE(matches("void* x[3];", matcher)); + EXPECT_TRUE(matches("const int x[3] = {1, 2, 3};", matcher)); + EXPECT_TRUE(matches("int x[3][4];", matcher)); + EXPECT_TRUE(matches("void foo(int x[]);", matcher)); + EXPECT_TRUE(matches("int a[] = {1, 2}; void foo() {int x[a[0]];}", matcher)); +} + +TEST_P(ASTMatchersTest, ArrayTypeLocTest_DoesNotBindToNonArrayTypeLoc) { + auto matcher = varDecl(hasName("x"), hasTypeLoc(arrayTypeLoc())); + EXPECT_TRUE(notMatches("int x;", matcher)); + EXPECT_TRUE(notMatches("float x;", matcher)); + EXPECT_TRUE(notMatches("char x;", matcher)); + EXPECT_TRUE(notMatches("void* x;", matcher)); +} + TEST_P(ASTMatchersTest, TemplateSpecializationTypeLocTest_BindsToVarDeclTemplateSpecialization) { if (!GetParam().isCXX()) { diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp index 8fc9a66d..d171d47 100644 --- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -2091,4 +2091,21 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByPointerToMemberOperator) { EXPECT_TRUE(isPointeeMutated(Results, AST.get())); } +TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsPointerToPointer) { + { + const std::string Code = "void f(int**); void g() { int* ip; f(&ip); }"; + auto AST = buildASTFromCode(Code); + auto Results = + match(withEnclosingCompound(declRefTo("ip")), AST->getASTContext()); + EXPECT_TRUE(isPointeeMutated(Results, AST.get())); + } + { + const std::string Code = "void f(void**); void g() { void* ip; f(&ip); }"; + auto AST = buildASTFromCode(Code); + auto Results = + match(withEnclosingCompound(declRefTo("ip")), AST->getASTContext()); + EXPECT_TRUE(isPointeeMutated(Results, AST.get())); + } +} + } // namespace clang diff --git a/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp b/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp index 8863011..60925543 100644 --- a/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp @@ -149,9 +149,18 @@ recordState(Elements=8, Branches=2, Joins=1) enterElement(return b ? p : q;) transfer() recordState(Elements=9, Branches=2, Joins=1) +enterElement([Parm: q] (Lifetime ends)) +transfer() +recordState(Elements=10, Branches=2, Joins=1) +enterElement([Parm: p] (Lifetime ends)) +transfer() +recordState(Elements=11, Branches=2, Joins=1) +enterElement([Parm: b] (Lifetime ends)) +transfer() +recordState(Elements=12, Branches=2, Joins=1) enterBlock(0, false) -recordState(Elements=9, Branches=2, Joins=1) +recordState(Elements=12, Branches=2, Joins=1) endAnalysis() )"); diff --git a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp index 2e528ed..8ea2015 100644 --- a/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp +++ b/clang/unittests/Analysis/FlowSensitive/MockHeaders.cpp @@ -1358,6 +1358,8 @@ bool operator!=(const Status &lhs, const Status &rhs); Status OkStatus(); Status InvalidArgumentError(const char *); +} // namespace absl + #endif // STATUS_H )cc"; @@ -1370,6 +1372,8 @@ constexpr const char StatusOrDefsHeader[] = R"cc( #include "std_type_traits.h" #include "std_utility.h" +namespace absl { + template <typename T> struct StatusOr; namespace internal_statusor { @@ -2232,6 +2236,95 @@ using testing::AssertionResult; #endif // TESTING_DEFS_H )cc"; +constexpr const char StdUniquePtrHeader[] = R"cc( +namespace std { + + template <typename T> + struct default_delete {}; + + template <typename T, typename D = default_delete<T>> + class unique_ptr { + public: + using element_type = T; + using deleter_type = D; + + constexpr unique_ptr(); + constexpr unique_ptr(nullptr_t) noexcept; + unique_ptr(unique_ptr&&); + explicit unique_ptr(T*); + template <typename U, typename E> + unique_ptr(unique_ptr<U, E>&&); + + ~unique_ptr(); + + unique_ptr& operator=(unique_ptr&&); + template <typename U, typename E> + unique_ptr& operator=(unique_ptr<U, E>&&); + unique_ptr& operator=(nullptr_t); + + void reset(T* = nullptr) noexcept; + T* release(); + T* get() const; + + T& operator*() const; + T* operator->() const; + explicit operator bool() const noexcept; + }; + + template <typename T, typename D> + class unique_ptr<T[], D> { + public: + T* get() const; + T& operator[](size_t i); + const T& operator[](size_t i) const; + }; + + template <typename T, typename... Args> + unique_ptr<T> make_unique(Args&&...); + + template <class T, class D> + void swap(unique_ptr<T, D>& x, unique_ptr<T, D>& y) noexcept; + + template <class T1, class D1, class T2, class D2> + bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + template <class T1, class D1, class T2, class D2> + bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y); + + template <class T, class D> + bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept; + template <class T, class D> + bool operator==(nullptr_t, const unique_ptr<T, D>& y) noexcept; + template <class T, class D> + bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept; + template <class T, class D> + bool operator!=(nullptr_t, const unique_ptr<T, D>& y) noexcept; + template <class T, class D> + bool operator<(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator<(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator<=(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator<=(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator>(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator>(nullptr_t, const unique_ptr<T, D>& y); + template <class T, class D> + bool operator>=(const unique_ptr<T, D>& x, nullptr_t); + template <class T, class D> + bool operator>=(nullptr_t, const unique_ptr<T, D>& y); +} +)cc"; + std::vector<std::pair<std::string, std::string>> getMockHeaders() { std::vector<std::pair<std::string, std::string>> Headers; Headers.emplace_back("cstddef.h", CStdDefHeader); @@ -2249,6 +2342,7 @@ std::vector<std::pair<std::string, std::string>> getMockHeaders() { Headers.emplace_back("statusor_defs.h", StatusOrDefsHeader); Headers.emplace_back("absl_log.h", AbslLogHeader); Headers.emplace_back("testing_defs.h", TestingDefsHeader); + Headers.emplace_back("std_unique_ptr.h", StdUniquePtrHeader); return Headers; } diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp index 66b3bba..a6308d1 100644 --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -17,6 +17,7 @@ #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" #include "clang/Analysis/FlowSensitive/NoopLattice.h" #include "clang/Analysis/FlowSensitive/RecordOps.h" @@ -4382,6 +4383,40 @@ TEST(TransferTest, VarDeclInitAssignConditionalOperator) { }); } +TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) { + std::string Code = R"( + struct A { + int i; + }; + + void target(A Foo, A Bar, bool Cond) { + A &Baz = Cond ? Foo : Bar; + // Make sure A::i is modeled. + Baz.i; + /*[[p]]*/ + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); + + auto *FooIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i", + ASTCtx, Env)); + auto *BarIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i", + ASTCtx, Env)); + auto *BazIVal = cast<IntegerValue>(getFieldValue( + &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i", + ASTCtx, Env)); + + EXPECT_NE(BazIVal, FooIVal); + EXPECT_NE(BazIVal, BarIVal); + }); +} + TEST(TransferTest, VarDeclInDoWhile) { std::string Code = R"( void target(int *Foo) { @@ -6150,6 +6185,45 @@ TEST(TransferTest, ConditionalOperatorValue) { }); } +TEST(TransferTest, ConditionalOperatorValuesTested) { + // We should be able to show that the result of the conditional operator, + // JoinResultMustBeB1, must be equal to B1, because the condition is checking + // `B1 == B2` and selecting B1 on the false branch, or B2 on the true branch. + // Similarly, for JoinResultMustBeB2 == B2. + // Note that the conditional operator involves a join of two *different* + // glvalues, before casting the lvalue to an rvalue, which may affect the + // implementation of the transfer function, and thus affect whether or not we + // can prove that IsB1 == B1. + std::string Code = R"( + void target(bool B1, bool B2) { + bool JoinResultMustBeB1 = (B1 == B2) ? B2 : B1; + bool JoinResultMustBeB2 = (B1 == B2) ? B1 : B2; + // [[p]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); + + auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1"); + auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2"); + auto &JoinResultMustBeB1 = + getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB1"); + auto &JoinResultMustBeB2 = + getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB2"); + + const Formula &MustBeB1_Eq_B1 = + Env.arena().makeEquals(JoinResultMustBeB1.formula(), B1.formula()); + EXPECT_TRUE(Env.proves(MustBeB1_Eq_B1)); + + const Formula &MustBeB2_Eq_B2 = + Env.arena().makeEquals(JoinResultMustBeB2.formula(), B2.formula()); + EXPECT_TRUE(Env.proves(MustBeB2_Eq_B2)); + }); +} + TEST(TransferTest, ConditionalOperatorLocation) { std::string Code = R"( void target(bool Cond, int I1, int I2) { @@ -6177,6 +6251,66 @@ TEST(TransferTest, ConditionalOperatorLocation) { }); } +TEST(TransferTest, ConditionalOperatorLocationUpdatedAfter) { + // We don't currently model a Conditional Operator with an LValue result + // as having aliases to the LHS and RHS (if it isn't just the same LValue + // on both sides). We also don't model that the update "may" happen + // (a weak update). So, we don't consider the LHS and RHS as being weakly + // updated at [[after_diff]]. + std::string Code = R"( + void target(bool Cond, bool B1, bool B2) { + (void)0; + // [[before_same]] + (Cond ? B1 : B1) = !B1; + // [[after_same]] + (Cond ? B1 : B2) = !B1; + // [[after_diff]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + Environment BeforeSameEnv = + getEnvironmentAtAnnotation(Results, "before_same").fork(); + Environment AfterSameEnv = + getEnvironmentAtAnnotation(Results, "after_same").fork(); + Environment AfterDiffEnv = + getEnvironmentAtAnnotation(Results, "after_diff").fork(); + + auto &BeforeSameB1 = + getValueForDecl<BoolValue>(ASTCtx, BeforeSameEnv, "B1"); + auto &AfterSameB1 = + getValueForDecl<BoolValue>(ASTCtx, AfterSameEnv, "B1"); + auto &AfterDiffB1 = + getValueForDecl<BoolValue>(ASTCtx, AfterDiffEnv, "B1"); + + EXPECT_NE(&BeforeSameB1, &AfterSameB1); + EXPECT_NE(&BeforeSameB1, &AfterDiffB1); + // FIXME: The formula for AfterSameB1 should be different from + // AfterDiffB1 to reflect that B1 may be updated. + EXPECT_EQ(&AfterSameB1, &AfterDiffB1); + + // The value of B1 is definitely different from before_same vs + // after_same. + const Formula &B1ChangedForSame = + AfterSameEnv.arena().makeNot(AfterSameEnv.arena().makeEquals( + AfterSameB1.formula(), BeforeSameB1.formula())); + EXPECT_TRUE(AfterSameEnv.allows(B1ChangedForSame)); + EXPECT_TRUE(AfterSameEnv.proves(B1ChangedForSame)); + + const Formula &B1ChangedForDiff = + AfterDiffEnv.arena().makeNot(AfterDiffEnv.arena().makeEquals( + AfterDiffB1.formula(), AfterSameB1.formula())); + // FIXME: It should be possible that B1 *may* be updated, so it may be + // that AfterSameB1 != AfterDiffB1 or AfterSameB1 == AfterDiffB1. + EXPECT_FALSE(AfterSameEnv.allows(B1ChangedForDiff)); + // proves() should be false, since B1 may or may not have changed + // depending on `Cond`. + EXPECT_FALSE(AfterSameEnv.proves(B1ChangedForDiff)); + }); +} + TEST(TransferTest, ConditionalOperatorOnConstantExpr) { // This is a regression test: We used to crash when a `ConstantExpr` was used // in the branches of a conditional operator. diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp index 5635ff4..deaeea3 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedStatusOrAccessModelTestFixture.cpp @@ -1725,6 +1725,90 @@ TEST_P(UncheckedStatusOrAccessModelTest, QcheckMacro) { )cc"); } +TEST_P(UncheckedStatusOrAccessModelTest, CheckOkMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_OK(sor.status()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_OK(sor); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUS s = Make<STATUS>(); + CHECK_OK(s); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckOkMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_OK(sor.status()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_OK(sor); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + STATUS s = Make<STATUS>(); + QCHECK_OK(s); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, CheckEqMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + CHECK_EQ(sor.status(), absl::OkStatus()); + sor.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + CHECK_EQ(Make<STATUS>(), absl::OkStatus()); + CHECK_EQ(absl::OkStatus(), Make<STATUS>()); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, QcheckEqMacro) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(STATUSOR_INT sor) { + QCHECK_EQ(sor.status(), absl::OkStatus()); + sor.value(); + } + )cc"); +} + TEST_P(UncheckedStatusOrAccessModelTest, CheckNeMacro) { ExpectDiagnosticsFor(R"cc( #include "unchecked_statusor_access_test_defs.h" @@ -3186,6 +3270,252 @@ TEST_P(UncheckedStatusOrAccessModelTest, ConstructStatusFromReference) { )cc"); } +TEST_P(UncheckedStatusOrAccessModelTest, AccessorCall) { + // Accessor returns reference. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); + } + )cc"); + + // Uses an operator + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& operator()() const { return sor_; } + }; + + void target(Foo foo) { + if (foo().ok()) foo().value(); + } + )cc"); + + // Calls nonconst method in between. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + void invalidate() {} + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) { + foo.invalidate(); + foo.sor().value(); // [[unsafe]] + } + } + )cc"); + + // Calls nonconst operator in between. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + void operator()() {} + + const STATUSOR_INT& sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) { + foo(); + foo.sor().value(); // [[unsafe]] + } + } + )cc"); + + // Accessor returns copy. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + STATUSOR_INT sor() const { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); + } + )cc"); + + // Non-const accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() { return sor_; } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); // [[unsafe]] + } + )cc"); + + // Non-const rvalue accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + STATUSOR_INT&& sor() { return std::move(sor_); } + }; + + void target(Foo foo) { + if (foo.sor().ok()) foo.sor().value(); // [[unsafe]] + } + )cc"); + + // const pointer accessor. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT* sor() const { return &sor_; } + }; + + void target(Foo foo) { + if (foo.sor()->ok()) foo.sor()->value(); + } + )cc"); + + // const pointer operator. + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT* operator->() const { return &sor_; } + }; + + void target(Foo foo) { + if (foo->ok()) foo->value(); + } + )cc"); + + // We copy the result of the accessor. + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + struct Foo { + STATUSOR_INT sor_; + + const STATUSOR_INT& sor() const { return sor_; } + }; + void target() { + Foo foo; + if (!foo.sor().ok()) return; + const auto sor = foo.sor(); + sor.value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, PointerLike) { + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo { + public: + std::pair<int, STATUSOR_VOIDPTR>& operator*() const; + std::pair<int, STATUSOR_VOIDPTR>* operator->() const; + bool operator!=(const Foo& other) const; + }; + + void target() { + Foo foo; + if (foo->second.ok() && *foo->second != nullptr) { + *foo->second; + (*foo).second.value(); + } + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + class Foo { + public: + std::pair<int, STATUSOR_INT>& operator*() const; + std::pair<int, STATUSOR_INT>* operator->() const; + }; + void target() { + Foo foo; + if (!foo->second.ok()) return; + foo->second.value(); + (*foo).second.value(); + } + )cc"); + ExpectDiagnosticsFor(R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target(std::pair<int, STATUSOR_VOIDPTR>* foo) { + if (foo->second.ok() && *foo->second != nullptr) { + *foo->second; + (*foo).second.value(); + } + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UniquePtr) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor_up = Make<std::unique_ptr<STATUSOR_INT>>(); + if (sor_up->ok()) sor_up->value(); + } + )cc"); +} + +TEST_P(UncheckedStatusOrAccessModelTest, UniquePtrReset) { + ExpectDiagnosticsFor( + R"cc( +#include "unchecked_statusor_access_test_defs.h" + + void target() { + auto sor_up = Make<std::unique_ptr<STATUSOR_INT>>(); + if (sor_up->ok()) { + sor_up.reset(Make<STATUSOR_INT*>()); + sor_up->value(); // [[unsafe]] + } + } + )cc"); +} + } // namespace std::string @@ -3235,6 +3565,7 @@ GetHeaders(UncheckedStatusOrAccessModelTestAliasKind AliasKind) { #include "std_pair.h" #include "absl_log.h" #include "testing_defs.h" +#include "std_unique_ptr.h" template <typename T> T Make(); diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 34af476..fee4e79 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -20,6 +20,7 @@ namespace clang::lifetimes::internal { namespace { using namespace ast_matchers; +using ::testing::Not; using ::testing::SizeIs; using ::testing::UnorderedElementsAreArray; @@ -58,6 +59,7 @@ public: BuildOptions.setAllAlwaysAdd(); BuildOptions.AddImplicitDtors = true; BuildOptions.AddTemporaryDtors = true; + BuildOptions.AddLifetime = true; // Run the main analysis. Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr); @@ -112,9 +114,10 @@ public: return {}; } std::vector<LoanID> LID; - for (const Loan &L : Analysis.getFactManager().getLoanMgr().getLoans()) - if (L.Path.D == VD) - LID.push_back(L.ID); + for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans()) + if (const auto *BL = dyn_cast<PathLoan>(L)) + if (BL->getAccessPath().D == VD) + LID.push_back(L->getID()); if (LID.empty()) { ADD_FAILURE() << "Loan for '" << VarName << "' not found."; return {}; @@ -122,6 +125,40 @@ public: return LID; } + // Gets the set of loans that are live at the given program point. A loan is + // considered live at point P if there is a live origin which contains this + // loan. + std::optional<LoanSet> getLiveLoansAtPoint(ProgramPoint P) const { + const auto &LiveOriginsAnalysis = Runner.getAnalysis().getLiveOrigins(); + const auto &LoanPropagation = Runner.getAnalysis().getLoanPropagation(); + + LivenessMap LiveOriginsMap = LiveOriginsAnalysis.getLiveOriginsAt(P); + + LoanSet::Factory F; + LoanSet Result = F.getEmptySet(); + + for (const auto &[OID, LI] : LiveOriginsMap) { + LoanSet Loans = LoanPropagation.getLoans(OID, P); + Result = clang::lifetimes::internal::utils::join(Result, Loans, F); + } + + if (Result.isEmpty()) + return std::nullopt; + + return Result; + } + + const ExpireFact * + getExpireFactFromAllFacts(const llvm::ArrayRef<const Fact *> &FactsInBlock, + const LoanID &loanID) { + for (const Fact *F : FactsInBlock) { + if (auto const *CurrentEF = F->getAs<ExpireFact>()) + if (CurrentEF->getLoanID() == loanID) + return CurrentEF; + } + return nullptr; + } + std::optional<LoanSet> getLoansAtPoint(OriginID OID, llvm::StringRef Annotation) { ProgramPoint PP = Runner.getProgramPoint(Annotation); @@ -141,6 +178,14 @@ public: return Result; } + ProgramPoint getProgramPoint(llvm::StringRef Annotation) { + return Runner.getProgramPoint(Annotation); + } + + llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) { + return Runner.getAnalysis().getFactManager().getBlockContaining(P); + } + private: template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) { auto &Ctx = Runner.getASTContext(); @@ -304,6 +349,43 @@ MATCHER_P2(AreLiveAtImpl, Annotation, ConfFilter, "") { return true; } +MATCHER_P2(HasLiveLoanAtExpiryImpl, HelperPtr, Annotation, "") { + llvm::StringRef VarName = arg; + LifetimeTestHelper &Helper = *HelperPtr; + + std::vector<LoanID> Loans = Helper.getLoansForVar(VarName); + if (Loans.empty()) { + *result_listener << "No loans found for variable" << VarName.str(); + return false; + } + + ProgramPoint PP = Helper.getProgramPoint(Annotation); + llvm::ArrayRef<const Fact *> AllFactsInBlock = Helper.getBlockContaining(PP); + + bool NoExpireFactLive = false; + for (const LoanID CurrentLoanID : Loans) { + const ExpireFact *EF = + Helper.getExpireFactFromAllFacts(AllFactsInBlock, CurrentLoanID); + if (!EF) { + NoExpireFactLive = true; + continue; + } + std::optional<LoanSet> LiveLoans = Helper.getLiveLoansAtPoint(EF); + if (!LiveLoans.has_value()) { + *result_listener << "No Live Loans At Expiry Location."; + continue; + } + if (LiveLoans->contains({CurrentLoanID})) + return true; + } + if (NoExpireFactLive) { + *result_listener << "No Expire Fact for loan of " << VarName.str(); + return false; + } + *result_listener << "No loans of " << VarName.str() << " are live"; + return false; +} + MATCHER_P(MustBeLiveAt, Annotation, "") { return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::Must), arg, result_listener); @@ -353,6 +435,10 @@ protected: return HasLoansToImpl(std::vector<std::string>(LoanVars), Annotation); } + auto HasLiveLoanAtExpiry(const char *Annotation) { + return HasLiveLoanAtExpiryImpl(Helper.get(), Annotation); + } + std::unique_ptr<LifetimeTestRunner> Runner; std::unique_ptr<LifetimeTestHelper> Helper; }; @@ -689,7 +775,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) { EXPECT_THAT(Origin("q"), HasLoansTo({"a"}, "p1")); } -// FIXME: Handle loans in ternary operator! TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) { SetupTest(R"( void target(bool cond) { @@ -698,7 +783,24 @@ TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) { POINT(p1); } )"); - EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1")); + EXPECT_THAT(Origin("v"), HasLoansTo({"a", "b"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, ExtraParenthesis) { + SetupTest(R"( + void target() { + MyObj a; + View x = ((View((((a)))))); + View y = ((View{(((x)))})); + View z = ((View(((y))))); + View p = ((View{((x))})); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1")); } // FIXME: Handle temporaries. @@ -1207,5 +1309,269 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) { EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1")); } +TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) { + SetupTest(R"( + void target() { + int *ptr; + { + int s = 1; + ptr = &s; + } + POINT(p1); + (void)*ptr; + } + )"); + EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); + EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) { + SetupTest(R"( + class S { + View a, b; + }; + + void target() { + S* ptr; + { + S s; + ptr = &s; + } + POINT(p1); + (void)ptr; + } + )"); + EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); + EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) { + SetupTest(R"( + MyObj* target() { + MyObj s; + MyObj* p = &s; + POINT(p1); + return p; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); +} + +TEST_F(LifetimeAnalysisTest, DirectReturn) { + SetupTest(R"( + MyObj* target() { + MyObj s; + POINT(P); + return &s; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj s1; + MyObj* p = nullptr; + if (c) { + p = &s1; + } + POINT(P); + return p; + } + )"); + EXPECT_THAT("s1", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, MultipleAssignments) { + SetupTest(R"( + MyObj* target() { + MyObj s; + MyObj* p1 = &s; + MyObj* p2 = &s; + POINT(P); + return p2; + } + )"); + // Test if atleast one loan to "s" is live; + EXPECT_THAT("s", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj s1; + static MyObj s2; + MyObj* p = nullptr; + if (c) { + p = &s1; + } else { + p = &s2; + } + POINT(P); + return p; + } + )"); + EXPECT_THAT("s1", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) { + SetupTest(R"( + MyObj* target() { + static MyObj safe_obj; + MyObj local_obj; + MyObj* p = &safe_obj; + + p = &local_obj; + POINT(P); + return p; + } + )"); + EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, PointerChainToLocal) { + SetupTest(R"( + MyObj* target() { + MyObj local_obj; + MyObj* p1 = &local_obj; + MyObj* p2 = p1; + POINT(P); + return p2; + } + )"); + EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) { + SetupTest(R"( + MyObj* target(bool c1, bool c2) { + static MyObj global_obj; + MyObj local_obj1; + MyObj local_obj2; + MyObj* p = nullptr; + if(c1){ + p = &local_obj1; + POINT(C1); + return p; + } + else if(c2){ + p = &local_obj2; + POINT(C2); + return p; + } + p = &global_obj; + POINT(C3); + return p; + } + )"); + + EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("C1")); + EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("C2")); + + EXPECT_THAT("local_obj1", Not(HasLiveLoanAtExpiry("C3"))); + EXPECT_THAT("local_obj2", Not(HasLiveLoanAtExpiry("C3"))); +} + +TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) { + SetupTest(R"( + MyObj* target(bool c1, bool c2) { + static MyObj global_obj; + MyObj local_obj1; + MyObj local_obj2; + MyObj* p = nullptr; + if(c1){ + p = &local_obj1; + } + else if(c2){ + p = &local_obj2; + } + else{ + p = &global_obj; + } + POINT(P); + return p; + } + )"); + EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("P")); + EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("P")); +} + +TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) { + SetupTest(R"( + MyObj* target() { + MyObj* p; + { + MyObj local_obj; + p = &local_obj; + POINT(p1); + } + POINT(p2); + return p; + } + )"); + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); + + EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p2")); +} + +TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj* p; + static MyObj global_obj; + { + MyObj local_obj; + p = &local_obj; + if(c){ + POINT(p1); + return p; + } + } + POINT(p2); + return &global_obj; + } + )"); + EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p1")); + + EXPECT_THAT(NoOrigins(), AreLiveAt("p2")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) { + SetupTest(R"( + int* target() { + int s = 10; + int* p = &s; + POINT(p1); + return p; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); +} + +TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) { + SetupTest(R"( + class S { + View a, b; + }; + + S* target() { + S *ptr; + S s; + ptr = &s; + POINT(p1); + return ptr; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal diff --git a/clang/unittests/Basic/DiagnosticTest.cpp b/clang/unittests/Basic/DiagnosticTest.cpp index de09086..5492146 100644 --- a/clang/unittests/Basic/DiagnosticTest.cpp +++ b/clang/unittests/Basic/DiagnosticTest.cpp @@ -294,7 +294,7 @@ TEST_F(SuppressionMappingTest, EmitCategoryIsExcluded) { locForFile("foo.cpp"))); } -TEST_F(SuppressionMappingTest, LongestMatchWins) { +TEST_F(SuppressionMappingTest, LastMatchWins) { llvm::StringLiteral SuppressionMappingFile = R"( [unused] src:*clang/* @@ -327,10 +327,8 @@ TEST_F(SuppressionMappingTest, LongShortMatch) { EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function, locForFile("test/t1.cpp"))); - - // FIXME: This is confusing. - EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function, - locForFile("lld/test/t2.cpp"))); + EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_unused_function, + locForFile("lld/test/t2.cpp"))); } TEST_F(SuppressionMappingTest, ShortLongMatch) { diff --git a/clang/unittests/Basic/SarifTest.cpp b/clang/unittests/Basic/SarifTest.cpp index 089b6cb..42e8508 100644 --- a/clang/unittests/Basic/SarifTest.cpp +++ b/clang/unittests/Basic/SarifTest.cpp @@ -290,7 +290,7 @@ TEST_F(SarifDocumentWriterTest, checkSerializingResultsWithCustomRuleConfig) { TEST_F(SarifDocumentWriterTest, checkSerializingArtifacts) { // GIVEN: const std::string ExpectedOutput = - R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":40,"location":{"index":0,"uri":"file:///main.cpp"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":[{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file:///main.cpp"},"region":{"endColumn":14,"startColumn":14,"startLine":3}}}],"message":{"text":"expected ';' after top level declarator"},"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})"; + R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":40,"location":{"index":0,"uri":"file:///main.cpp"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":[{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file:///main.cpp"},"region":{"endColumn":14,"startColumn":14,"startLine":3}}}],"message":{"text":"expected ';' after top level declarator"},"relatedLocations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file:///main.cpp"},"region":{"endColumn":14,"startColumn":14,"startLine":3}}}],"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})"; SarifDocumentWriter Writer{SourceMgr}; const SarifRule &Rule = @@ -312,12 +312,12 @@ TEST_F(SarifDocumentWriterTest, checkSerializingArtifacts) { registerSource("/main.cpp", SourceText, /* IsMainFile = */ true); CharSourceRange SourceCSR = getFakeCharSourceRange(MainFileID, {3, 14}, {3, 14}); - DiagLocs.push_back(SourceCSR); const SarifResult &Result = SarifResult::create(RuleIdx) - .setLocations(DiagLocs) + .addLocations(DiagLocs) + .addRelatedLocations(DiagLocs) .setDiagnosticMessage("expected ';' after top level declarator") .setDiagnosticLevel(SarifResultLevel::Error); Writer.appendResult(Result); @@ -377,7 +377,7 @@ TEST_F(SarifDocumentWriterTest, checkSerializingCodeflows) { unsigned RuleIdx = Writer.createRule(Rule); const SarifResult &Result = SarifResult::create(RuleIdx) - .setLocations({DiagLoc}) + .addLocations({DiagLoc}) .setDiagnosticMessage("Redefinition of 'foo'") .setThreadFlows(Threadflows) .setDiagnosticLevel(SarifResultLevel::Warning); diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt index 54c781a3..438a5c4 100644 --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -79,6 +79,7 @@ add_subdirectory(Basic) add_subdirectory(Lex) add_subdirectory(Parse) add_subdirectory(Driver) +add_subdirectory(DependencyScanning) if(CLANG_ENABLE_STATIC_ANALYZER) add_subdirectory(Analysis) add_subdirectory(StaticAnalyzer) diff --git a/clang/unittests/DependencyScanning/CMakeLists.txt b/clang/unittests/DependencyScanning/CMakeLists.txt new file mode 100644 index 0000000..4042582 --- /dev/null +++ b/clang/unittests/DependencyScanning/CMakeLists.txt @@ -0,0 +1,11 @@ +add_clang_unittest(ClangDependencyScanningTests + DependencyScanningFilesystemTest.cpp + DependencyScanningWorkerTest.cpp + CLANG_LIBS + clangDependencyScanning + clangFrontend # For TextDiagnosticPrinter. + LLVM_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + Option + Support + ) diff --git a/clang/unittests/Tooling/DependencyScanning/DependencyScanningFilesystemTest.cpp b/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp index 023c02dd..0e19541 100644 --- a/clang/unittests/Tooling/DependencyScanning/DependencyScanningFilesystemTest.cpp +++ b/clang/unittests/DependencyScanning/DependencyScanningFilesystemTest.cpp @@ -6,12 +6,45 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h" +#include "clang/DependencyScanning/DependencyScanningFilesystem.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/VirtualFileSystem.h" #include "gtest/gtest.h" -using namespace clang::tooling::dependencies; +using namespace clang::dependencies; + +TEST(DependencyScanningFilesystem, OpenFileAndGetBufferRepeatedly) { + auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); + InMemoryFS->setCurrentWorkingDirectory("/"); + InMemoryFS->addFile("/foo", 0, llvm::MemoryBuffer::getMemBuffer("content")); + + DependencyScanningFilesystemSharedCache SharedCache; + DependencyScanningWorkerFilesystem DepFS(SharedCache, InMemoryFS); + + auto FileOrErr1 = DepFS.openFileForRead("foo"); + auto FileOrErr2 = DepFS.openFileForRead("foo"); + ASSERT_EQ(FileOrErr1.getError(), std::error_code{}); + ASSERT_EQ(FileOrErr1.getError(), std::error_code{}); + std::unique_ptr<llvm::vfs::File> File1 = std::move(*FileOrErr1); + std::unique_ptr<llvm::vfs::File> File2 = std::move(*FileOrErr2); + ASSERT_NE(File1, nullptr); + ASSERT_NE(File2, nullptr); + auto BufOrErr11 = File1->getBuffer("buf11"); + auto BufOrErr12 = File1->getBuffer("buf12"); + auto BufOrErr21 = File1->getBuffer("buf21"); + ASSERT_EQ(BufOrErr11.getError(), std::error_code{}); + ASSERT_EQ(BufOrErr12.getError(), std::error_code{}); + ASSERT_EQ(BufOrErr21.getError(), std::error_code{}); + std::unique_ptr<llvm::MemoryBuffer> Buf11 = std::move(*BufOrErr11); + std::unique_ptr<llvm::MemoryBuffer> Buf12 = std::move(*BufOrErr12); + std::unique_ptr<llvm::MemoryBuffer> Buf21 = std::move(*BufOrErr21); + ASSERT_NE(Buf11, nullptr); + ASSERT_NE(Buf12, nullptr); + ASSERT_NE(Buf21, nullptr); + ASSERT_EQ(Buf11->getBuffer().data(), Buf12->getBuffer().data()); + ASSERT_EQ(Buf11->getBuffer().data(), Buf21->getBuffer().data()); + EXPECT_EQ(Buf11->getBuffer(), "content"); +} TEST(DependencyScanningWorkerFilesystem, CacheStatusFailures) { auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); diff --git a/clang/unittests/DependencyScanning/DependencyScanningWorkerTest.cpp b/clang/unittests/DependencyScanning/DependencyScanningWorkerTest.cpp new file mode 100644 index 0000000..e6a5684 --- /dev/null +++ b/clang/unittests/DependencyScanning/DependencyScanningWorkerTest.cpp @@ -0,0 +1,97 @@ +//===- DependencyScanningWorkerTest.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 +// +//===----------------------------------------------------------------------===// + +#include "clang/DependencyScanning/DependencyScanningWorker.h" +#include "clang/DependencyScanning/DependencyScanningUtils.h" +#include "llvm/Support/FormatVariadic.h" +#include "gtest/gtest.h" +#include <string> + +using namespace clang; +using namespace dependencies; + +TEST(DependencyScanner, ScanDepsWithDiagConsumer) { + StringRef CWD = "/root"; + + auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); + VFS->setCurrentWorkingDirectory(CWD); + auto Sept = llvm::sys::path::get_separator(); + std::string HeaderPath = + std::string(llvm::formatv("{0}root{0}header.h", Sept)); + std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); + std::string AsmPath = std::string(llvm::formatv("{0}root{0}test.s", Sept)); + + VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); + VFS->addFile(TestPath, 0, + llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n")); + VFS->addFile(AsmPath, 0, llvm::MemoryBuffer::getMemBuffer("")); + + DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Make); + DependencyScanningWorker Worker(Service, VFS); + + llvm::DenseSet<ModuleID> AlreadySeen; + FullDependencyConsumer DC(AlreadySeen); + CallbackActionController AC(nullptr); + + struct EnsureFinishedConsumer : public DiagnosticConsumer { + bool Finished = false; + void finish() override { Finished = true; } + }; + + { + // Check that a successful scan calls DiagConsumer.finish(). + std::vector<std::string> Args = {"clang", + "-target", + "x86_64-apple-macosx10.7", + "-c", + "test.cpp", + "-o" + "test.cpp.o"}; + + EnsureFinishedConsumer DiagConsumer; + bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); + + EXPECT_TRUE(Success); + EXPECT_EQ(DiagConsumer.getNumErrors(), 0u); + EXPECT_TRUE(DiagConsumer.Finished); + } + + { + // Check that an invalid command-line, which never enters the scanning + // action calls DiagConsumer.finish(). + std::vector<std::string> Args = {"clang", "-invalid-arg"}; + EnsureFinishedConsumer DiagConsumer; + bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); + + EXPECT_FALSE(Success); + EXPECT_GE(DiagConsumer.getNumErrors(), 1u); + EXPECT_TRUE(DiagConsumer.Finished); + } + + { + // Check that a valid command line that produces no scanning jobs calls + // DiagConsumer.finish(). + std::vector<std::string> Args = {"clang", + "-target", + "x86_64-apple-macosx10.7", + "-c", + "-x", + "assembler", + "test.s", + "-o" + "test.cpp.o"}; + + EnsureFinishedConsumer DiagConsumer; + bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); + + EXPECT_FALSE(Success); + EXPECT_EQ(DiagConsumer.getNumErrors(), 1u); + EXPECT_TRUE(DiagConsumer.Finished); + } +} diff --git a/clang/unittests/Driver/DXCModeTest.cpp b/clang/unittests/Driver/DXCModeTest.cpp index 6227423..130da62 100644 --- a/clang/unittests/Driver/DXCModeTest.cpp +++ b/clang/unittests/Driver/DXCModeTest.cpp @@ -15,6 +15,7 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/TargetOptions.h" #include "clang/Driver/Compilation.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Driver/Driver.h" #include "clang/Driver/ToolChain.h" #include "clang/Frontend/CompilerInstance.h" @@ -131,8 +132,8 @@ TEST(DxcModeTest, ValidatorVersionValidation) { TC.TranslateArgs(*DAL, "0", Action::OffloadKind::OFK_None)}; EXPECT_NE(TranslatedArgs, nullptr); if (TranslatedArgs) { - auto *A = TranslatedArgs->getLastArg( - clang::driver::options::OPT_dxil_validator_version); + auto *A = + TranslatedArgs->getLastArg(clang::options::OPT_dxil_validator_version); EXPECT_NE(A, nullptr); if (A) { EXPECT_STREQ(A->getValue(), "1.1"); diff --git a/clang/unittests/Driver/ToolChainTest.cpp b/clang/unittests/Driver/ToolChainTest.cpp index afa17ff..8f53379 100644 --- a/clang/unittests/Driver/ToolChainTest.cpp +++ b/clang/unittests/Driver/ToolChainTest.cpp @@ -17,6 +17,7 @@ #include "clang/Basic/TargetInfo.h" #include "clang/Basic/TargetOptions.h" #include "clang/Driver/Compilation.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Driver/Driver.h" #include "clang/Frontend/CompilerInstance.h" #include "llvm/ADT/ArrayRef.h" diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index d578fa7..fec1c48 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -1164,6 +1164,36 @@ TEST(ConfigParseTest, ParsesConfiguration) { FormatStyle::BLS_Block); CHECK_PARSE("Cpp11BracedListStyle: true", Cpp11BracedListStyle, FormatStyle::BLS_AlignFirstComment); + + constexpr FormatStyle::IntegerLiteralSeparatorStyle + ExpectedIntegerLiteralSeparatorStyle{/*Binary=*/2, + /*BinaryMinDigitInsert=*/5, + /*BinaryMaxDigitRemove=*/2, + /*Decimal=*/6, + /*DecimalMinDigitInsert=*/6, + /*DecimalMaxDigitRemove=*/3, + /*Hex=*/4, + /*HexMinDigitInsert=*/2, + /*HexMaxDigitRemove=*/1}; + CHECK_PARSE("IntegerLiteralSeparator:\n" + " Binary: 2\n" + " BinaryMinDigitsInsert: 5\n" + " BinaryMaxDigitsRemove: 2\n" + " Decimal: 6\n" + " DecimalMinDigitsInsert: 6\n" + " DecimalMaxDigitsRemove: 3\n" + " Hex: 4\n" + " HexMinDigitsInsert: 2\n" + " HexMaxDigitsRemove: 1", + IntegerLiteralSeparator, ExpectedIntegerLiteralSeparatorStyle); + + // Backward compatibility: + CHECK_PARSE_NESTED_VALUE("BinaryMinDigits: 6", IntegerLiteralSeparator, + BinaryMinDigitsInsert, 6); + CHECK_PARSE_NESTED_VALUE("DecimalMinDigits: 5", IntegerLiteralSeparator, + DecimalMinDigitsInsert, 5); + CHECK_PARSE_NESTED_VALUE("HexMinDigits: 5", IntegerLiteralSeparator, + HexMinDigitsInsert, 5); } TEST(ConfigParseTest, ParsesConfigurationWithLanguages) { diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index c9446fa..3ff7840 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -19851,6 +19851,14 @@ TEST_F(FormatTest, AlignConsecutiveDeclarations) { " Test &operator=(const Test &) = default;\n" "};", Alignment); + + // The comment to the right should still align right. + verifyFormat("void foo(int name, // name\n" + " float name, // name\n" + " int name) // name\n" + "{}", + Alignment); + unsigned OldColumnLimit = Alignment.ColumnLimit; // We need to set ColumnLimit to zero, in order to stress nested alignments, // otherwise the function parameters will be re-flowed onto a single line. @@ -20681,6 +20689,16 @@ TEST_F(FormatTest, AlignWithLineBreaks) { "}", Style); + verifyFormat("auto someLongName = 3;\n" + "auto x = someLongExpression //\n" + " | ranges::views::values;", + Style); + verifyFormat( + "veryverylongvariablename = somethingelse;\n" + "shortervariablename = anotherverylonglonglongvariablename + //\n" + " somevariablethatwastoolongtofitonthesamerow;", + Style); + // clang-format off verifyFormat("void foo() {\n" " const int capacityBefore = Entries.capacity();\n" @@ -20754,6 +20772,42 @@ TEST_F(FormatTest, AlignWithLineBreaks) { Style); // clang-format on + // The start of the closure is indented from the start of the line. It should + // not move with the equal sign. + Style.ContinuationIndentWidth = 6; + Style.IndentWidth = 8; + Style.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.BeforeLambdaBody = true; + Style.BraceWrapping.IndentBraces = true; + Style.ColumnLimit = 32; + verifyFormat("auto aaaaaaaaaaa = {};\n" + "b = []() constexpr\n" + " -> aaaaaaaaaaaaaaaaaaaaaaa\n" + " {\n" + " return {}; //\n" + " };", + Style); + verifyFormat("auto aaaaaaaaaaaaaaaaaaaaa = {};\n" + "b = []()\n" + " {\n" + " return; //\n" + " };", + Style); + Style.ColumnLimit = 33; + verifyFormat("auto aaaaaaaaaaa = {};\n" + "b = []() constexpr\n" + " -> aaaaaaaaaaaaaaaaaaaaaaa\n" + " {\n" + " return {}; //\n" + " };", + Style); + verifyFormat("auto aaaaaaaaaaaaaaaaaaaaa = {};\n" + "b = []()\n" + " {\n" + " return; //\n" + " };", + Style); + Style = getLLVMStyleWithColumns(20); Style.AlignConsecutiveAssignments.Enabled = true; Style.IndentWidth = 4; @@ -20818,6 +20872,13 @@ TEST_F(FormatTest, AlignWithLineBreaks) { Style); Style.AlignConsecutiveAssignments.Enabled = true; + verifyFormat("float i2 = 0;\n" + "auto v = false ? type{}\n" + " : type{\n" + " 1,\n" + " };", + Style); + Style.ColumnLimit = 15; verifyFormat("int i1 = 1;\n" "k = bar(\n" @@ -20878,6 +20939,12 @@ TEST_F(FormatTest, AlignWithInitializerPeriods) { "});", Style); + verifyFormat("auto aaaaaaaaaaaaaaaaaaaaa = {};\n" + "auto b = {.a = {\n" + " .a = 0,\n" + " }};", + Style); + Style.AlignConsecutiveAssignments.Enabled = false; Style.AlignConsecutiveDeclarations.Enabled = true; verifyFormat("void foo3(void) {\n" @@ -27320,6 +27387,7 @@ TEST_F(FormatTest, Cpp20ModulesSupport) { verifyFormat("export", Style); verifyFormat("import /* not keyword */ = val ? 2 : 1;"); + verifyFormat("_world->import<engine_module>();"); } TEST_F(FormatTest, CoroutineForCoawait) { diff --git a/clang/unittests/Format/FormatTestObjC.cpp b/clang/unittests/Format/FormatTestObjC.cpp index cf8143a..c685c55 100644 --- a/clang/unittests/Format/FormatTestObjC.cpp +++ b/clang/unittests/Format/FormatTestObjC.cpp @@ -876,6 +876,32 @@ TEST_F(FormatTestObjC, FormatObjCMethodExpr) { verifyFormat("aaaaaa = [aa aa:aa\n" " aa:aa];"); + Style.AlignConsecutiveAssignments.Enabled = true; + // When the method name and parameters are on their own lines, their positions + // only depend on the continuation indentation configuration, not where the + // square bracket is. Thus they should not move with the square bracket in the + // alignment step. + verifyFormat("aaaaaa = [aa aa:aa\n" + " aa:aa];\n" + "a = [a //\n" + " aaaaaaa:aa];"); + verifyFormat("aaaaaa = [aa aa:aa\n" + " aa:aa];\n" + "aaaaa = [a //\n" + " a:aa\n" + " aaaaaaa:aa];"); + // When the method name is on the same line as the square bracket, the + // positions of the parameters depend on where the square bracket is. Thus + // they should move with the square bracket in the alignment step. + verifyFormat("aaaaa = [a aa:aa\n" + " aa:aa];\n" + "aaaaaa = [aa aa:aa\n" + " aa:aa];\n" + "aaaaa = [a aa:aa\n" + " aaaaaaaaaa:aa];"); + + Style.AlignConsecutiveAssignments.Enabled = false; + // Message receiver taking multiple lines. // Non-corner case. verifyFormat("[[object block:^{\n" diff --git a/clang/unittests/Format/FormatTestVerilog.cpp b/clang/unittests/Format/FormatTestVerilog.cpp index 63e2cadf..f407fc3 100644 --- a/clang/unittests/Format/FormatTestVerilog.cpp +++ b/clang/unittests/Format/FormatTestVerilog.cpp @@ -423,6 +423,11 @@ TEST_F(FormatTestVerilog, Declaration) { verifyFormat("wire (strong1, pull0) mynet, mynet1 = enable;"); verifyFormat("wire (strong1, pull0) mynet, //\n" " mynet1 = enable;"); + + // The type or variable can be a C++ keyword. + verifyFormat("private mynet;"); + verifyFormat("switch mynet;"); + verifyFormat("wire try;"); } TEST_F(FormatTestVerilog, Delay) { @@ -676,6 +681,16 @@ TEST_F(FormatTestVerilog, Hierarchy) { " endprogram\n" "endmodule"); // Test that an extern declaration doesn't change the indentation. + verifyFormat("import \"DPI-C\" context MyCFunc = function integer MapID\n" + " (int portID);\n" + "x = x;"); + verifyFormat("export \"DPI-C\" function exported_sv_func;\n" + "x = x;"); + verifyFormat("import \"DPI-C\" function void f1\n" + " (input int i1,\n" + " pair i2,\n" + " output logic [63 : 0] o3);\n" + "x = x;"); verifyFormat("extern module x;\n" "x = x;"); // Test complex headers diff --git a/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp b/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp index 53b6dd8..21cdab2 100644 --- a/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp +++ b/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp @@ -137,34 +137,34 @@ TEST_F(IntegerLiteralSeparatorTest, UnderscoreAsSeparator) { verifyFormat("o = 0o400000000000000003n;", Style); } -TEST_F(IntegerLiteralSeparatorTest, MinDigits) { +TEST_F(IntegerLiteralSeparatorTest, MinDigitsInsert) { FormatStyle Style = getLLVMStyle(); Style.IntegerLiteralSeparator.Binary = 3; Style.IntegerLiteralSeparator.Decimal = 3; Style.IntegerLiteralSeparator.Hex = 2; - Style.IntegerLiteralSeparator.BinaryMinDigits = 7; + Style.IntegerLiteralSeparator.BinaryMinDigitsInsert = 7; verifyFormat("b1 = 0b101101;\n" "b2 = 0b1'101'101;", "b1 = 0b101'101;\n" "b2 = 0b1101101;", Style); - Style.IntegerLiteralSeparator.DecimalMinDigits = 5; + Style.IntegerLiteralSeparator.DecimalMinDigitsInsert = 5; verifyFormat("d1 = 2023;\n" "d2 = 10'000;", "d1 = 2'023;\n" "d2 = 100'00;", Style); - Style.IntegerLiteralSeparator.DecimalMinDigits = 3; + Style.IntegerLiteralSeparator.DecimalMinDigitsInsert = 3; verifyFormat("d1 = 123;\n" "d2 = 1'234;", "d1 = 12'3;\n" "d2 = 12'34;", Style); - Style.IntegerLiteralSeparator.HexMinDigits = 6; + Style.IntegerLiteralSeparator.HexMinDigitsInsert = 6; verifyFormat("h1 = 0xABCDE;\n" "h2 = 0xAB'CD'EF;", "h1 = 0xA'BC'DE;\n" @@ -243,6 +243,23 @@ TEST_F(IntegerLiteralSeparatorTest, FloatingPoint) { Style); } +TEST_F(IntegerLiteralSeparatorTest, MaxDigitsRemove) { + auto Style = getLLVMStyle(); + Style.IntegerLiteralSeparator.Decimal = 3; + Style.IntegerLiteralSeparator.DecimalMaxDigitsRemove = 4; + Style.IntegerLiteralSeparator.DecimalMinDigitsInsert = 7; + + verifyFormat("d1 = 123456;\n" + "d2 = 1234'56;", + Style); + + verifyFormat("d0 = 2023;\n" + "d3 = 5'000'000;", + "d0 = 20'2'3;\n" + "d3 = 5000000;", + Style); +} + } // namespace } // namespace test } // namespace format diff --git a/clang/unittests/Format/QualifierFixerTest.cpp b/clang/unittests/Format/QualifierFixerTest.cpp index 58e64ff..86c69a5 100644 --- a/clang/unittests/Format/QualifierFixerTest.cpp +++ b/clang/unittests/Format/QualifierFixerTest.cpp @@ -215,6 +215,8 @@ TEST_F(QualifierFixerTest, RightQualifier) { Style); verifyFormat("void foo() const override;", Style); verifyFormat("void foo() const override LLVM_READONLY;", Style); + verifyFormat("MOCK_METHOD(ReturnType, myMethod, (int), (const override));", + Style); verifyFormat("void foo() const final;", Style); verifyFormat("void foo() const final LLVM_READONLY;", Style); verifyFormat("void foo() const LLVM_READONLY;", Style); diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp index 815c79e..a3c0a32 100644 --- a/clang/unittests/Format/TokenAnnotatorTest.cpp +++ b/clang/unittests/Format/TokenAnnotatorTest.cpp @@ -1392,6 +1392,15 @@ TEST_F(TokenAnnotatorTest, UnderstandsRequiresClausesAndConcepts) { EXPECT_TOKEN(Tokens[19], tok::l_brace, TT_RequiresExpressionLBrace); Tokens = + annotate("template <typename... Ts>\n" + " requires requires {\n" + " requires std::same_as<int, SomeTemplate<void(Ts &&...)>>;\n" + " }\n" + "void Foo();"); + ASSERT_EQ(Tokens.size(), 34u) << Tokens; + EXPECT_TOKEN(Tokens[21], tok::ampamp, TT_PointerOrReference); + + Tokens = annotate("template <class A, class B> concept C =" "std::same_as<std::iter_value_t<A>, std::iter_value_t<B>>;"); ASSERT_EQ(Tokens.size(), 31u) << Tokens; @@ -2313,6 +2322,12 @@ TEST_F(TokenAnnotatorTest, UnderstandsLambdas) { EXPECT_TOKEN(Tokens[3], tok::l_square, TT_LambdaLSquare); EXPECT_TOKEN(Tokens[5], tok::l_paren, TT_LambdaDefinitionLParen); EXPECT_TOKEN(Tokens[10], tok::l_square, TT_ArraySubscriptLSquare); + + Tokens = annotate("foo = bar * [] { return 2; }();"); + ASSERT_EQ(Tokens.size(), 15u) << Tokens; + EXPECT_TOKEN(Tokens[3], tok::star, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[4], tok::l_square, TT_LambdaLSquare); + EXPECT_TOKEN(Tokens[6], tok::l_brace, TT_LambdaLBrace); } TEST_F(TokenAnnotatorTest, UnderstandsFunctionAnnotations) { @@ -2909,6 +2924,13 @@ TEST_F(TokenAnnotatorTest, UnderstandsVerilogOperators) { EXPECT_EQ(Tokens[0]->TokenText, R"(\busa+index\ +)"); EXPECT_TOKEN(Tokens[1], tok::semi, TT_Unknown); + + // A C++ keyword should be treated as an identifier. + Tokens = Annotate("volatile delete;"); + ASSERT_EQ(Tokens.size(), 4u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::identifier, TT_Unknown); + EXPECT_TOKEN(Tokens[1], tok::identifier, TT_StartOfName); + // An escaped newline should not be treated as an escaped identifier. Tokens = Annotate("\\\n"); ASSERT_EQ(Tokens.size(), 1u) << Tokens; @@ -3361,6 +3383,11 @@ TEST_F(TokenAnnotatorTest, UnderstandDesignatedInitializers) { ASSERT_EQ(Tokens.size(), 14u) << Tokens; EXPECT_TOKEN(Tokens[6], tok::l_square, TT_DesignatedInitializerLSquare); EXPECT_BRACE_KIND(Tokens[9], BK_BracedInit); + + Tokens = annotate("Foo foo[] = {[0] = 1, [1] = 2};"); + ASSERT_EQ(Tokens.size(), 20u) << Tokens; + EXPECT_TOKEN(Tokens[6], tok::l_square, TT_DesignatedInitializerLSquare); + EXPECT_TOKEN(Tokens[12], tok::l_square, TT_DesignatedInitializerLSquare); } TEST_F(TokenAnnotatorTest, UnderstandsJavaScript) { diff --git a/clang/unittests/Frontend/ASTUnitTest.cpp b/clang/unittests/Frontend/ASTUnitTest.cpp index dfdbe90..bf9e4e1 100644 --- a/clang/unittests/Frontend/ASTUnitTest.cpp +++ b/clang/unittests/Frontend/ASTUnitTest.cpp @@ -9,6 +9,8 @@ #include <fstream> #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateASTUnitFromArgs.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" @@ -173,7 +175,7 @@ TEST_F(ASTUnitTest, LoadFromCommandLineEarlyError) { auto PCHContainerOps = std::make_shared<PCHContainerOperations>(); std::unique_ptr<clang::ASTUnit> ErrUnit; - std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCommandLine( + std::unique_ptr<ASTUnit> AST = CreateASTUnitFromCommandLine( &Args[0], &Args[4], PCHContainerOps, DiagOpts, Diags, "", false, "", false, CaptureDiagsKind::All, {}, true, 0, TU_Complete, false, false, false, SkipFunctionBodiesScope::None, false, true, false, false, @@ -201,7 +203,7 @@ TEST_F(ASTUnitTest, LoadFromCommandLineWorkingDirectory) { auto PCHContainerOps = std::make_shared<PCHContainerOperations>(); std::unique_ptr<clang::ASTUnit> ErrUnit; - std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCommandLine( + std::unique_ptr<ASTUnit> AST = CreateASTUnitFromCommandLine( &Args[0], &Args[4], PCHContainerOps, DiagOpts, Diags, "", false, "", false, CaptureDiagsKind::All, {}, true, 0, TU_Complete, false, false, false, SkipFunctionBodiesScope::None, false, true, false, false, diff --git a/clang/unittests/Frontend/CompilerInstanceTest.cpp b/clang/unittests/Frontend/CompilerInstanceTest.cpp index cd3fefa..39d35b4 100644 --- a/clang/unittests/Frontend/CompilerInstanceTest.cpp +++ b/clang/unittests/Frontend/CompilerInstanceTest.cpp @@ -8,6 +8,7 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" diff --git a/clang/unittests/Frontend/UtilsTest.cpp b/clang/unittests/Frontend/UtilsTest.cpp index fc411e4..a82733d 100644 --- a/clang/unittests/Frontend/UtilsTest.cpp +++ b/clang/unittests/Frontend/UtilsTest.cpp @@ -9,6 +9,7 @@ #include "clang/Frontend/Utils.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/TargetOptions.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Lex/PreprocessorOptions.h" diff --git a/clang/unittests/Lex/PPCallbacksTest.cpp b/clang/unittests/Lex/PPCallbacksTest.cpp index 990689c..9533fbc 100644 --- a/clang/unittests/Lex/PPCallbacksTest.cpp +++ b/clang/unittests/Lex/PPCallbacksTest.cpp @@ -437,6 +437,7 @@ TEST_F(PPCallbacksTest, FileNotFoundSkipped) { PreprocessorOptions PPOpts; HeaderSearch HeaderInfo(HSOpts, SourceMgr, Diags, LangOpts, Target.get()); + unsigned int NumCalls = 0; DiagnosticConsumer *DiagConsumer = new DiagnosticConsumer; DiagnosticsEngine FileNotFoundDiags(DiagID, DiagOpts, DiagConsumer); Preprocessor PP(PPOpts, FileNotFoundDiags, LangOpts, SourceMgr, HeaderInfo, @@ -445,21 +446,68 @@ TEST_F(PPCallbacksTest, FileNotFoundSkipped) { class FileNotFoundCallbacks : public PPCallbacks { public: - unsigned int NumCalls = 0; + unsigned int &NumCalls; + + FileNotFoundCallbacks(unsigned int &NumCalls) : NumCalls(NumCalls) {} + bool FileNotFound(StringRef FileName) override { NumCalls++; return FileName == "skipped.h"; } }; - auto *Callbacks = new FileNotFoundCallbacks; - PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(Callbacks)); + PP.addPPCallbacks(std::make_unique<FileNotFoundCallbacks>(NumCalls)); + + // Lex source text. + PP.EnterMainSourceFile(); + PP.LexTokensUntilEOF(); + + ASSERT_EQ(1u, NumCalls); + ASSERT_EQ(0u, DiagConsumer->getNumErrors()); +} + +TEST_F(PPCallbacksTest, EmbedFileNotFoundChained) { + const char *SourceText = "#embed \"notfound.h\"\n"; + + std::unique_ptr<llvm::MemoryBuffer> SourceBuf = + llvm::MemoryBuffer::getMemBuffer(SourceText); + SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(SourceBuf))); + + unsigned int NumCalls = 0; + HeaderSearchOptions HSOpts; + TrivialModuleLoader ModLoader; + PreprocessorOptions PPOpts; + HeaderSearch HeaderInfo(HSOpts, SourceMgr, Diags, LangOpts, Target.get()); + + DiagnosticConsumer *DiagConsumer = new DiagnosticConsumer; + DiagnosticsEngine EmbedFileNotFoundDiags(DiagID, DiagOpts, DiagConsumer); + Preprocessor PP(PPOpts, EmbedFileNotFoundDiags, LangOpts, SourceMgr, + HeaderInfo, ModLoader, /*IILookup=*/nullptr, + /*OwnsHeaderSearch=*/false); + PP.Initialize(*Target); + + class EmbedFileNotFoundCallbacks : public PPCallbacks { + public: + unsigned int &NumCalls; + + EmbedFileNotFoundCallbacks(unsigned int &NumCalls) : NumCalls(NumCalls) {} + + bool EmbedFileNotFound(StringRef FileName) override { + NumCalls++; + return true; + } + }; + + // Add two instances of `EmbedFileNotFoundCallbacks` to ensure the + // preprocessor is using an instance of `PPChainedCallbaks`. + PP.addPPCallbacks(std::make_unique<EmbedFileNotFoundCallbacks>(NumCalls)); + PP.addPPCallbacks(std::make_unique<EmbedFileNotFoundCallbacks>(NumCalls)); // Lex source text. PP.EnterMainSourceFile(); PP.LexTokensUntilEOF(); - ASSERT_EQ(1u, Callbacks->NumCalls); + ASSERT_EQ(2u, NumCalls); ASSERT_EQ(0u, DiagConsumer->getNumErrors()); } diff --git a/clang/unittests/Sema/CMakeLists.txt b/clang/unittests/Sema/CMakeLists.txt index b61ed8c..188f613 100644 --- a/clang/unittests/Sema/CMakeLists.txt +++ b/clang/unittests/Sema/CMakeLists.txt @@ -13,6 +13,7 @@ add_distinct_clang_unittest(SemaTests clangAST clangASTMatchers clangBasic + clangDriver clangFrontend clangParse clangSema diff --git a/clang/unittests/Sema/SemaNoloadLookupTest.cpp b/clang/unittests/Sema/SemaNoloadLookupTest.cpp index e565372..3944269 100644 --- a/clang/unittests/Sema/SemaNoloadLookupTest.cpp +++ b/clang/unittests/Sema/SemaNoloadLookupTest.cpp @@ -10,6 +10,7 @@ #include "clang/AST/DeclarationName.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/ForceCheckFileInputTest.cpp b/clang/unittests/Serialization/ForceCheckFileInputTest.cpp index edf33ae..b76dcfe 100644 --- a/clang/unittests/Serialization/ForceCheckFileInputTest.cpp +++ b/clang/unittests/Serialization/ForceCheckFileInputTest.cpp @@ -9,6 +9,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp index d7b5549..f55925a 100644 --- a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp +++ b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/ModuleCacheTest.cpp b/clang/unittests/Serialization/ModuleCacheTest.cpp index e9b8da3..df26e54 100644 --- a/clang/unittests/Serialization/ModuleCacheTest.cpp +++ b/clang/unittests/Serialization/ModuleCacheTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/NoCommentsTest.cpp b/clang/unittests/Serialization/NoCommentsTest.cpp index 01bb699..444a082 100644 --- a/clang/unittests/Serialization/NoCommentsTest.cpp +++ b/clang/unittests/Serialization/NoCommentsTest.cpp @@ -9,6 +9,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/PreambleInNamedModulesTest.cpp b/clang/unittests/Serialization/PreambleInNamedModulesTest.cpp index 55ee728..b826f20 100644 --- a/clang/unittests/Serialization/PreambleInNamedModulesTest.cpp +++ b/clang/unittests/Serialization/PreambleInNamedModulesTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Serialization/VarDeclConstantInitTest.cpp b/clang/unittests/Serialization/VarDeclConstantInitTest.cpp index 743f851..2be01de 100644 --- a/clang/unittests/Serialization/VarDeclConstantInitTest.cpp +++ b/clang/unittests/Serialization/VarDeclConstantInitTest.cpp @@ -9,6 +9,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/FileManager.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt index 106c6b9..8c8b222 100644 --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/clang/unittests/Tooling/CMakeLists.txt @@ -13,8 +13,7 @@ add_clang_unittest(ToolingTests LookupTest.cpp QualTypeNamesTest.cpp RangeSelectorTest.cpp - DependencyScanning/DependencyScannerTest.cpp - DependencyScanning/DependencyScanningFilesystemTest.cpp + DependencyScannerTest.cpp RecursiveASTVisitorTests/Attr.cpp RecursiveASTVisitorTests/BitfieldInitializer.cpp RecursiveASTVisitorTests/CallbacksLeaf.cpp diff --git a/clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp b/clang/unittests/Tooling/DependencyScannerTest.cpp index 4523af3..da47252 100644 --- a/clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp +++ b/clang/unittests/Tooling/DependencyScannerTest.cpp @@ -9,13 +9,13 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" +#include "clang/DependencyScanning/DependencyScanningWorker.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CompilationDatabase.h" -#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" -#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "clang/Tooling/DependencyScanningTool.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/MC/TargetRegistry.h" @@ -304,84 +304,3 @@ TEST(DependencyScanner, ScanDepsWithModuleLookup) { EXPECT_TRUE(!llvm::is_contained(InterceptFS->StatPaths, OtherPath)); EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"}); } - -TEST(DependencyScanner, ScanDepsWithDiagConsumer) { - StringRef CWD = "/root"; - - auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); - VFS->setCurrentWorkingDirectory(CWD); - auto Sept = llvm::sys::path::get_separator(); - std::string HeaderPath = - std::string(llvm::formatv("{0}root{0}header.h", Sept)); - std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); - std::string AsmPath = std::string(llvm::formatv("{0}root{0}test.s", Sept)); - - VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); - VFS->addFile(TestPath, 0, - llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n")); - VFS->addFile(AsmPath, 0, llvm::MemoryBuffer::getMemBuffer("")); - - DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, - ScanningOutputFormat::Make); - DependencyScanningWorker Worker(Service, VFS); - - llvm::DenseSet<ModuleID> AlreadySeen; - FullDependencyConsumer DC(AlreadySeen); - CallbackActionController AC(nullptr); - - struct EnsureFinishedConsumer : public DiagnosticConsumer { - bool Finished = false; - void finish() override { Finished = true; } - }; - - { - // Check that a successful scan calls DiagConsumer.finish(). - std::vector<std::string> Args = {"clang", - "-target", - "x86_64-apple-macosx10.7", - "-c", - "test.cpp", - "-o" - "test.cpp.o"}; - - EnsureFinishedConsumer DiagConsumer; - bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); - - EXPECT_TRUE(Success); - EXPECT_EQ(DiagConsumer.getNumErrors(), 0u); - EXPECT_TRUE(DiagConsumer.Finished); - } - - { - // Check that an invalid command-line, which never enters the scanning - // action calls DiagConsumer.finish(). - std::vector<std::string> Args = {"clang", "-invalid-arg"}; - EnsureFinishedConsumer DiagConsumer; - bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); - - EXPECT_FALSE(Success); - EXPECT_GE(DiagConsumer.getNumErrors(), 1u); - EXPECT_TRUE(DiagConsumer.Finished); - } - - { - // Check that a valid command line that produces no scanning jobs calls - // DiagConsumer.finish(). - std::vector<std::string> Args = {"clang", - "-target", - "x86_64-apple-macosx10.7", - "-c", - "-x", - "assembler", - "test.s", - "-o" - "test.cpp.o"}; - - EnsureFinishedConsumer DiagConsumer; - bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer); - - EXPECT_FALSE(Success); - EXPECT_EQ(DiagConsumer.getNumErrors(), 1u); - EXPECT_TRUE(DiagConsumer.Finished); - } -} diff --git a/clang/unittests/Tooling/RangeSelectorTest.cpp b/clang/unittests/Tooling/RangeSelectorTest.cpp index a1fcbb0..9e83fa1 100644 --- a/clang/unittests/Tooling/RangeSelectorTest.cpp +++ b/clang/unittests/Tooling/RangeSelectorTest.cpp @@ -327,6 +327,45 @@ TEST(RangeSelectorTest, EncloseOpGeneralParsed) { EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("3, 7")); } +TEST(RangeSelectorTest, MergeOp) { + StringRef Code = R"cc( + int f(int x, int y, int z) { return 3; } + int g() { return f(/* comment */ 3, 7 /* comment */, 9); } + )cc"; + auto Matcher = callExpr(hasArgument(0, expr().bind("a0")), + hasArgument(1, expr().bind("a1")), + hasArgument(2, expr().bind("a2"))); + RangeSelector R = merge(node("a0"), node("a1")); + TestMatch Match = matchCode(Code, Matcher); + EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7")); + // Test the merge of two non-contiguous and out-of-order token-ranges. + R = merge(node("a2"), node("a0")); + EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7 /* comment */, 9")); + // Test the merge of a token-range (expr node) with a char-range (before). + R = merge(node("a1"), before(node("a0"))); + EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7")); + // Test the merge of two char-ranges. + R = merge(before(node("a0")), before(node("a1"))); + EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, ")); +} + +TEST(RangeSelectorTest, MergeOpParsed) { + StringRef Code = R"cc( + int f(int x, int y, int z) { return 3; } + int g() { return f(/* comment */ 3, 7 /* comment */, 9); } + )cc"; + auto Matcher = callExpr(hasArgument(0, expr().bind("a0")), + hasArgument(1, expr().bind("a1")), + hasArgument(2, expr().bind("a2"))); + auto R = parseRangeSelector(R"rs(merge(node("a0"), node("a1")))rs"); + ASSERT_THAT_EXPECTED(R, llvm::Succeeded()); + TestMatch Match = matchCode(Code, Matcher); + EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("3, 7")); + R = parseRangeSelector(R"rs(merge(node("a2"), node("a1")))rs"); + ASSERT_THAT_EXPECTED(R, llvm::Succeeded()); + EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("7 /* comment */, 9")); +} + TEST(RangeSelectorTest, NodeOpStatement) { StringRef Code = "int f() { return 3; }"; TestMatch Match = matchCode(Code, returnStmt().bind("id")); @@ -339,6 +378,13 @@ TEST(RangeSelectorTest, NodeOpExpression) { EXPECT_THAT_EXPECTED(select(node("id"), Match), HasValue("3")); } +TEST(RangeSelectorTest, NodeOpTypeLoc) { + StringRef Code = "namespace ns {struct Foo{};} ns::Foo a;"; + TestMatch Match = + matchCode(Code, varDecl(hasTypeLoc(typeLoc().bind("typeloc")))); + EXPECT_THAT_EXPECTED(select(node("typeloc"), Match), HasValue("ns::Foo")); +} + TEST(RangeSelectorTest, StatementOp) { StringRef Code = "int f() { return 3; }"; TestMatch Match = matchCode(Code, expr().bind("id")); diff --git a/clang/unittests/Tooling/SourceCodeTest.cpp b/clang/unittests/Tooling/SourceCodeTest.cpp index 549b777..a998954 100644 --- a/clang/unittests/Tooling/SourceCodeTest.cpp +++ b/clang/unittests/Tooling/SourceCodeTest.cpp @@ -510,10 +510,14 @@ TEST(SourceCodeTest, EditInvolvingExpansionIgnoringExpansionShouldFail) { #define M1(x) x(1) #define M2(x, y) x ## y #define M3(x) foobar(x) +#define M4(x, y) x y +#define M5(x) x int foobar(int); int a = M1(foobar); int b = M2(foo, bar(2)); int c = M3(3); +int d = M4(foobar, (4)); +int e = M5(foobar) (5); )cpp"); CallsVisitor Visitor; diff --git a/clang/unittests/Tooling/Syntax/TokensTest.cpp b/clang/unittests/Tooling/Syntax/TokensTest.cpp index 47184cb..468ca5d 100644 --- a/clang/unittests/Tooling/Syntax/TokensTest.cpp +++ b/clang/unittests/Tooling/Syntax/TokensTest.cpp @@ -20,6 +20,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.def" #include "clang/Basic/TokenKinds.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/Utils.h" diff --git a/clang/unittests/Tooling/Syntax/TreeTestBase.cpp b/clang/unittests/Tooling/Syntax/TreeTestBase.cpp index b2be64f..dad7585 100644 --- a/clang/unittests/Tooling/Syntax/TreeTestBase.cpp +++ b/clang/unittests/Tooling/Syntax/TreeTestBase.cpp @@ -13,6 +13,7 @@ #include "TreeTestBase.h" #include "clang/AST/ASTConsumer.h" #include "clang/Basic/LLVM.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendAction.h" |
