diff --git a/clang/include/clang/Testing/CommandLineArgs.h b/clang/include/clang/Testing/CommandLineArgs.h index fe2103a3dce214..e668781ee2ce17 100644 --- a/clang/include/clang/Testing/CommandLineArgs.h +++ b/clang/include/clang/Testing/CommandLineArgs.h @@ -29,6 +29,7 @@ enum TestLanguage { Lang_CXX17, Lang_CXX20, Lang_OpenCL, + Lang_OBJC, Lang_OBJCXX }; diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index 05f3470a179d23..b15036a11ad92f 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -1242,10 +1242,10 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return true; } -/// Determine structural equivalence of two fields. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, - FieldDecl *Field1, FieldDecl *Field2) { - const auto *Owner2 = cast(Field2->getDeclContext()); + FieldDecl *Field1, FieldDecl *Field2, + QualType Owner2Type) { + const auto *Owner2 = cast(Field2->getDeclContext()); // For anonymous structs/unions, match up the anonymous struct/union type // declarations directly, so that we don't go off searching for anonymous @@ -1265,7 +1265,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, Context.Diag2( Owner2->getLocation(), Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)) - << Context.ToCtx.getTypeDeclType(Owner2); + << Owner2Type; Context.Diag2(Field2->getLocation(), diag::note_odr_field_name) << Field2->getDeclName(); Context.Diag1(Field1->getLocation(), diag::note_odr_field_name) @@ -1280,7 +1280,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, Context.Diag2( Owner2->getLocation(), Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)) - << Context.ToCtx.getTypeDeclType(Owner2); + << Owner2Type; Context.Diag2(Field2->getLocation(), diag::note_odr_field) << Field2->getDeclName() << Field2->getType(); Context.Diag1(Field1->getLocation(), diag::note_odr_field) @@ -1296,6 +1296,14 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return true; } +/// Determine structural equivalence of two fields. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + FieldDecl *Field1, FieldDecl *Field2) { + const auto *Owner2 = cast(Field2->getDeclContext()); + return IsStructurallyEquivalent(Context, Field1, Field2, + Context.ToCtx.getTypeDeclType(Owner2)); +} + /// Determine structural equivalence of two methods. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, CXXMethodDecl *Method1, @@ -1610,6 +1618,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, } // Check the fields for consistency. + QualType D2Type = Context.ToCtx.getTypeDeclType(D2); RecordDecl::field_iterator Field2 = D2->field_begin(), Field2End = D2->field_end(); for (RecordDecl::field_iterator Field1 = D1->field_begin(), @@ -1628,7 +1637,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return false; } - if (!IsStructurallyEquivalent(Context, *Field1, *Field2)) + if (!IsStructurallyEquivalent(Context, *Field1, *Field2, D2Type)) return false; } @@ -1934,6 +1943,126 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return true; } +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + ObjCIvarDecl *D1, ObjCIvarDecl *D2, + QualType Owner2Type) { + if (D1->getAccessControl() != D2->getAccessControl()) + return false; + + return IsStructurallyEquivalent(Context, cast(D1), + cast(D2), Owner2Type); +} + +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + ObjCIvarDecl *D1, ObjCIvarDecl *D2) { + QualType Owner2Type = + Context.ToCtx.getObjCInterfaceType(D2->getContainingInterface()); + return IsStructurallyEquivalent(Context, D1, D2, Owner2Type); +} + +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + ObjCMethodDecl *Method1, + ObjCMethodDecl *Method2) { + bool PropertiesEqual = + Method1->isInstanceMethod() == Method2->isInstanceMethod() && + Method1->isVariadic() == Method2->isVariadic() && + Method1->isDirectMethod() == Method2->isDirectMethod(); + if (!PropertiesEqual) + return false; + + // Compare selector slot names. + Selector Selector1 = Method1->getSelector(), + Selector2 = Method2->getSelector(); + unsigned NumArgs = Selector1.getNumArgs(); + if (NumArgs != Selector2.getNumArgs()) + return false; + // Compare all selector slots. For selectors with arguments it means all arg + // slots. And if there are no arguments, compare the first-and-only slot. + unsigned SlotsToCheck = NumArgs > 0 ? NumArgs : 1; + for (unsigned I = 0; I < SlotsToCheck; ++I) { + if (!IsStructurallyEquivalent(Selector1.getIdentifierInfoForSlot(I), + Selector2.getIdentifierInfoForSlot(I))) + return false; + } + + // Compare types. + if (!IsStructurallyEquivalent(Context, Method1->getReturnType(), + Method2->getReturnType())) + return false; + assert( + Method1->param_size() == Method2->param_size() && + "Same number of arguments should be already enforced in Selector checks"); + for (ObjCMethodDecl::param_type_iterator + ParamT1 = Method1->param_type_begin(), + ParamT1End = Method1->param_type_end(), + ParamT2 = Method2->param_type_begin(), + ParamT2End = Method2->param_type_end(); + (ParamT1 != ParamT1End) && (ParamT2 != ParamT2End); + ++ParamT1, ++ParamT2) { + if (!IsStructurallyEquivalent(Context, *ParamT1, *ParamT2)) + return false; + } + + return true; +} + +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + ObjCCategoryDecl *D1, + ObjCCategoryDecl *D2) { + if (!IsStructurallyEquivalent(D1->getIdentifier(), D2->getIdentifier())) + return false; + + if (!IsStructurallyEquivalent(D1->getClassInterface()->getIdentifier(), + D2->getClassInterface()->getIdentifier())) + return false; + + // Compare protocols. + ObjCCategoryDecl::protocol_iterator Protocol2 = D2->protocol_begin(), + Protocol2End = D2->protocol_end(); + for (ObjCCategoryDecl::protocol_iterator Protocol1 = D1->protocol_begin(), + Protocol1End = D1->protocol_end(); + Protocol1 != Protocol1End; ++Protocol1, ++Protocol2) { + if (Protocol2 == Protocol2End) + return false; + if (!IsStructurallyEquivalent((*Protocol1)->getIdentifier(), + (*Protocol2)->getIdentifier())) + return false; + } + if (Protocol2 != Protocol2End) + return false; + + // Compare ivars. + QualType D2Type = Context.ToCtx.getObjCInterfaceType(D2->getClassInterface()); + ObjCCategoryDecl::ivar_iterator Ivar2 = D2->ivar_begin(), + Ivar2End = D2->ivar_end(); + for (ObjCCategoryDecl::ivar_iterator Ivar1 = D1->ivar_begin(), + Ivar1End = D1->ivar_end(); + Ivar1 != Ivar1End; ++Ivar1, ++Ivar2) { + if (Ivar2 == Ivar2End) + return false; + if (!IsStructurallyEquivalent(Context, *Ivar1, *Ivar2, D2Type)) + return false; + } + if (Ivar2 != Ivar2End) + return false; + + // Compare methods. + ObjCCategoryDecl::method_iterator Method2 = D2->meth_begin(), + Method2End = D2->meth_end(); + for (ObjCCategoryDecl::method_iterator Method1 = D1->meth_begin(), + Method1End = D1->meth_end(); + Method1 != Method1End; ++Method1, ++Method2) { + if (Method2 == Method2End) + return false; + if (!IsStructurallyEquivalent(Context, *Method1, *Method2)) + return false; + } + if (Method2 != Method2End) + return false; + + return true; +} + /// Determine structural equivalence of two declarations. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, Decl *D1, Decl *D2) { diff --git a/clang/lib/Testing/CommandLineArgs.cpp b/clang/lib/Testing/CommandLineArgs.cpp index d71b9361f95890..c04702f689028b 100644 --- a/clang/lib/Testing/CommandLineArgs.cpp +++ b/clang/lib/Testing/CommandLineArgs.cpp @@ -36,6 +36,9 @@ std::vector getCommandLineArgsForTesting(TestLanguage Lang) { case Lang_CXX20: Args = {"-std=c++20", "-frtti"}; break; + case Lang_OBJC: + Args = {"-x", "objective-c", "-frtti", "-fobjc-nonfragile-abi"}; + break; case Lang_OBJCXX: Args = {"-x", "objective-c++", "-frtti"}; break; @@ -94,6 +97,9 @@ StringRef getFilenameForTesting(TestLanguage Lang) { case Lang_OpenCL: return "input.cl"; + case Lang_OBJC: + return "input.m"; + case Lang_OBJCXX: return "input.mm"; } diff --git a/clang/unittests/AST/MatchVerifier.h b/clang/unittests/AST/MatchVerifier.h index c49a2f8221b42a..8bcf05642cb5bc 100644 --- a/clang/unittests/AST/MatchVerifier.h +++ b/clang/unittests/AST/MatchVerifier.h @@ -120,6 +120,10 @@ MatchVerifier::match(const std::string &Code, Args.push_back("-cl-no-stdinc"); FileName = "input.cl"; break; + case Lang_OBJC: + Args.push_back("-fobjc-nonfragile-abi"); + FileName = "input.m"; + break; case Lang_OBJCXX: FileName = "input.mm"; break; diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp index a1b506c3c28d90..9ddc460d1a094a 100644 --- a/clang/unittests/AST/StructuralEquivalenceTest.cpp +++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp @@ -1120,6 +1120,187 @@ TEST_F(StructuralEquivalenceEnumConstantTest, EnumConstantsWithDifferentName) { EXPECT_FALSE(testStructuralMatch(t)); } +struct StructuralEquivalenceObjCCategoryTest : StructuralEquivalenceTest {}; + +TEST_F(StructuralEquivalenceObjCCategoryTest, MatchinCategoryNames) { + auto t = makeDecls("@interface A @end @interface A(X) @end", + "@interface A @end @interface A(X) @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, CategoriesForDifferentClasses) { + auto t = makeDecls("@interface A @end @interface A(X) @end", + "@interface B @end @interface B(X) @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, CategoriesWithDifferentNames) { + auto t = makeDecls("@interface A @end @interface A(X) @end", + "@interface A @end @interface A(Y) @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, CategoryAndExtension) { + auto t = makeDecls("@interface A @end @interface A(X) @end", + "@interface A @end @interface A() @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, MatchingProtocols) { + auto t = makeDecls( + "@protocol P @end @interface A @end @interface A(X)

