This is an archive of the discontinued LLVM Phabricator instance.

[clang][ASTImporter]Skip check depth of friend template parameter
ClosedPublic

Authored by jcsxky on Jul 31 2023, 7:15 AM.

Details

Summary

Depth of the parameter of friend template class declaration in a template class is 1, while in the specialization the depth is 0. This will cause failure on 'IsStructurallyEquivalent' as a name conflict in 'VisitClassTemplateDecl'(see testcase of 'SkipComparingFriendTemplateDepth'). The patch fix it by ignore the depth only in this special case.

Diff Detail

Event Timeline

jcsxky created this revision.Jul 31 2023, 7:15 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 31 2023, 7:15 AM
Herald added a subscriber: martong. · View Herald Transcript
jcsxky requested review of this revision.Jul 31 2023, 7:15 AM
jcsxky changed the visibility from "Public (No Login Required)" to "jcsxky (Qizhi Hu)".Aug 1 2023, 12:59 PM
jcsxky updated this revision to Diff 546281.Aug 1 2023, 5:48 PM
jcsxky edited the summary of this revision. (Show Details)

update patch

jcsxky changed the visibility from "jcsxky (Qizhi Hu)" to "Public (No Login Required)".Aug 1 2023, 5:48 PM
jcsxky retitled this revision from [clang][ASTImporter]Don't add template decl as in friend decl to [clang][ASTImporter]Skip check friend template declaration in VisitClassTemplateDecl.Aug 1 2023, 6:28 PM
jcsxky updated this revision to Diff 546301.EditedAug 1 2023, 8:19 PM

update patch for passing unittest

jcsxky updated this revision to Diff 546309.EditedAug 1 2023, 8:50 PM

update patch for passing unittest and test

jcsxky updated this revision to Diff 546698.Aug 2 2023, 10:14 PM

format code

aprantl edited reviewers, added: Michael137; removed: aprantl.Aug 3 2023, 9:27 AM
aprantl added a subscriber: aprantl.
jcsxky updated this revision to Diff 547124.Aug 4 2023, 12:30 AM

add unittest

Could you please elaborate in the commit message what exactly the issue currently is and how the patch addresses it?

jcsxky edited the summary of this revision. (Show Details)Aug 4 2023, 3:14 AM
jcsxky edited the summary of this revision. (Show Details)Aug 4 2023, 3:15 AM

Could you please elaborate in the commit message what exactly the issue currently is and how the patch addresses it?

Could you please elaborate in the commit message what exactly the issue currently is and how the patch addresses it?

details are added to commit message

The summary tells nothing about what is the real problem to fix here. In the lit test I see that an error message is displayed, this causes the test failure. The problem is that the structural equivalence check is not good for this special case. The imported AST contains a class template specialization for A, in this specialization the friend class template A has a previous decl that points to the original friend class template that is in a ClassTemplateDecl. In the specialization the "depth" of template arguments is 0, but in the original template it is 1 (the "to" code at import contains only the "original template", no specialization). This difference in the "depth" causes the type mismatch when the specialization is imported.
AST dump of this code can show the problem:

template<class T, T U>
class A;

template<class T, T U>
class A {
public:
  template<class P, P Q>
  friend class A;

  A(T x):x(x){}
	
private:
  T x;
};

A<int,3> a1(0);

It is really interesting that at friend templates the depth is 1 but friend declarations point to objects outside the class, so really the depth should not increase in a friend template from this point of view. But this is an AST issue.

clang/lib/AST/ASTImporter.cpp
5857

It is not good to use ParentMap in the AST importer because it does AST traversal, even worse if this is done on the To context where the AST is modified and may be in incomplete state.
This way of fix is probably not good for a case when there is a real structural in-equivalence, this would be not detected. And the current solution skips this FoundDecl but (at least in the used test code) this should be found, not skipped. (But we can create code where the skip is correct, if there is a real structural in-equivalence.)

clang/unittests/AST/ASTImporterTest.cpp
4252

I think the namespace __1 is not important for reproduction of this problem.

4268

Functions foo, bar, main are not required. It is only important to have a variable of type A like A<int, 3> a1(0); in the imported code at getTuDecl.

4281

The coding format should be aligned to the format of other test codes in this file, and this is normally same as the clang format guidelines (automatic reformatting does not work in the test code).

4318

Definition is misleading because this is not the definition, it matches the first declaration of A in the AST. Better name is like FromA like in the other tests, or FromXxx.

4320

The imported name can be ToA or ToXxx or ImportedXxx, this makes real distinction between the from and to objects.

4322

This cast is not needed, type of Template is already ClassTemplateDecl*.

jcsxky added a comment.Aug 5 2023, 9:36 AM

