Skip to content
This repository has been archived by the owner on Mar 30, 2021. It is now read-only.

Added check for scope in structural equivalence (experimental). #629

Open
wants to merge 3 commits into
base: ctu-clang7
Choose a base branch
from

Conversation

balazske
Copy link
Collaborator

@balazske balazske commented Mar 8, 2019

I am not sure if this is the best solution but the tests do not fail. The CI tests should be tried. (This is not the final code.)

@@ -338,6 +430,23 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
llvm_unreachable("Invalid template argument kind");
}

/// Determine whether two template argument lists are equivalent.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this hunk related?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see you use it when comparing the contexts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is used in IsEquivalentContext.

@@ -1633,6 +1742,8 @@ unsigned StructuralEquivalenceContext::getApplicableDiagnostic(
return diag::warn_odr_parameter_pack_non_pack;
case diag::err_odr_non_type_parameter_type_inconsistent:
return diag::warn_odr_non_type_parameter_type_inconsistent;
default:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not related too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make a build warning go away (I have another one in ASTImporterTest.cpp, probably a new PR can be created to correct this).

if (auto *ND1 = dyn_cast<NamedDecl>(D1)) {
auto *ND2 = dyn_cast<NamedDecl>(D2);
if (!ND2)
return false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if here we would just get the two DeclContexts from D1 and D2 and we would compare them by either NamedDecl::getQualifiedNameAsString or by index::generateUSRForDecl?
The USR seems more precise and both seems to be slow.
About the slowness we could add yet another local cache to the StructuralEquivalenceContext class.

How is it different than what we had previously in #624 ?
This time we would compare only the containing scopes by string and not the Decls themselves.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I am saying here is just an alternative solution to consider, not necessarily better ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to compare only scope, not the decl name itself (that is done already). Probably this was the reason for some test failures when the qualified name was used.

}
}

static bool IsEquivalentContext(StructuralEquivalenceContext &Context, const ContextVector &Contexts1, const ContextVector &Contexts2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid that we are going to miss too much cases here by implementing this logic all from scratch. A similar logic is already implemented in either NamedDecl::getQualifiedNameAsString or by index::generateUSRForDec. I think we should reuse the USR version ... see my other comment too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The USR seems to be usable but again a string-like (slow?) check.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, I think we should maybe try both solutions.

I like the context vector based solution you have and maybe that is the winner because seems much faster. I am just afraid that we are going to miss something or make errors when implementing that. We can't make big mistakes however when comparing strings.

@martong
Copy link

martong commented Mar 8, 2019

This PR could possibly supersede #625, right?


using ContextVector = llvm::SmallVector<const DeclContext *, 4>;

static void GetContexts(ContextVector &Contexts, const NamedDecl *ND) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it handle transparent contexts correctly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should these be handled? They can be skipped here.

const DeclContext *DC2 = *DC2I;
++DC2I;

if (const auto *Spec1 = dyn_cast<ClassTemplateSpecializationDecl>(DC1)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should compare the template args with function template specializations (FunctionDecl with TK_MemberSpecialization TK_FunctionTemplateSpecialization) too.

OS << *ED;
else
continue;*/
} else if (const auto *ND1 = dyn_cast<NamedDecl>(DC1)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could start with this and then an early return (continue)?

}
}

static bool IsEquivalentContext(StructuralEquivalenceContext &Context, const ContextVector &Contexts1, const ContextVector &Contexts2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd split this function into two:

  1. comparing all the contexts (basically the for loop)
  2. comparing two contexts

}

static bool IsEquivalentContext(StructuralEquivalenceContext &Context, const ContextVector &Contexts1, const ContextVector &Contexts2) {
auto DC2I = Contexts2.begin();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a check before to see whether the sizes are equal and then an early return if not would make it simpler.

Ctx = Ctx->getParent();

// Collect named contexts.
while (Ctx) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems dangerous, what is the parent of a TranslationUnitDecl? Perhaps another guard is needed?
while (Ctx && !Ctx->isTranslationUnit())

@balazske
Copy link
Collaborator Author

balazske commented Mar 8, 2019

Maybe the problems only occur when the scope is explicitly written in source code (like at X::A and Y::A only A is checked). So the "written string" could be compared, or only record and namespace decl (but not function). And the scope check can stop at the innermost encountered function.

if (auto *ID = MD->getClassInterface())
Ctx = ID;

while (Ctx && Ctx->isFunctionOrMethod())
Copy link

@martong martong Mar 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that we can skip functions. Think about this case:

namespace A {
  void foo() {
    struct B { int a; };
  }
  void bar() {
    struct B { int a; };
  }
}

Here the first and last B have a different scope.

Strictly speaking, the equivalency check should handle these cases too, shouldn't it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes theoretically it is possible to make such a structural equivalence check. But can this case occur from ASTImporter? During AST import of bar it is not possible to find (and compare) the B in foo? A DeclContext outside of foo should not have references to things inside foo, or can something "leak" to outside? (A special case can be the "struct definition in function parameter list" case.)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you convinced me, let's skip the functions.

@martong
Copy link

martong commented Mar 8, 2019

Maybe the problems only occur when the scope is explicitly written in source code (like at X::A and Y::A only A is checked). So the "written string" could be compared, or only record and namespace decl (but not function). And the scope check can stop at the innermost encountered function.

Not necessarily. Consider the below test case, where the same X denotes different types. So I think the problem is NOT limited to qualified names only, thus we have to do the scope check.

TEST_F(StructuralEquivalenceFunctionTest,
    Maci2) {
  auto t = makeNamedDecls(
      "namespace A { class X; } namespace C { using namespace A; void foo(X&); }",
      "namespace B { class X; } namespace C { using namespace B; void foo(X&); }",
      Lang_CXX11);
  EXPECT_FALSE(testStructuralMatch(t));
}

@martong
Copy link

martong commented Mar 8, 2019

There is another case which we should keep in mind:

TEST_F(StructuralEquivalenceFunctionTest,
    Maci) {
  auto t = makeNamedDecls(
      "class X; namespace A { using Y = X; } void foo(A::Y&);",
      "class X; namespace B { using Y = X; } void foo(B::Y&);",
      Lang_CXX11);
  EXPECT_TRUE(testStructuralMatch(t));
}

Here, even if the using decls have different scopes, the underlying type is the same. So if we just compare the scope of two using decls we might end up having the wrong decision. Probably we have to skip using decls from the scope check.

... And I think this case shows that if we just did a string based (USR or the other) comparison of the params then we would falsely report an inequivalence in this case.

... if we did the scope check with string on the underlying types then it would still work

... As a consequence we can't use the scope check in CheckCommonEquivalence, we have to branch based on the node kind and that means we have to put this to the CheckKindSpecificEquivalence.

Update: Just recognized, that the FunctionProtoType stores the canonical types for the arguments, so in this case it will be the X properly. (But I am not sure with other kind of types)

@martong martong added the Experimental We still don't know whether this PR will be needed or not. label Apr 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Experimental We still don't know whether this PR will be needed or not.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants