A doubt about cvr-qualified array types.

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

struct S {
int a[2];
};

void foo(void) {
const struct S* ps;
const int (*b)[2];
b = &ps->a;
}

as well as its ast dump:

struct S {
int a[2];
};
void foo() (CompoundStmt 0x3c48bf8 <arraytype.c:5:16, line:9:1>
(DeclStmt 0x3c1be20 <line:6:3, col:21>
0x3c1bdd0 "const struct S *ps")
(DeclStmt 0x3c1bf60 <line:7:3, col:20>
0x3c1bf10 "const int (*b)[2]")
(BinaryOperator 0x3c48bc8 <line:8:3, col:12> 'const int (*)[2]' '='
(DeclRefExpr 0x3c1bf80 <col:3> 'const int (*)[2]' Var='b' 0x3c1bf10)
(UnaryOperator 0x3c48ba0 <col:7, col:12> 'int const (*)[2]' prefix '&'
(MemberExpr 0x3c48b30 <col:8, col:12> 'int const[2]' ->a 0x3c1bc10
(DeclRefExpr 0x3c1bfb8 <col:8> 'const struct S *' Var='ps'
0x3c1bdd0)))))

The dump shows a mismatch between the types of the lhs and the rhs of
the assignment expression:
- the lhs has type const int (*)[2]
- the rhs has type int const (*)[2]
that is, in the rhs the const qualifier is applied to the array type,
rather than to the array element type. The two types are anyway detected
as being equivalent as witnessed by the absence of an implicit cast.

Per C99, those are the same type.

We thought there was an implementation invariant whereby qualifiers of
array types, when stored in clang::Expr nodes, were automatically pushed
on the corresponding array element type.
Is this NOT the case?

For canonical types, yes... for non-canonical types, no. The two
types in question here should probably be fixed to print the same way,
though... it's confusing to have what appears to be the same type
print in different ways.

-Eli

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

struct S {
int a[2];
};

void foo(void) {
const struct S* ps;
const int (*b)[2];
b = &ps->a;
}

as well as its ast dump:

struct S {
   int a[2];
};
void foo() (CompoundStmt 0x3c48bf8 <arraytype.c:5:16, line:9:1>
(DeclStmt 0x3c1be20 <line:6:3, col:21>
   0x3c1bdd0 "const struct S *ps")
(DeclStmt 0x3c1bf60 <line:7:3, col:20>
   0x3c1bf10 "const int (*b)[2]")
(BinaryOperator 0x3c48bc8 <line:8:3, col:12> 'const int (*)[2]' '='
   (DeclRefExpr 0x3c1bf80 <col:3> 'const int (*)[2]' Var='b' 0x3c1bf10)
   (UnaryOperator 0x3c48ba0 <col:7, col:12> 'int const (*)[2]' prefix '&'
     (MemberExpr 0x3c48b30 <col:8, col:12> 'int const[2]' ->a 0x3c1bc10
       (DeclRefExpr 0x3c1bfb8 <col:8> 'const struct S *' Var='ps'
0x3c1bdd0)))))

The dump shows a mismatch between the types of the lhs and the rhs of
the assignment expression:
- the lhs has type const int (*)[2]
- the rhs has type int const (*)[2]
that is, in the rhs the const qualifier is applied to the array type,
rather than to the array element type. The two types are anyway detected
as being equivalent as witnessed by the absence of an implicit cast.

Per C99, those are the same type.

To be exact C99 6.7.3.8 says something a little different:

"If the specification of an array type includes any type qualifiers, the
element type is so qualified, not the array type."

So it seems to exclude the existence of qualified array type...

We thought there was an implementation invariant whereby qualifiers of
array types, when stored in clang::Expr nodes, were automatically pushed
on the corresponding array element type.
Is this NOT the case?

For canonical types, yes... for non-canonical types, no. The two
types in question here should probably be fixed to print the same way,
though... it's confusing to have what appears to be the same type
print in different ways.

However, if the current behaviour is confirmed to be intentional, we
should taken in account that the type stored along with Expr nodes might
have qualifiers also in array type.

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

struct S {
int a[2];
};

void foo(void) {
const struct S* ps;
const int (*b)[2];
b = &ps->a;
}

as well as its ast dump:

struct S {
int a[2];
};
void foo() (CompoundStmt 0x3c48bf8 <arraytype.c:5:16, line:9:1>
(DeclStmt 0x3c1be20 <line:6:3, col:21>
0x3c1bdd0 "const struct S *ps")
(DeclStmt 0x3c1bf60 <line:7:3, col:20>
0x3c1bf10 "const int (*b)[2]")
(BinaryOperator 0x3c48bc8 <line:8:3, col:12> 'const int (*)[2]' '='
(DeclRefExpr 0x3c1bf80 <col:3> 'const int (*)[2]' Var='b' 0x3c1bf10)
(UnaryOperator 0x3c48ba0 <col:7, col:12> 'int const (*)[2]' prefix '&'
(MemberExpr 0x3c48b30 <col:8, col:12> 'int const[2]' ->a 0x3c1bc10
(DeclRefExpr 0x3c1bfb8 <col:8> 'const struct S *' Var='ps'
0x3c1bdd0)))))

The dump shows a mismatch between the types of the lhs and the rhs of
the assignment expression:
- the lhs has type const int (*)[2]
- the rhs has type int const (*)[2]
that is, in the rhs the const qualifier is applied to the array type,
rather than to the array element type. The two types are anyway detected
as being equivalent as witnessed by the absence of an implicit cast.

Per C99, those are the same type.

To be exact C99 6.7.3.8 says something a little different:

"If the specification of an array type includes any type qualifiers, the
element type is so qualified, not the array type."

So it seems to exclude the existence of qualified array type...

Err, "int const (*)[2]" isn't a qualified array type... it's just an
equivalent way of writing "const int (*)[2]". But yes, the reason
it's printed differently is due to the internal representation.

We thought there was an implementation invariant whereby qualifiers of
array types, when stored in clang::Expr nodes, were automatically pushed
on the corresponding array element type.
Is this NOT the case?

For canonical types, yes... for non-canonical types, no. The two
types in question here should probably be fixed to print the same way,
though... it's confusing to have what appears to be the same type
print in different ways.

However, if the current behaviour is confirmed to be intentional, we
should taken in account that the type stored along with Expr nodes might
have qualifiers also in array type.

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Why would you need such a type?

-Eli

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

struct S {
int a[2];
};

void foo(void) {
const struct S* ps;
const int (*b)[2];
b = &ps->a;
}

as well as its ast dump:

struct S {
   int a[2];
};
void foo() (CompoundStmt 0x3c48bf8 <arraytype.c:5:16, line:9:1>
(DeclStmt 0x3c1be20 <line:6:3, col:21>
   0x3c1bdd0 "const struct S *ps")
(DeclStmt 0x3c1bf60 <line:7:3, col:20>
   0x3c1bf10 "const int (*b)[2]")
(BinaryOperator 0x3c48bc8 <line:8:3, col:12> 'const int (*)[2]' '='
   (DeclRefExpr 0x3c1bf80 <col:3> 'const int (*)[2]' Var='b' 0x3c1bf10)
   (UnaryOperator 0x3c48ba0 <col:7, col:12> 'int const (*)[2]' prefix '&'
     (MemberExpr 0x3c48b30 <col:8, col:12> 'int const[2]' ->a 0x3c1bc10
       (DeclRefExpr 0x3c1bfb8 <col:8> 'const struct S *' Var='ps'
0x3c1bdd0)))))

The dump shows a mismatch between the types of the lhs and the rhs of
the assignment expression:
- the lhs has type const int (*)[2]
- the rhs has type int const (*)[2]
that is, in the rhs the const qualifier is applied to the array type,
rather than to the array element type. The two types are anyway detected
as being equivalent as witnessed by the absence of an implicit cast.

Per C99, those are the same type.

To be exact C99 6.7.3.8 says something a little different:

"If the specification of an array type includes any type qualifiers, the
element type is so qualified, not the array type."

So it seems to exclude the existence of qualified array type...

Err, "int const (*)[2]" isn't a qualified array type... it's just an
equivalent way of writing "const int (*)[2]". But yes, the reason
it's printed differently is due to the internal representation.

Ok, so unfortunately we have an internal representation that is
incongruent with C standard. This is related to efficiency issues?

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Why would you need such a type?

Consider this example:

struct S {
  volatile int a[2];
};

void foo(void) {
  const struct S* ps;
  &ps->a;
}

The type of &ps->a has a volatile qualifier attached to builtin int type
and a const qualifier attached to array type.

It seems to me rather obvious that this is an undesiderable situation
for library client that ask a sensible type for an Expr AST node.

Note also that methods like QualType::getCVRQualifiersThroughArrayTypes
does not handle properly this situation and obtain the wrong result.

To tell you the truth I don't see many reasons to synthesize array Types
with qualifiers instead to push the qualifiers in the right place, but
probably I'm missing something...

Hello.

We have a doubt regarding qualified array types.

Consider the following program:

struct S {
int a[2];
};

void foo(void) {
const struct S* ps;
const int (*b)[2];
b = &ps->a;
}

as well as its ast dump:

struct S {
int a[2];
};
void foo() (CompoundStmt 0x3c48bf8 <arraytype.c:5:16, line:9:1>
(DeclStmt 0x3c1be20 <line:6:3, col:21>
0x3c1bdd0 "const struct S *ps")
(DeclStmt 0x3c1bf60 <line:7:3, col:20>
0x3c1bf10 "const int (*b)[2]")
(BinaryOperator 0x3c48bc8 <line:8:3, col:12> 'const int (*)[2]' '='
(DeclRefExpr 0x3c1bf80 <col:3> 'const int (*)[2]' Var='b' 0x3c1bf10)
(UnaryOperator 0x3c48ba0 <col:7, col:12> 'int const (*)[2]' prefix '&'
(MemberExpr 0x3c48b30 <col:8, col:12> 'int const[2]' ->a 0x3c1bc10
(DeclRefExpr 0x3c1bfb8 <col:8> 'const struct S *' Var='ps'
0x3c1bdd0)))))

The dump shows a mismatch between the types of the lhs and the rhs of
the assignment expression:
- the lhs has type const int (*)[2]
- the rhs has type int const (*)[2]
that is, in the rhs the const qualifier is applied to the array type,
rather than to the array element type. The two types are anyway detected
as being equivalent as witnessed by the absence of an implicit cast.

Per C99, those are the same type.

To be exact C99 6.7.3.8 says something a little different:

"If the specification of an array type includes any type qualifiers, the
element type is so qualified, not the array type."

So it seems to exclude the existence of qualified array type...

Err, "int const (*)[2]" isn't a qualified array type... it's just an
equivalent way of writing "const int (*)[2]". But yes, the reason
it's printed differently is due to the internal representation.

Ok, so unfortunately we have an internal representation that is
incongruent with C standard. This is related to efficiency issues?

The issue is more with cases like the following:

typedef int foobar[2];
const foobar* x;

If you want to talk about the type of x, you want to print it as
"const foobar*", not "const int (*)[2]".

In terms of putting const qualifiers on the array type itself, it's
mostly just efficiency; it saves creating an extra type, and it
doesn't really make anything much more complicated than we already
have to be able to deal with.

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Why would you need such a type?

Consider this example:

struct S {
volatile int a[2];
};

void foo(void) {
const struct S* ps;
&ps->a;
}

The type of &ps->a has a volatile qualifier attached to builtin int type
and a const qualifier attached to array type.

It seems to me rather obvious that this is an undesiderable situation
for library client that ask a sensible type for an Expr AST node.

Note also that methods like QualType::getCVRQualifiersThroughArrayTypes
does not handle properly this situation and obtain the wrong result.

To tell you the truth I don't see many reasons to synthesize array Types
with qualifiers instead to push the qualifiers in the right place, but
probably I'm missing something...

Assuming the APIs aren't buggy, it should essentially be invisible for
code that isn't digging through QualType internals... but as you
correctly point out, there are bugs. I think the right way to
approach it is to just fix the bugs, though.

-Eli

Hello.

We have a doubt regarding qualified array types.

[...snip...]

In terms of putting const qualifiers on the array type itself, it's
mostly just efficiency; it saves creating an extra type, and it
doesn't really make anything much more complicated than we already
have to be able to deal with.

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Why would you need such a type?

Consider this example:

struct S {
volatile int a[2];
};

void foo(void) {
const struct S* ps;
&ps->a;
}

The type of &ps->a has a volatile qualifier attached to builtin int type
and a const qualifier attached to array type.

It seems to me rather obvious that this is an undesiderable situation
for library client that ask a sensible type for an Expr AST node.

Note also that methods like QualType::getCVRQualifiersThroughArrayTypes
does not handle properly this situation and obtain the wrong result.

To tell you the truth I don't see many reasons to synthesize array Types
with qualifiers instead to push the qualifiers in the right place, but
probably I'm missing something...

Assuming the APIs aren't buggy, it should essentially be invisible for
code that isn't digging through QualType internals...

I am not sure I fully understand the motivations for NOT always pushing
the qualifiers down to the element type.

On the one hand, I really doubt there can actually be a (significant)
efficiency penalty.

On the other hand, having the element-type qualifiers potentially stored
in many places is going to be troublesome for end-user applications. It
would really be of great help if these qualifiers are always found in
their correct place.

Note that canonicalization is often a solution, but it is not _always_
the solution. Sometimes we do not want to canonicalize types, because
expanding all the typedefs reduces readability.

Are you saying that all clang-based applications working at the
source-code level should search and go through all of the
(multidimensional) array-type derivations and explicitly collect all of
the type qualifiers in order to produce a correctly qualified element
type? This sounds rather error-prone.

