This is an archive of the discontinued LLVM Phabricator instance.

Fix the __interface inheritence rules to work better with IUnknown and IDispatch
ClosedPublic

Authored by erichkeane on Aug 30 2017, 12:51 PM.

Details

Summary

interface objects in MSVC are permitted to inherit from interface types, and interface-like types.

Additionally, there are two default interface-like types (IUnknown and IDispatch) that all interface-like
types must inherit from.

Diff Detail

Repository
rL LLVM

Event Timeline

zahiraam created this revision.Aug 30 2017, 12:51 PM
erichkeane edited edge metadata.Aug 30 2017, 12:56 PM

1- You need to do your diff with -U99999 so that we get the full context of the file.
2- Better explain the issue in the description
3 SemaDeclCXX.cpp changes look like they need clang-format run on them.

aaron.ballman added inline comments.Aug 30 2017, 1:21 PM
lib/Sema/SemaDeclCXX.cpp
2403–2406 ↗(On Diff #113302)

I'm not certain that this helper function helps all that much.

2465–2466 ↗(On Diff #113302)

This should be using RD->hasAttr<UuidAttr>() since you don't need to use the resulting AST information. Also, why are you checking Class->hasAttrs()?

zahiraam updated this revision to Diff 113387.Aug 31 2017, 5:32 AM

Removed the helper function.

If RD (base class) has uuid attribute, we want to ensure that the interface doesn't have attributes. Otherwise cases like:

class declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown1 {};
interface __declspec(dllimport) ISfFileIOPropertyPage1 : public IUnknown1 {};

will compile.

Removed the helper function.

If RD (base class) has uuid attribute, we want to ensure that the interface doesn't have attributes. Otherwise cases like:

class declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown1 {};
interface __declspec(dllimport) ISfFileIOPropertyPage1 : public IUnknown1 {};

will compile.

Is there a reason this code shouldn't work?

class __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown1 {};
__interface __declspec(deprecated("Don't use this")) blah : public IUnknown1 {};

Is it only dllimport that is a problem?

The test case you gave doesn't compile. It returns the diagnostic. CL has the same behavior. I don't think it is because of the dllimport.

The test case you gave doesn't compile. It returns the diagnostic. CL has the same behavior. I don't think it is because of the dllimport.

My test case was based off your example code, not something I had actually tried. Your original example doesn't compile with CL either, even when you remove the dllimport.

That's both of the code snip-its are supposed to fail (take the diagnostic). If I remove the ClasshasAttrs() condition, from line #2459, these snip-its will pass. I was trying to explain the need for that part of the condition.

erichkeane requested changes to this revision.Aug 31 2017, 12:57 PM

When forming 'diffs', please make sure you do so against the current status of Clang. Your latest diff seems to be only against your first commit, so the diff is crazy looking, and also not submit-able.

@aaron.ballman : The purpose of this patch is to allow this to succeed (Zahira, please correct me if I'm wrong):

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {}; 
__interface ISfFileIOPropertyPage : public IUnknown {};

Previously, clang's implementation only permitted interface to inherit from public interfaces. You can see these examples in test/SemaCXX/ms-interface.cpp.

However, our testers discovered that there is an exception to this case: When the base struct/class has attribute "uuid" defined. Apparently while researching, @zahiraam also discovered that EVEN IF the inherited struct/class has UUID, any attribute on the __interface makes said inheritance an error.

THAT SAID, in researching this to try to explain this better, I just discovered that this diagnosis is actually not correct. It seems that CL has some 'special cases' for the inheritence from a struct/class. For example:

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
__interface ISfFileIOPropertyPage : public IUnknown {};

Correctly compiles in CL.

class __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
__interface ISfFileIOPropertyPage : public IUnknown {};

DOES NOT, with the only difference being struct->class.

Additionally,

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown2 {};
__interface ISfFileIOPropertyPage : public IUnknown2 {};

struct __declspec(uuid("00000000-0000-0000-C000-000000000045")) IUnknown {};
__interface ISfFileIOPropertyPage : public IUnknown {};

Fails in CL, with the only difference being changing the UUID by 1 character.

On the other hand,

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
__interface ISfFileIOPropertyPage :  IUnknown {};

DOES compile in CL (note the lack of 'public' inheritence, 'private' there also permits this to compile.

Additionally if IUnknown is not empty (i tried adding 'int x;' to it), this ALSO fails in CL.

Fails to compile in CL. Note that the ONLY change I made was to rename the base-struct. I believe that this commit is being way too liberal in this case. A solution to this SHOULD happen, since this extensively affects the Windows SDK, however @zahiraam likely needs to figure out what the ACTUAL exceptions in CL are. I suspect strongly that the exception here is going to be:
-Base is struct
-base has the UUID (listed above)
-Base is named "IUnknown"
-Base is empty.

This revision now requires changes to proceed.Aug 31 2017, 12:57 PM

Aaron,

Yes I want to this to succeed:

struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
interface ISfFileIOPropertyPage : public IUnknown {};

But I also want this to succeed:

struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
struct IPropertyPage : public IUnknown {};
interface ISfFileIOPropertyPage : public IPropertyPage {};

And I can't figure out how these 2 can be differentiated. I think the conditions that I have currently are differently too. This later case doesn't succeed with the current code.
And I want this to fail:

class declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown1 {};
interface __declspec(dllimport) ISfFileIOPropertyPage1 : public IUnknown1 {};

This currently does with the current code.

I guess I need to work on it a bit more.

Aaron,

Yes I want to this to succeed:

struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
interface ISfFileIOPropertyPage : public IUnknown {};

But I also want this to succeed:

struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
struct IPropertyPage : public IUnknown {};
interface ISfFileIOPropertyPage : public IPropertyPage {};

And I can't figure out how these 2 can be differentiated. I think the conditions that I have currently are differently too. This later case doesn't succeed with the current code.
And I want this to fail:

class declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown1 {};
interface __declspec(dllimport) ISfFileIOPropertyPage1 : public IUnknown1 {};

This currently does with the current code.

I guess I need to work on it a bit more.

It definitely looks like you'll have to walk the inheritance tree to get the 2nd example to work. Based on that, the rule seems to be, an interface can inherit from:
1 - Another
interface
2 - The COMPLETE IUnknown type, such that it is a struct, with a uuid of 000...C000...46, named "IUnknown", with an empty definition. (note that this is independent of namespace)
3- Any other type that:
-a: is a class or struct
-b: has no members
-c: inherits from at least 1 type that:
--I: Is an IUnknown type (in any namespace) --or--
--II: A type that satisfies this point 3.
-d: Inherits from no types other than those in -c.

Note that multiple inheritance is permitted:

namespace S{
struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
}
namespace S2{
struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
}
class __declspec(deprecated("asdf")) IPropertyPage2 : S::IUnknown, S2::IUnknown { }; // Inherits from 2 different IUnknowns
class __declspec(deprecated("asdf")) IPropertyPage3 : S::IUnknown, S2::IUnknown { }; 
class IP3 : IPropertyPage2, IPropertyPage3 {}; // Inherits from 2 different classes that meet #3 above.
zahiraam updated this revision to Diff 114201.Sep 7 2017, 10:11 AM
zahiraam edited edge metadata.

Just upload a new patch. Please review.

erichkeane added inline comments.Sep 7 2017, 10:21 AM
lib/Sema/SemaDeclCXX.cpp
2377 ↗(On Diff #114201)

This function isn't testing the 'base', it is testing the actual record decl.

2391 ↗(On Diff #114201)

This function isn't nearly correct. IT is only testing the first base (over and over, but returns immediately...).

2396 ↗(On Diff #114201)

Note that this can go down a number of layers. You have to ensure that it goes multiple layers down. You may wish to make this recursive, and to use your "IsUnknownType" function..

aaron.ballman added inline comments.Sep 8 2017, 7:16 AM
lib/Sema/SemaDeclCXX.cpp
2385 ↗(On Diff #114201)

This should probably also ensure that the class is in the global namespace so we don't handle foobar::IUnknown improperly.

2386 ↗(On Diff #114201)

Can remove the spurious parens. Also, rather than call hasAttr<> followed by getAttr<> on the same thing, you should factor out the call to getAttr<>.

zahiraam updated this revision to Diff 114428.Sep 8 2017, 1:50 PM

Responding to Erich 's and Aaron's reviews. Thanks.

erichkeane requested changes to this revision.Sep 8 2017, 1:59 PM
erichkeane added inline comments.
lib/Sema/SemaDeclCXX.cpp
2377 ↗(On Diff #114428)

Not sure that 'Record' is the correct word here. You should likely just do "IsDeclPublicInterface". Additionally (though not necessary to change), the only requirement for this type is the isInterface, which comes from TagDecl.

2385 ↗(On Diff #114428)

The type name is "IUnknown". This function name (and the comment above) needs to match this.

2393 ↗(On Diff #114428)

This logic is completely wrong. You are trying to test if RD is IUnknown OR if ANY of its bases are IUnknown (or if they inherit). You absolutely have to loop over the bases here, which you aren't doing here at all.

test/SemaCXX/ms-uuid.cpp
97 ↗(On Diff #114428)

Add some should-compile examples that show multiple inheritance. Particularly where the IUnknown is inherited from the 2nd or later one.

This revision now requires changes to proceed.Sep 8 2017, 1:59 PM

1 more thing I missed.

lib/Sema/SemaDeclCXX.cpp
2389 ↗(On Diff #114428)

This also has to ensure that IUnknown doesn't inherit from anyone AND that it is in the global namespace.

zahiraam updated this revision to Diff 114497.Sep 9 2017, 12:59 PM
zahiraam edited edge metadata.

Erich,

Addressed your comments.
Thanks.

By my reading of the code, you still haven't fixed this example:

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
struct IPropertyPageBase : public IUnknown {};
struct IPropertyPage : public IPropertyPageBase {};

__interface foo {};
__interface bar {};
__interface Prop2 : foo, bar, IUnknown{};

__interface Prop3 : foo, Prop2{};

According to godbolt, all compile w/o error.

lib/Sema/SemaDeclCXX.cpp
2394 ↗(On Diff #114497)

See my example below, you still need to fix this.

zahiraam updated this revision to Diff 114691.Sep 11 2017, 2:13 PM

Erich,
Please review at your convenience. Thanks.

You seem to have had a hard time with the diff tool too... there is an extra file here that needs to be removed.

lib/Sema/SemaDeclCXX.cpp
2390 ↗(On Diff #114691)

@aaron.ballman This logic we'd like you to particularly check on. Does this ensure it isn't in a namespace?

Zahira: since you don't need the value, "isa<TranslationUnitDecl>" is more appropriate here.

2393 ↗(On Diff #114691)

Misspelled IUnknown here.

2394 ↗(On Diff #114691)

If you do the below function right, this one ends up not being necessary.

2405 ↗(On Diff #114691)

This doesn't cover the other children of RD. It checks only the first Base.

aaron.ballman added inline comments.Sep 12 2017, 8:46 AM
lib/Sema/SemaDeclCXX.cpp
2376 ↗(On Diff #114691)

The comment does not match the behavior of the function.

2382 ↗(On Diff #114691)

uuid Unknown -> UUID for IUnknown

2386 ↗(On Diff #114691)

This can be const auto *

2389 ↗(On Diff #114691)

I would move the test for Uuid to be one of the first things tested, since it's the least expensive.

2390 ↗(On Diff #114691)

I would skip the casts entirely and use RD->getDeclContext()->isTranslationUnit()

zahiraam updated this revision to Diff 114935.Sep 12 2017, 4:24 PM

Erich, Aaron,
Please review at your convenience.
Thanks.

Also, Craig mentioned to me that naming rules require static fucntions to start with a lower case letter (not uppercase). Additionally, Variables (like 'result') need to start with a capital letter.

lib/Sema/SemaDeclCXX.cpp
2388 ↗(On Diff #114935)

Up to @aaron.ballman, but moving the getGuid string check (a massive string-diff) to 2nd defeats the purpose of moving UUID up front, which is to put the 'easy' things first. IsTU, isStruct, and isEmpty are likely better.

Additionally, I'd mentioned it before, this still doesn't check to make sure that the IUnknown doesn't inherit from anywhere, does it? MSVC also will error if IUnknown's definition includes an inherited type.

2393 ↗(On Diff #114935)

Still a misspelled comment.

2395 ↗(On Diff #114935)

Since you know the value of this here, there is no reason to waste time with the below loop. Return immediately.

2396 ↗(On Diff #114935)

This should use a range-based for.

2397 ↗(On Diff #114935)

What does "BB" stand for? Please use a descriptive name.

2398 ↗(On Diff #114935)

Two big issues here:
1- This will never allow this loop to hit the second iteration, which it absolutely needs to do.
2- Again, you're in a situation where you only need to continue the execution of this function in certain cases.

Tighten up the 'IUnknown' check, and do the check I mentioned above, and I think this logic is correct. Searching would be required in the positive case, but this is the negative case.

lib/Sema/SemaDeclCXX.cpp
2483 ↗(On Diff #114935)

So, I just realized... this call here is useless, we got sidetracked because of the inversion of logic here. You ACTUALLY just care that this base is either a public interface, OR if it is IUnknown. This logic can be:

if (Class->isInterface() && !IsDeclPublicInterface(...) && !IsIUnknownType(...)) {...}

The search doesn't actually need to take place, since the current base COULD NOT be a valid 'interface' unless it inherited only from interfaces/IUnknown anyway. Unless I'm missing something, you can delete the function "IsOrInheritsFromIUnknown" as well.

Actually... disregard that... the rule is more complex than that.

Based on some playing around with MSVC on godbolt, it seems that it actually marks any type that inherits ONLY from interface types or IUnknown as an interface itself. We may be better off doing that instead.

Additionally, the implementation of "isIUnknown" is actually more complicated too, since it contains function declarations.

lib/Sema/SemaDeclCXX.cpp
2388 ↗(On Diff #114935)

Also, "isEmpty" isn't sufficient. IUnknown actually contains functions as long as none have an implementation. (Declarations only).

zahiraam updated this revision to Diff 115125.Sep 13 2017, 2:55 PM

Hi have made all the changes requested.

Erich,

The IsOrInheritsFromIUnknown function is needed.
Wh

lib/Sema/SemaDeclCXX.cpp
2377 ↗(On Diff #114201)

RD is the base of the interface. But I can rename it to IsRDPublicInterface.

MSVC and xmain compile this:
struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
struct PageBase : public IUnknown {};
struct Page3 : public PageBase {};
struct Page4 : public PageBase {};
interface PropertyPage : public Page4 {};

But MSVC doesn't compile this:

struct declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown {};
struct PageBase : public IUnknown {};
struct Page3 : public PageBase {};
struct Page4 : public PageBase {};
struct Page5 : public Page3, Page4{};
interface PropertyPage : public Page5 {};

So a base of RD that has siblings and inherits from a Unknown is not permitted.
Which means that if the number is siblings are greater than one, we should fail. I guess the current function doesn't take that into account.

Yeah, but

__interface IF1 {};
__interface PP : IUnknown, IF1{}; 
__interface PP2 : PP, Page3, Page4{};

Base PP has siblings here. It seems the rule is even more complex then.

erichkeane added a comment.EditedSep 13 2017, 4:08 PM

A bit more research based on a different implementation:

First, there are TWO special types, not just IUnknown! There is also "IDispatch" with UUID: "00020400-0000-0000-c000-000000000046"

A type is 'interface like' if:
-it has a ZERO virtual inheritance bases
-it has AT LEAST 1 'interface-like' base (interfaces are not 'interface_like' here)
-it has ZERO non-interface/non-interface-like bases.
-it has zero user defined destructors, constructors, operators, or conversions
-it has zero DEFINED methods
-it has ZERO data members
-it does not have 'private' or 'protected' data access specifiers
-it does not have any "friend" decls.

An interface-like can inherit from other __interfaces, or interface-like bases. However, it is limited to only 1 of the latter.

SO, in your example, "Page5" loses its interface-like-ness because it has 2 separate interface-like bases.

erichkeane added a comment.EditedSep 13 2017, 4:13 PM

SO, the implementation would likely be (~2489):
if (Class->isInterface() &&

(KnownBase->getAccessSpecifier() != TTK_public || !RD->isInterfaceLike()) {

with "isInterfaceLike" testing what I outlined above, including checking 'is interface' first.

**Edit wtih a better idea that actually gets parens right

erichkeane commandeered this revision.Sep 13 2017, 6:13 PM
erichkeane edited reviewers, added: zahiraam; removed: erichkeane.

@zahiraam: quickly commendeering, since I have an implementation that I think better explains my discovery than my words. Feel free to commandeer it back later (inside the 'Add Action...' box above the comment at the bottom).

erichkeane retitled this revision from Interface class with uuid base record to Fix the __interface inheritence rules to work better with IUnknown and IDispatch.
erichkeane edited the summary of this revision. (Show Details)

Based off of what I discovered previously, this is my first run at a solution to this problem. @zahiraam (and others), if you can see if I missed anything in this patch, it would be appreciated. I think this is a good place to start further discussion than text conversation.

rnk added inline comments.Sep 14 2017, 11:43 AM
lib/AST/DeclCXX.cpp
1474 ↗(On Diff #115153)

nit: use "interface-like" for consistency with the comments below

1477 ↗(On Diff #115153)

What should this do for incomplete types, i.e. forward decls and templates undergoing instantiation? The only caller of this checks for this case before hand, but maybe we should assert(hasDefinition()); on entry.

1478 ↗(On Diff #115153)

Maybe "Interface-like types cannot have ..."

Can an interface-like type have an explicitly defaulted copy ctor? That will be user declared, but it will probably be trivial.

1486–1489 ↗(On Diff #115153)

Huh, so they can be declared, but they don't have to be pure.

1516–1517 ↗(On Diff #115153)

Are you sure you want this? MSVC rejects the test case that exercises this case.

test/SemaCXX/ms-iunknown-outofline-def.cpp
9–10 ↗(On Diff #115153)

Again, this is a pretty weird error. If the method isn't pure, there must be a definition *somewhere* in the program, unless interface types are always declared with novtable.

test/SemaCXX/ms-iunknown.cpp
24 ↗(On Diff #115153)

MSVC seems to reject this use of multiple inheritance. Are you sure it doesn't just require single-inheritance from an interface-like type? If so, maybe you can simplify the loop over the bases?

erichkeane marked 4 inline comments as done.Sep 14 2017, 12:48 PM

Thanks for the quick response! New patch coming soon.

lib/AST/DeclCXX.cpp
1478 ↗(On Diff #115153)

It cannot according to godbolt. https://godbolt.org/g/wZuNzT

1486–1489 ↗(On Diff #115153)

Correct, and strange. Annoyingly, it is possible to do:

struct S : someIFace {
void foo();
};
  __interface F  : public S {}; // This one is OK.
  void S::foo();
  __interface F2  : public S {}; // This one is NOT OK.
1516–1517 ↗(On Diff #115153)

AM I sure? Not anymore. WAS I sure? Pretty darn. Thanks for the catch!

test/SemaCXX/ms-iunknown-outofline-def.cpp
9–10 ↗(On Diff #115153)

Yeah, I agree. I have no idea if I can limit this to just pure functions, since I'm not totally sure about the use cases. We have at least 1 test in our testbase that isn't virtual, but the context has been lost (might have been minimized from another repro).

erichkeane marked an inline comment as done.

Fixed for @rnk s comments.

rnk accepted this revision.Sep 14 2017, 6:09 PM

lgtm

This revision is now accepted and ready to land.Sep 14 2017, 6:09 PM
This revision was automatically updated to reflect the committed changes.