Missing Availability Information for Enums

I’m currently working on a Clang modification that analyses the (iOS SDK) version in which certain symbols were introduced. Most of it is based on code that looks similar to this:

decl->getAttr()->getIntroduced();

So far, this works fine for ObjC methods, properties, classes, and C functions. One missing piece are enumerations, but I’ve found that they seem to lack availability information alltogether. Let’s pick an example, I’m trying to use the PKPassKitErrorCode enum that was introduced in iOS 6. The framework header declares the following:

typedef NS_ENUM(NSInteger, PKPassKitErrorCode) {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} NS_ENUM_AVAILABLE_IOS(6_0);

Obviously, availability information is provided using the usual macros. This is what the preprocessor generates:

typedef enum PKPassKitErrorCode : NSInteger PKPassKitErrorCode; enum PKPassKitErrorCode : NSInteger {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} attribute((availability(ios,introduced=6.0)));

Still looks ok to me, all the information I’m looking for is included. However, when I try to get the availability information during the compilation process (I’m modifying Sema::DiagnoseUseOfDecl() in SemaExpr.cpp), there is none – getAttr() returns NULL for EnumConstantDecls.

I have not started examining the parsing of the declaration, but wanted to ask first whether there’s an obvious reason why the information is missing, or there’s an obvious mistake/misassumption I’m making?

Any help is greatly appreciated.
Hagi

The attribute here isn’t on the EnumConstantDecls; it’s on the EnumDecl as a whole. You’ll also have to be careful about redeclarations; in this case I would guess that the first EnumDecl (inside the typedef) doesn’t have the attribute, but the second one (where the enumerators are actually defined) does.

Jordan

Thanks for your response!

Re:EnumConstantDecl vs. EnumDecl… my bad. I actually tested both though, and neither the constants nor the declaration have availability information.

Re:Redeclarations: How is this handled in Clang? After looking at the EnumDecl class and the TagDecl class specifically, I assumed Clang keeps all the (re)declarations it finds and makes them accessible via iterators. So I tried to acces the most recent declaration (which should be the one with the constants defined), but also go through all previous declarations and always test whether there’s an availability attribute. Looks like this…

EnumDecl *enumD = dyn_cast_or_null(decl);

bool hit = false;

if (enumD) {
EnumDecl *prev = enumD->getMostRecentDecl();
while (prev) {

if (prev->getAttr()) {
hit = true;
}

prev = prev->getPreviousDecl();
}
}

Unfortunately, prev->getPreviousDecl() always returns NULL, so the while loop executes only once. And it does not find availability information for the most recent declaration.

Is there another way to check all (re)declarations?

I’m currently working on a Clang modification that analyses the (iOS SDK) version in which certain symbols were introduced. Most of it is based on code that looks similar to this:

decl->getAttr()->getIntroduced();

So far, this works fine for ObjC methods, properties, classes, and C functions. One missing piece are enumerations, but I’ve found that they seem to lack availability information alltogether. Let’s pick an example, I’m trying to use the PKPassKitErrorCode enum that was introduced in iOS 6. The framework header declares the following:

typedef NS_ENUM(NSInteger, PKPassKitErrorCode) {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} NS_ENUM_AVAILABLE_IOS(6_0);

Obviously, availability information is provided using the usual macros. This is what the preprocessor generates:

typedef enum PKPassKitErrorCode : NSInteger PKPassKitErrorCode; enum PKPassKitErrorCode : NSInteger {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} attribute((availability(ios,introduced=6.0)));

Still looks ok to me, all the information I’m looking for is included. However, when I try to get the availability information during the compilation process (I’m modifying Sema::DiagnoseUseOfDecl() in SemaExpr.cpp), there is none – getAttr() returns NULL for EnumConstantDecls.

I have not started examining the parsing of the declaration, but wanted to ask first whether there’s an obvious reason why the information is missing, or there’s an obvious mistake/misassumption I’m making?

The attribute here isn’t on the EnumConstantDecls; it’s on the EnumDecl as a whole. You’ll also have to be careful about redeclarations; in this case I would guess that the first EnumDecl (inside the typedef) doesn’t have the attribute, but the second one (where the enumerators are actually defined) does.