The summary tells nothing about what is the real problem to fix here. In the lit test I see that an error message is displayed, this causes the test failure. The problem is that the structural equivalence check is not good for this special case. The imported AST contains a class template specialization for A, in this specialization the friend class template A has a previous decl that points to the original friend class template that is in a ClassTemplateDecl. In the specialization the "depth" of template arguments is 0, but in the original template it is 1 (the "to" code at import contains only the "original template", no specialization). This difference in the "depth" causes the type mismatch when the specialization is imported.
AST dump of this code can show the problem:

template<class T, T U>
class A;

template<class T, T U>
class A {
public:
  template<class P, P Q>
  friend class A;

  A(T x):x(x){}
	
private:
  T x;
};

A<int,3> a1(0);

It is really interesting that at friend templates the depth is 1 but friend declarations point to objects outside the class, so really the depth should not increase in a friend template from this point of view. But this is an AST issue.

Depth of specialization friend template is 0 while it is 1 in class declaration is reasonable, because after template class specialization there not exists template parameter and the friend template becomes outermost scope, thus depth is 0. Thanks to the different of depth causes the non equivalence of the two 'NonTypeTemplateParm', I think there should ignore comparing the depth in this special case to make two types equivalence.

jcsxky updated this revision to Diff 547501.EditedAug 5 2023, 10:42 AM
jcsxky edited the summary of this revision. (Show Details)

update diff: ignore comparing depth in friend class template

jcsxky retitled this revision from [clang][ASTImporter]Skip check friend template declaration in VisitClassTemplateDecl to [clang][ASTImporter]Skip check friend template depth.Aug 5 2023, 10:43 AM
jcsxky updated this revision to Diff 547505.Aug 5 2023, 11:16 AM

clean unittest

jcsxky edited the summary of this revision. (Show Details)Aug 7 2023, 12:33 AM

This fix can cause problems because the depth comparison is omitted all times. It would be better to omit depth check if the imported template is a friend, and has a ClassTemplateSpecializationDecl as context. Probably even then it is possible to construct cases where the checked template has references to other templates with different "depth" which should not omitted in the check. But I have no idea of a better solution, only to pass a ClassTemplateDecl or ClassTemplateSpecializationDecl to StructuralEquivalenceContext and omit the depth check only at this object.

jcsxky updated this revision to Diff 548166.Aug 8 2023, 5:35 AM
jcsxky edited the summary of this revision. (Show Details)

update diff: add more conditions on when to ignore comparing depth of two template classes to minimize influence to others and fix name of test case to make easy understanding

jcsxky added a comment.Aug 8 2023, 5:45 AM

This fix can cause problems because the depth comparison is omitted all times. It would be better to omit depth check if the imported template is a friend, and has a ClassTemplateSpecializationDecl as context. Probably even then it is possible to construct cases where the checked template has references to other templates with different "depth" which should not omitted in the check. But I have no idea of a better solution, only to pass a ClassTemplateDecl or ClassTemplateSpecializationDecl to StructuralEquivalenceContext and omit the depth check only at this object.

Thanks for your advice. More conditions are added to check when to ignore the comparison and minimize the influence to other.
Also I have tried to not increase depth in friend declaration in the template class, but the code affects others a lot. Only to skip the comparison can pass this special case.

jcsxky retitled this revision from [clang][ASTImporter]Skip check friend template depth to [clang][ASTImporter]Skip check depth of friend template parameter.Aug 11 2023, 9:53 AM
jcsxky edited the summary of this revision. (Show Details)

A simple test should be added to StructuralEquivalenceTest.cpp too to check if ignore (and not ignore) depth works.

I think this solution is not always correct, but is still an improvement.

clang/include/clang/AST/ASTStructuralEquivalence.h
80
bool IgnoreTemplateParmDepth = false)
clang/lib/AST/ASTImporter.cpp
512

This should be false to have the original behavior if not specified.

5857

Probably add IsFriendTemplate?

clang/unittests/AST/ASTImporterTest.cpp
4292

Probably add hasDefinition() to the matcher.

jcsxky updated this revision to Diff 550030.Aug 14 2023, 11:05 AM

fixed according to the review and add testcase to StructuralEquivalenceTest.cpp.

jcsxky added inline comments.Aug 14 2023, 11:14 AM
clang/lib/AST/ASTImporter.cpp
5857

I think this is not needed because D is not always a friend template class according to the testcase (IsFriendTemplate is false in testcase).