@end", + "@protocol P @end @interface A @end @interface A(X)

@end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentProtocols) { + auto t = makeDecls( + "@protocol P @end @interface A @end @interface A(X)

@end", + "@protocol Q @end @interface A @end @interface A(X) @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentProtocolsOrder) { + auto t = makeDecls( + "@protocol P @end @protocol Q @end @interface A @end @interface A(X) @end", + "@protocol P @end @protocol Q @end @interface A @end @interface A(X) @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, MatchingIvars) { + auto t = makeDecls( + "@interface A @end @interface A() { int x; } @end", + "@interface A @end @interface A() { int x; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarName) { + auto t = makeDecls( + "@interface A @end @interface A() { int x; } @end", + "@interface A @end @interface A() { int y; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarType) { + auto t = makeDecls( + "@interface A @end @interface A() { int x; } @end", + "@interface A @end @interface A() { float x; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarBitfieldWidth) { + auto t = makeDecls( + "@interface A @end @interface A() { int x: 1; } @end", + "@interface A @end @interface A() { int x: 2; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarVisibility) { + auto t = makeDecls( + "@interface A @end @interface A() { @public int x; } @end", + "@interface A @end @interface A() { @protected int x; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarNumber) { + auto t = makeDecls( + "@interface A @end @interface A() { int x; } @end", + "@interface A @end @interface A() {} @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentIvarOrder) { + auto t = makeDecls( + "@interface A @end @interface A() { int x; int y; } @end", + "@interface A @end @interface A() { int y; int x; } @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, MatchingMethods) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test; @end", + "@interface A @end @interface A(X) -(void)test; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodName) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test; @end", + "@interface A @end @interface A(X) -(void)wasd; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); + + auto t2 = makeDecls( + "@interface A @end @interface A(X) -(void)test:(int)x more:(int)y; @end", + "@interface A @end @interface A(X) -(void)test:(int)x :(int)y; @end", + Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t2)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodClassInstance) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test; @end", + "@interface A @end @interface A(X) +(void)test; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodReturnType) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test; @end", + "@interface A @end @interface A(X) -(int)test; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodParameterType) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test:(int)x; @end", + "@interface A @end @interface A(X) -(void)test:(float)x; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodParameterName) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test:(int)x; @end", + "@interface A @end @interface A(X) -(void)test:(int)y; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodNumber) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)test; @end", + "@interface A @end @interface A(X) @end", Lang_OBJC, objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceObjCCategoryTest, DifferentMethodOrder) { + auto t = makeDecls( + "@interface A @end @interface A(X) -(void)u; -(void)v; @end", + "@interface A @end @interface A(X) -(void)v; -(void)u; @end", Lang_OBJC, + objcCategoryDecl()); + EXPECT_FALSE(testStructuralMatch(t)); +} + struct StructuralEquivalenceTemplateTest : StructuralEquivalenceTest {}; TEST_F(StructuralEquivalenceTemplateTest, ExactlySameTemplates) {