Jordan

Thanks for your response!

Re:EnumConstantDecl vs. EnumDecl… my bad. I actually tested both though, and neither the constants nor the declaration have availability information.

Re:Redeclarations: How is this handled in Clang? After looking at the EnumDecl class and the TagDecl class specifically, I assumed Clang keeps all the (re)declarations it finds and makes them accessible via iterators. So I tried to acces the most recent declaration (which should be the one with the constants defined), but also go through all previous declarations and always test whether there’s an availability attribute. Looks like this…

EnumDecl *enumD = dyn_cast_or_null(decl);

bool hit = false;

if (enumD) {
EnumDecl *prev = enumD->getMostRecentDecl();
while (prev) {

if (prev->getAttr()) {
hit = true;
}

prev = prev->getPreviousDecl();
}
}

Unfortunately, prev->getPreviousDecl() always returns NULL, so the while loop executes only once. And it does not find availability information for the most recent declaration.

Is there another way to check all (re)declarations?

Hm, that looks correct to me. It does seem strange to me that the forward-declared enum does not get its own declaration, but even then I’m not sure it would matter. Here’s what Clang’s internal -ast-dump option prints out for this code:

typedef long NSInteger;
typedef enum PKPassKitErrorCode : NSInteger PKPassKitErrorCode; enum PKPassKitErrorCode : NSInteger {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} attribute((availability(ios,introduced=6.0)));

TranslationUnitDecl 0x1020232d0 <>