balazske added inline comments.Aug 15 2023, 12:00 AM
clang/unittests/AST/StructuralEquivalenceTest.cpp
1700
IgnoreTemplateParmDepth) {
1735

It should work with more simple code like:

makeDecls<ClassTemplateDecl>(
  R"(
    template<class> struct A;
  )",
  R"(
    template<class> struct S {
      template<class> friend struct A;
    };
  )",
  Lang_CXX03, classTemplateDecl(hasName("A")), classTemplateDecl(hasName("A"))
);
jcsxky updated this revision to Diff 550208.Aug 15 2023, 12:54 AM

Add more simple testcase to StructuralEquivalenceTest.cpp.

balazske added inline comments.Aug 15 2023, 1:12 AM
clang/unittests/AST/StructuralEquivalenceTest.cpp
1711

The intent was to have EXPECT_FALSE without ignore depth, and the next test would be not needed. If it does not work with this code a different code can be found. The depth of the (unnamed) template parameter is really different in these cases too, but it is probably not detected by structural equivalence. Maybe template<int U> or template<class T, T U> is needed. But to document this behavior the current test can be added too, with name IgnoreTemplateParmDepthAtTemplateTypeParmDecl, and the new one with name IgnoreTemplateParmDepthAtNonTypeTemplateParmDecl.

jcsxky added inline comments.Aug 15 2023, 2:01 AM
clang/unittests/AST/StructuralEquivalenceTest.cpp
1711
  1. The reason why it's true without ignore depth is that template parameter is 'TemplateTypeParm' in this case. So
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                     TemplateTypeParmDecl *D1,
                                     TemplateTypeParmDecl *D2)

will be called and return true.

  1. When process 'NonTypeTemplateParm',
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                     QualType T1, QualType T2)

is called and return false as the different depth.
I will add IgnoreTemplateParmDepthAtNonTypeTemplateParmDecl to illustrate the difference. Also, next test will be removed.

jcsxky updated this revision to Diff 550214.Aug 15 2023, 2:10 AM

Add testcase to illustrate the difference between TemplateTypeParm and NonTypeTemplateParm in detecting by structural equivalence.

balazske added inline comments.Aug 15 2023, 8:15 AM
clang/unittests/AST/StructuralEquivalenceTest.cpp
1718

The convention is to start variable names with uppercase, for example "Decls".

jcsxky updated this revision to Diff 550352.Aug 15 2023, 8:55 AM

Fix variable names according to the convention.

A simple test should be added to StructuralEquivalenceTest.cpp too to check if ignore (and not ignore) depth works.

I think this solution is not always correct, but is still an improvement.

As a friend template declaration, compared to the template class definition, the only difference is the depth of the NonTypeTemplateParam. If the template class definition is equivalent to the class in From context, there is no probelm. Otherwise, the friend template declaration is also can't equivalent to it. Because the friend template declaration is equivalent to its definition when ignore the depth. To make a counterexample, we should put the friend declaration in different depth, but they are all equivalent in these cases.

balazske accepted this revision.Aug 21 2023, 7:03 AM

My concern was related to nested namespaces or nested classes with friend declarations that are equivalent and differ only in the nesting level. It may be possible to construct code where a declaration at an inner (nested) level is found to be equivalent with a similar looking class at an outer level. But I now do not have time to look for an example to test it, and I am not fully familiar with exact rules of friend declarations, so I accept this fix.

This revision is now accepted and ready to land.Aug 21 2023, 7:03 AM

My concern was related to nested namespaces or nested classes with friend declarations that are equivalent and differ only in the nesting level. It may be possible to construct code where a declaration at an inner (nested) level is found to be equivalent with a similar looking class at an outer level. But I now do not have time to look for an example to test it, and I am not fully familiar with exact rules of friend declarations, so I accept this fix.

  • The same declcontext will make the template class and friend template class in the same namespace.
  • Namespace will not change the depth of template class parameter. So, the equvalence will not be changed whether ignore the depth or not.
  • Declcontext of friend tempalte class is outer namespace (if exists), So, classes in this declcontext are equvalent with identical name.

According the the conclusion above, the nested namespace or nested class will not affect the equvalence of the two classes (as far as I concerned).

shafik added inline comments.Aug 21 2023, 11:35 AM
clang/unittests/AST/StructuralEquivalenceTest.cpp
140

See bugprone-argument-comment for details of this format.

We use this everywhere we have literals without clear meaning being used as arguments.

145
jcsxky updated this revision to Diff 552179.Aug 21 2023, 6:43 PM

add argument comment according to bugprone-argument-comment.

jcsxky added inline comments.Aug 21 2023, 6:46 PM
clang/unittests/AST/StructuralEquivalenceTest.cpp
140

argument comment has been added.

This revision was landed with ongoing or failed builds.Aug 21 2023, 10:16 PM
This revision was automatically updated to reflect the committed changes.