but as you
correctly point out, there are bugs. I think the right way to
approach it is to just fix the bugs, though.

-Eli

As mentioned above, besides the potential bugs inside clang, it is also
worth considering whether such an implementation choice is going to
increase or decrease the rate of bugs in the applications that are based
on clang.

Anyway, here is some code that is accepted by gcc and rejected by clang
and is related to qualifications of array types.
I am not sure whether or not it is a genuine bug ... but according to my
own reading of 6.7.3p2 and 6.7.3p8 it seems to be legal code.

$ cat restricted_array.c
typedef int* Array[10];
typedef __restrict__ Array RArray;

$ llvm/Debug+Asserts/bin/clang -cc1 restricted_array.c
restricted_array.c:3:9: error: restrict requires a pointer or reference
('Array' (aka 'int *[10]') is invalid)
typedef __restrict__ Array RArray;
~~~~~~~~^~~~~~~~~~~~~~~~~~
1 error generated.

Cheers,
Enea.

Hello.

We have a doubt regarding qualified array types.

[...snip...]

In terms of putting const qualifiers on the array type itself, it's
mostly just efficiency; it saves creating an extra type, and it
doesn't really make anything much more complicated than we already
have to be able to deal with.

Does this means that we might encounter all the possible combinations
(i.e. element with qualified/unqualified type inside nested arrays with
qualified/unqualified type) and that the qualifiers of arrays types and
item type should be merged to get a "normalized" type (not canonical)
where qualifiers are pushed to the item type as standard mandates?

There is a method to get such type?

Why would you need such a type?

Consider this example:

struct S {
volatile int a[2];
};

void foo(void) {
const struct S* ps;
&ps->a;
}

The type of &ps->a has a volatile qualifier attached to builtin int type
and a const qualifier attached to array type.

It seems to me rather obvious that this is an undesiderable situation
for library client that ask a sensible type for an Expr AST node.

Note also that methods like QualType::getCVRQualifiersThroughArrayTypes
does not handle properly this situation and obtain the wrong result.

To tell you the truth I don't see many reasons to synthesize array Types
with qualifiers instead to push the qualifiers in the right place, but
probably I'm missing something...

Assuming the APIs aren't buggy, it should essentially be invisible for
code that isn't digging through QualType internals...

I am not sure I fully understand the motivations for NOT always pushing
the qualifiers down to the element type.

On the one hand, I really doubt there can actually be a (significant)
efficiency penalty.

On the other hand, having the element-type qualifiers potentially stored
in many places is going to be troublesome for end-user applications. It
would really be of great help if these qualifiers are always found in
their correct place.

The primary issue is that if we unconditionally push down all
qualifiers all the way to the element type, we lose "sugar" (typedefs
etc.). So the qualifiers can't always be in the correct place.
Putting them in the correct place some of the time doesn't seem like
it helps... the TypePrinter example is really an edge case because
it's not working with the semantics of the types in question.

Note that canonicalization is often a solution, but it is not _always_
the solution. Sometimes we do not want to canonicalize types, because
expanding all the typedefs reduces readability.

Are you saying that all clang-based applications working at the
source-code level should search and go through all of the
(multidimensional) array-type derivations and explicitly collect all of
the type qualifiers in order to produce a correctly qualified element
type? This sounds rather error-prone.

It's tricky if you have to write the code yourself, but applications
can just call ASTContext::getBaseElementType to get a correctly
qualified element type.

but as you
correctly point out, there are bugs. I think the right way to
approach it is to just fix the bugs, though.

-Eli

As mentioned above, besides the potential bugs inside clang, it is also
worth considering whether such an implementation choice is going to
increase or decrease the rate of bugs in the applications that are based
on clang.

Because of the issues mentioned above, I really don't think it will help.

Anyway, here is some code that is accepted by gcc and rejected by clang
and is related to qualifications of array types.
I am not sure whether or not it is a genuine bug ... but according to my
own reading of 6.7.3p2 and 6.7.3p8 it seems to be legal code.

$ cat restricted_array.c
typedef int* Array[10];
typedef __restrict__ Array RArray;

$ llvm/Debug+Asserts/bin/clang -cc1 restricted_array.c
restricted_array.c:3:9: error: restrict requires a pointer or reference
('Array' (aka 'int *[10]') is invalid)
typedef __restrict__ Array RArray;
~~~~~~~~^~~~~~~~~~~~~~~~~~
1 error generated.

Err, wow... that's pretty wacky. Comeau actually agrees with clang,
but I don't know if that's intentional. Either way, though, the
restrict qualified type never actually exists in the AST, so it's not
really relevant to this discussion.

-Eli