-TypedefDecl 0x1020237b0 <> __int128_t ‘__int128’
-TypedefDecl 0x102023810 <> __uint128_t ‘unsigned __int128’
-TypedefDecl 0x1020238a0 <> SEL ‘SEL *’
-TypedefDecl 0x102023970 <> id ‘id’
-TypedefDecl 0x102023a40 <> Class ‘Class *’
-ObjCInterfaceDecl 0x102023a90 <> Protocol
-TypedefDecl 0x102023e30 <> __builtin_va_list ‘__va_list_tag [1]’
-TypedefDecl 0x102023e90 <:1:1, col:14> NSInteger ‘long’
-TypedefDecl 0x102058630 <line:2:1, col:45> PKPassKitErrorCode ‘enum PKPassKitErrorCode’:‘enum PKPassKitErrorCode’
-EnumDecl 0x1020586a0 <col:65, line:7:1> PKPassKitErrorCode 'NSInteger':'long' -**AvailabilityAttr 0x102058900 <col:18, col:49> ios 6.0 0 0 ""** -EnumConstantDecl 0x102058780 <line:3:5, col:23> PKUnknownError 'NSInteger':'long' -ImplicitCastExpr 0x102058768 <col:22, col:23> ‘NSInteger’:‘long’
-UnaryOperator 0x102058748 <col:22, col:23> 'int' prefix '-' -IntegerLiteral 0x102058728 col:23 ‘int’ 1
-EnumConstantDecl 0x102058810 <line:4:5, col:26> PKInvalidDataError ‘NSInteger’:‘long’
-ImplicitCastExpr 0x1020587f0 <col:26> 'NSInteger':'long' <IntegralCast> -IntegerLiteral 0x1020587d0 col:26 ‘int’ 1
-EnumConstantDecl 0x102058860 line:5:5 PKUnsupportedVersionError ‘NSInteger’:‘long’
`-EnumConstantDecl 0x1020588b0 line:6:5 PKInvalidSignature ‘NSInteger’:‘long’

So maybe there’s something else going on here, because the attribute is clearly there. You’ll have to provide a more complete test case, and perhaps your modification, to see what’s going on here.

Jordan

I’m currently working on a Clang modification that analyses the (iOS SDK) version in which certain symbols were introduced. Most of it is based on code that looks similar to this:

decl->getAttr()->getIntroduced();

So far, this works fine for ObjC methods, properties, classes, and C functions. One missing piece are enumerations, but I’ve found that they seem to lack availability information alltogether. Let’s pick an example, I’m trying to use the PKPassKitErrorCode enum that was introduced in iOS 6. The framework header declares the following:

typedef NS_ENUM(NSInteger, PKPassKitErrorCode) {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} NS_ENUM_AVAILABLE_IOS(6_0);

Obviously, availability information is provided using the usual macros. This is what the preprocessor generates:

typedef enum PKPassKitErrorCode : NSInteger PKPassKitErrorCode; enum PKPassKitErrorCode : NSInteger {
PKUnknownError = -1,
PKInvalidDataError = 1,
PKUnsupportedVersionError,
PKInvalidSignature,
} attribute((availability(ios,introduced=6.0)));

Still looks ok to me, all the information I’m looking for is included. However, when I try to get the availability information during the compilation process (I’m modifying Sema::DiagnoseUseOfDecl() in SemaExpr.cpp), there is none – getAttr() returns NULL for EnumConstantDecls.

I have not started examining the parsing of the declaration, but wanted to ask first whether there’s an obvious reason why the information is missing, or there’s an obvious mistake/misassumption I’m making?

The attribute here isn’t on the EnumConstantDecls; it’s on the EnumDecl as a whole. You’ll also have to be careful about redeclarations; in this case I would guess that the first EnumDecl (inside the typedef) doesn’t have the attribute, but the second one (where the enumerators are actually defined) does.

Jordan

Thanks for your response!

Re:EnumConstantDecl vs. EnumDecl… my bad. I actually tested both though, and neither the constants nor the declaration have availability information.

Re:Redeclarations: How is this handled in Clang? After looking at the EnumDecl class and the TagDecl class specifically, I assumed Clang keeps all the (re)declarations it finds and makes them accessible via iterators. So I tried to acces the most recent declaration (which should be the one with the constants defined), but also go through all previous declarations and always test whether there’s an availability attribute. Looks like this…

EnumDecl *enumD = dyn_cast_or_null(decl);

bool hit = false;

if (enumD) {
EnumDecl *prev = enumD->getMostRecentDecl();
while (prev) {

if (prev->getAttr()) {
hit = true;
}

prev = prev->getPreviousDecl();
}
}

Unfortunately, prev->getPreviousDecl() always returns NULL, so the while loop executes only once. And it does not find availability information for the most recent declaration.

Is there another way to check all (re)declarations?

Hm, that looks correct to me. It does seem strange to me that the forward-declared enum does not get its own declaration, but even then I’m not sure it would matter.

When built in C++11 mode, we get this, which looks much more reasonable:

TranslationUnitDecl 0x5e9b0d0 <>

-TypedefDecl 0x5e9b610 <> __int128_t ‘__int128’
-TypedefDecl 0x5e9b670 <> __uint128_t ‘unsigned __int128’
-TypedefDecl 0x5e9ba30 <> __builtin_va_list ‘__va_list_tag [1]’
-TypedefDecl 0x5e9ba90 <tmp.c:1:1, col:14> NSInteger ‘long’
-EnumDecl 0x5e9bb10 <line:2:9, col:14> PKPassKitErrorCode ‘NSInteger’:'long’
-TypedefDecl 0x5e9bc10 <col:1, col:45> PKPassKitErrorCode ‘enum PKPassKitErrorCode’:‘enum PKPassKitErrorCode’
-**EnumDecl 0x5e9bca0 prev 0x5e9bb10 <col:65, line:7:1> PKPassKitErrorCode 'NSInteger':'long'** -**AvailabilityAttr 0x5ecea50 <col:18, col:49> ios 6.0 0 0 ""** -EnumConstantDecl 0x5e9bd80 <line:3:5, col:23> PKUnknownError 'enum PKPassKitErrorCode' -ImplicitCastExpr 0x5e9bd68 <col:22, col:23> ‘NSInteger’:‘long’
-UnaryOperator 0x5e9bd48 <col:22, col:23> 'int' prefix '-' -IntegerLiteral 0x5e9bd28 col:23 ‘int’ 1
-EnumConstantDecl 0x5ece960 <line:4:5, col:26> PKInvalidDataError ‘enum PKPassKitErrorCode’
-ImplicitCastExpr 0x5ece940 <col:26> 'NSInteger':'long' <IntegralCast> -IntegerLiteral 0x5e9bdd0 col:26 ‘int’ 1
-EnumConstantDecl 0x5ece9b0 line:5:5 PKUnsupportedVersionError ‘enum PKPassKitErrorCode’
`-EnumConstantDecl 0x5ecea00 line:6:5 PKInvalidSignature ‘enum PKPassKitErrorCode’

Hm, may be an implementation quirk in Objective-C’s version of forward-declared enums, then. Not sure if accidental or by design. Doug, do you happen to know offhand?

Jordan

Hi Doug

have you found time to look into this?

Thanks
Hagi

When built in C++11 mode, we get this, which looks much more reasonable:

TranslationUnitDecl 0x5e9b0d0 <>

-TypedefDecl 0x5e9b610 <> __int128_t ‘__int128’
-TypedefDecl 0x5e9b670 <> __uint128_t ‘unsigned __int128’
-TypedefDecl 0x5e9ba30 <> __builtin_va_list ‘__va_list_tag [1]’
-TypedefDecl 0x5e9ba90 <tmp.c:1:1, col:14> NSInteger ‘long’
-EnumDecl 0x5e9bb10 <line:2:9, col:14> PKPassKitErrorCode ‘NSInteger’:'long’
-TypedefDecl 0x5e9bc10 <col:1, col:45> PKPassKitErrorCode ‘enum PKPassKitErrorCode’:‘enum PKPassKitErrorCode’
-**EnumDecl 0x5e9bca0 prev 0x5e9bb10 <col:65, line:7:1> PKPassKitErrorCode 'NSInteger':'long'** -**AvailabilityAttr 0x5ecea50 <col:18, col:49> ios 6.0 0 0 ""** -EnumConstantDecl 0x5e9bd80 <line:3:5, col:23> PKUnknownError 'enum PKPassKitErrorCode' -ImplicitCastExpr 0x5e9bd68 <col:22, col:23> ‘NSInteger’:‘long’
-UnaryOperator 0x5e9bd48 <col:22, col:23> 'int' prefix '-' -IntegerLiteral 0x5e9bd28 col:23 ‘int’ 1
-EnumConstantDecl 0x5ece960 <line:4:5, col:26> PKInvalidDataError ‘enum PKPassKitErrorCode’
-ImplicitCastExpr 0x5ece940 <col:26> 'NSInteger':'long' <IntegralCast> -IntegerLiteral 0x5e9bdd0 col:26 ‘int’ 1
-EnumConstantDecl 0x5ece9b0 line:5:5 PKUnsupportedVersionError ‘enum PKPassKitErrorCode’
`-EnumConstantDecl 0x5ecea00 line:6:5 PKInvalidSignature ‘enum PKPassKitErrorCode’

Hm, may be an implementation quirk in Objective-C’s version of forward-declared enums, then. Not sure if accidental or by design. Doug, do you happen to know offhand?

I ran this with top-of-tree Clang under Objective-C, and I see:

-TypedefDecl 0x7fcc2a827a90 <t.m:1:1, col:14> NSInteger ‘long’
-EnumDecl 0x7fcc2a827b10 <line:3:9, col:14> PKPassKitErrorCode ‘NSInteger’:‘long’
-TypedefDecl 0x7fcc2a859630 <col:1, col:45> PKPassKitErrorCode ‘enum PKPassKitErrorCode’:‘enum PKPassKitErrorCode’
-EnumDecl 0x7fcc2a8596a0 prev 0x7fcc2a827b10 <col:65, line:8:1> PKPassKitErrorCode 'NSInteger':'long' -**AvailabilityAttr 0x7fcc2a859900 <col:18, col:49> ios 6.0 0 0 ""** -EnumConstantDecl 0x7fcc2a859780 <line:4:5, col:23> PKUnknownError 'NSInteger':'long' -ImplicitCastExpr 0x7fcc2a859768 <col:22, col:23> ‘NSInteger’:‘long’
-UnaryOperator 0x7fcc2a859748 <col:22, col:23> 'int' prefix '-' -IntegerLiteral 0x7fcc2a859728 col:23 ‘int’ 1
-EnumConstantDecl 0x7fcc2a859810 <line:5:5, col:26> PKInvalidDataError ‘NSInteger’:‘long’
-ImplicitCastExpr 0x7fcc2a8597f0 <col:26> 'NSInteger':'long' <IntegralCast> -IntegerLiteral 0x7fcc2a8597d0 col:26 ‘int’ 1
-EnumConstantDecl 0x7fcc2a859860 line:6:5 PKUnsupportedVersionError ‘NSInteger’:‘long’
`-EnumConstantDecl 0x7fcc2a8598b0 line:7:5 PKInvalidSignature ‘NSInteger’:‘long’

which looks correct (and identical to what happens in C++11 mode). I suspect that Sebastian’s headers aren’t expanding NS_ENUM the same way in Objective-C as in Objective-C++11.

  • Doug

NS_ENUM isn’t defined the same in Objective-C as in Objective-C++.

#define NS_ENUM(_type,_name) enum _name : _type _name; enum _name : _type

vs.

#define NS_ENUM(_type,_name) _type _name; enum

This has come up before—something about conversions. But aha! In Objective-C++, the enum is unnamed. Could that be part of the problem?

Jordan

When built in C++11 mode, we get this, which looks much more reasonable:

TranslationUnitDecl 0x5e9b0d0 <<invalid sloc>>
>-TypedefDecl 0x5e9b610 <<invalid sloc>> __int128_t '__int128'
>-TypedefDecl 0x5e9b670 <<invalid sloc>> __uint128_t 'unsigned __int128'
>-TypedefDecl 0x5e9ba30 <<invalid sloc>> __builtin_va_list '__va_list_tag [1]'
>-TypedefDecl 0x5e9ba90 <tmp.c:1:1, col:14> NSInteger 'long'
>-EnumDecl 0x5e9bb10 <line:2:9, col:14> PKPassKitErrorCode 'NSInteger':'long'
>-TypedefDecl 0x5e9bc10 <col:1, col:45> PKPassKitErrorCode 'enum PKPassKitErrorCode':'enum PKPassKitErrorCode'
`-EnumDecl 0x5e9bca0 prev 0x5e9bb10 <col:65, line:7:1> PKPassKitErrorCode 'NSInteger':'long'
  >-AvailabilityAttr 0x5ecea50 <col:18, col:49> ios 6.0 0 0 ""
  >-EnumConstantDecl 0x5e9bd80 <line:3:5, col:23> PKUnknownError 'enum PKPassKitErrorCode'
  > `-ImplicitCastExpr 0x5e9bd68 <col:22, col:23> 'NSInteger':'long' <IntegralCast>
  > `-UnaryOperator 0x5e9bd48 <col:22, col:23> 'int' prefix '-'
  > `-IntegerLiteral 0x5e9bd28 <col:23> 'int' 1
  >-EnumConstantDecl 0x5ece960 <line:4:5, col:26> PKInvalidDataError 'enum PKPassKitErrorCode'
  > `-ImplicitCastExpr 0x5ece940 <col:26> 'NSInteger':'long' <IntegralCast>
  > `-IntegerLiteral 0x5e9bdd0 <col:26> 'int' 1
  >-EnumConstantDecl 0x5ece9b0 <line:5:5> PKUnsupportedVersionError 'enum PKPassKitErrorCode'
  `-EnumConstantDecl 0x5ecea00 <line:6:5> PKInvalidSignature 'enum PKPassKitErrorCode'

Hm, may be an implementation quirk in Objective-C's version of forward-declared enums, then. Not sure if accidental or by design. Doug, do you happen to know offhand?

I ran this with top-of-tree Clang under Objective-C, and I see:

>-TypedefDecl 0x7fcc2a827a90 <t.m:1:1, col:14> NSInteger 'long'
>-EnumDecl 0x7fcc2a827b10 <line:3:9, col:14> PKPassKitErrorCode 'NSInteger':'long'
>-TypedefDecl 0x7fcc2a859630 <col:1, col:45> PKPassKitErrorCode 'enum PKPassKitErrorCode':'enum PKPassKitErrorCode'
`-EnumDecl 0x7fcc2a8596a0 prev 0x7fcc2a827b10 <col:65, line:8:1> PKPassKitErrorCode 'NSInteger':'long'
  >-AvailabilityAttr 0x7fcc2a859900 <col:18, col:49> ios 6.0 0 0 ""
  >-EnumConstantDecl 0x7fcc2a859780 <line:4:5, col:23> PKUnknownError 'NSInteger':'long'
  > `-ImplicitCastExpr 0x7fcc2a859768 <col:22, col:23> 'NSInteger':'long' <IntegralCast>
  > `-UnaryOperator 0x7fcc2a859748 <col:22, col:23> 'int' prefix '-'
  > `-IntegerLiteral 0x7fcc2a859728 <col:23> 'int' 1
  >-EnumConstantDecl 0x7fcc2a859810 <line:5:5, col:26> PKInvalidDataError 'NSInteger':'long'
  > `-ImplicitCastExpr 0x7fcc2a8597f0 <col:26> 'NSInteger':'long' <IntegralCast>
  > `-IntegerLiteral 0x7fcc2a8597d0 <col:26> 'int' 1
  >-EnumConstantDecl 0x7fcc2a859860 <line:6:5> PKUnsupportedVersionError 'NSInteger':'long'
  `-EnumConstantDecl 0x7fcc2a8598b0 <line:7:5> PKInvalidSignature 'NSInteger':'long'

which looks correct (and identical to what happens in C++11 mode). I suspect that Sebastian's headers aren't expanding NS_ENUM the same way in Objective-C as in Objective-C++11.

NS_ENUM isn't defined the same in Objective-C as in Objective-C++.

#define NS_ENUM(_type,_name) enum _name : _type _name; enum _name : _type

vs.

#define NS_ENUM(_type,_name) _type _name; enum

This has come up before—something about conversions. But aha! In Objective-C++, the enum is unnamed. Could that be part of the problem?

Okay, so take:

typedef NS_ENUM(NSInteger, PKPassKitErrorCode) {
    PKUnknownError = -1,
    PKInvalidDataError = 1,
    PKUnsupportedVersionError,
    PKInvalidSignature,
} NS_ENUM_AVAILABLE_IOS(6_0);

and expand that out based on the latter #define and you get:

typedef NSInteger PKPassKitErrorCode;

enum {
    PKUnknownError = -1,
    PKInvalidDataError = 1,
    PKUnsupportedVersionError,
    PKInvalidSignature,
} __attribute__((availability(ios,introduced=6.0)));

with the AST

-TypedefDecl 0x7fe243027a90 <t.m:1:1, col:14> NSInteger 'long'
-TypedefDecl 0x7fe243027b10 <line:9:1, col:28> PKPassKitErrorCode 'NSInteger':'long'

`-EnumDecl 0x7fe243027b60 <line:6:43, line:14:1>
  >-AvailabilityAttr 0x7fe2430597d0 <col:18, col:49> ios 6.0 0 0 ""
  >-EnumConstantDecl 0x7fe243059670 <line:10:5, col:23> PKUnknownError 'int'
  > `-UnaryOperator 0x7fe243059650 <col:22, col:23> 'int' prefix '-'
  > `-IntegerLiteral 0x7fe243059630 <col:23> 'int' 1
  >-EnumConstantDecl 0x7fe2430596e0 <line:11:5, col:26> PKInvalidDataError 'int'
  > `-IntegerLiteral 0x7fe2430596c0 <col:26> 'int' 1
  >-EnumConstantDecl 0x7fe243059730 <line:12:5> PKUnsupportedVersionError 'int'
  `-EnumConstantDecl 0x7fe243059780 <line:13:5> PKInvalidSignature 'int'

The availability attribute is (correctly) on the anonymous enum… but there's nothing whatsoever that relates the typedef (PKPassKitErrorCode) to the anonymous enum in the source code.

Why aren't the two connected? Because PKPassKitErrorCode needs to have the underlying type NSInteger to be sure that it's the right size, and neither straight C nor C++ provide enumerations with a fixed underlying type. C++11 and Objective-C do allow enumerations with a fixed underlying type, so we can address this problem without having to split the typedef from the enum.

  - Doug