GCC aliasing rules: more aggressive than C99?

2010-01-02 Thread Joshua Haberman
The aliasing policies that GCC implements seem to be more strict than
what is in the C99 standard.  I am wondering if this is true or whether
I am mistaken (I am not an expert on the standard, so the latter is
definitely possible).

The relevant text is:

  An object shall have its stored value accessed only by an lvalue
  expression that has one of the following types:

  * a type compatible with the effective type of the object,
  [...]
  * an aggregate or union type that includes one of the aforementioned
types among its members (including, recursively, a member of a
subaggregate or contained union), or

To me this allows the following:

  int i;
  union u { int x; } *pu = (union u*)&i;
  printf("%d\n", pu->x);

In this example, the object "i", which is of type "int", is having its
stored value accessed by an lvalue expression of type "union u", which
includes the type "int" among its members.

I have seen other articles that interpret the standard in this way.
See section "Casting through a union (2)" from this article, which
claims that casts of this sort are legal and that GCC's warnings
against them are false positives:
  
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

However, this appears to be contrary to GCC's documentation.  From the
manpage:

  Similarly, access by taking the address, casting the resulting
  pointer and dereferencing the result has undefined behavior, even
  if the cast uses a union type, e.g.:

  int f() {
double d = 3.0; 
return ((union a_union *) &d)->i;
  }

I have also been able to experimentally verify that GCC will mis-compile
this fragment if we expect the behavior the standard specifies:
  int g;
  struct A { int x; };
  int foo(struct A *a) {
if(g) a->x = 5;
return g;
  }

With GCC 4.3.3 -O3 on x86-64 (Ubuntu), g is only loaded once:

 :
   0:   8b 05 00 00 00 00   moveax,DWORD PTR [rip+0x0]# 6 

   6:   85 c0   test   eax,eax
   8:   74 06   je 10 
   a:   c7 07 05 00 00 00   movDWORD PTR [rdi],0x5
  10:   f3 c3   repz ret

But this is incorrect if foo() was called as:
  
  foo((struct A*)&g);

Here is another example:
  
  struct A { int x; };
  struct B { int x; }; 
  int foo(struct A *a, struct B *b) { 
if(a->x) b->x = 5;
return a->x;
  }

When I compile this, a->x is only loaded once, even though foo()
could have been called like this:
  
  int i;
  foo((struct A*)&i, (struct B*)&i);

>From this I conclude that GCC diverges from the standard, in that it does not
allow casts of this sort.  In one sense this is good (because the policy GCC
implements is more aggressive, and yet still reasonable) but on the other hand
it means (if I am not mistaken) that GCC will incorrectly optimize strictly
conforming programs.

Clarifications are most welcome!

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-03 Thread Joshua Haberman
Richard Guenther  gmail.com> writes:
> On Sun, Jan 3, 2010 at 6:46 AM, Joshua Haberman  
gmail.com> wrote:
> > The aliasing policies that GCC implements seem to be more strict than
> > what is in the C99 standard.  I am wondering if this is true or whether
> > I am mistaken (I am not an expert on the standard, so the latter is
> > definitely possible).
> >
> > The relevant text is:
> >
> >  An object shall have its stored value accessed only by an lvalue
> >  expression that has one of the following types:
> >
> >  * a type compatible with the effective type of the object,
> >  [...]
> >  * an aggregate or union type that includes one of the aforementioned
> >types among its members (including, recursively, a member of a
> >subaggregate or contained union), or 
>
> Literally interpreting this sentence the way you do removes nearly all
> advantages of type-based aliasing that you have when dealing with
> disambiguating a pointer dereference vs. an object reference
> and thus cannot be the desired interpretation (and thus we do not allow this).

Thank you for the information.  I am very interested in distilling this
issue into a concise and easy to understand guideline that C and C++
programmers can use to determine whether they are following the rules
correctly or not, especially since the warnings are not perfect.  The
GCC manpage gives a basic rule:
  
  In particular, an object of one type is assumed never to reside at the
  same address as an object of a different type, unless the types are
  almost the same.  For example, an "unsigned int" can alias an "int",
  but not a "void*" or a "double".  A character type may alias any other
  type.
  
However, this explanation does not address how the rule applies to
aggregates (structures and arrays) and unions.  Here is my attempt;
please correct anything that looks wrong.

The best way I have had this explained to me so far is that
dereferencing "upcasted" pointers is ok, but "downcasted" pointers not.
For the purposes of this explanation only, we define "upcasts" and
"downcasts" as:

  struct A { int x; } a;
  int i;

  int *pi = &a.x;  // upcast
  int foo = *pi;   // ok

  struct A *pa = (struct A*)&i;  // downcast
  int bar = pa->x;// NOT ok
  struct A a2 = *pa;  // NOT ok

A distinguishing feature of the downcast is that it requires an actual
cast.  So in general, casts from one pointer type to another indicate
a likely problem.  Pointer casts *can* be valid, but only if you know
that the object was previously written as the casted-to type:

  struct A { int x; } a;
  int i;

  int *pi = &a.x;  // upcast
  // this downcast is just "undoing" the previous upcast.
  struct A *pa = (struct A*)&i;
  int foo = pa->x;  // ok

This is why perfect warnings about this issue are not possible; if we
see a downcast in isolation, we do not know if it is undoing a previous
upcast or not.  Only a tool like valgrind could check this perfectly, by
observing reads and writes at runtime and checking the types of pointers
that were used to perform the read/write.

It is possible in C (not C++) to run into trouble even without pointer
casts, since void* can assign to any pointer type without a cast:

  int i;
  void *voidp = &i;
  // Effective downcast.
  struct A *pa = voidp;
  int foo = pa->x;  // NOT ok

But since chars can alias anything, it is always allowed to read or
write an object's representation via char*.

  int i;
  char ch = *(char*)&i;  // ok

  char charray[sizeof(long)] = {...};
  long l = *(long*)charray;  // ok

This does not mean that casts to/from char* are always safe, for the
same reason that we have to watch out for void*: the object may have
previously been written as a different type.

Besides observing the upcast/downcast rule, the other major rule is that
pointers to union members may only be dereferenced for the *active*
union member, which can only be set by using the union directly.

  union U {
int i;
long l;
  } u;
  int *pi = &u.i;
  long *pl = &u.l;

  u.i = 5;
  int foo = *pi;   // ok, u.i is the active member.
  long bar = *pl;  // NOT ok, u.l is not the active member.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-03 Thread Joshua Haberman
Andrew Haley  redhat.com> writes:
> On 01/03/2010 10:53 AM, Richard Guenther wrote:
> > GCC follows its own documentation here, not some random
> > websites and maybe not the strict reading of the standard.
>
> GCC is compatible with C99 in this regard.

I do not believe this is true.  Your argument that GCC complies with C99
(which you moved to gcc-help@) is based on the argument that these are 
not compatible types:

  union u { int x; }
  int x;

However, I did not claim that they are compatible types, nor does my
argument rely on them being compatible types.  Rather, my argument is
based on section 6.5, paragraph 7 of C99, which I quoted, which 
specifies the circumstances under which an object may or may not be
aliased.  The case of compatible types is one case, but not the only 
case, in which values may be aliased according to the standard.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-03 Thread Joshua Haberman
Patrick Horgan  yahoo.com> writes:
> Since it would be hard to read this any other way, it seems then you-
> maintain that you found a bug in the standard, has it been brought up to-
> the committee?  The latest draft I see still has that wording.  Doesn't-
> seem to be on the radar for C1x.

I strongly agree that the standard should be brought into line with
common practice, and that if this is not currently on the committee's
radar someone with connections should raise the issue to them.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-03 Thread Joshua Haberman
Richard Guenther  gmail.com> writes:
> On Sun, Jan 3, 2010 at 10:55 PM, Joshua Haberman  
gmail.com> wrote:
> > This is why perfect warnings about this issue are not possible; if we
> > see a downcast in isolation, we do not know if it is undoing a previous
> > upcast or not.  Only a tool like valgrind could check this perfectly, by
> > observing reads and writes at runtime and checking the types of pointers
> > that were used to perform the read/write.
>
> Correct (though valgrind operates at a too low level to know access types).

Do the DWARF debugging symbols have enough information to determine the
pointer type that was used for each load/store?

> >  char charray[sizeof(long)] = {...};
> >  long l = *(long*)charray;  // ok
>
> not correct ;)  (the lvalue has to be of character type, yours is of
> type 'long' - the type of the actual object does not matter)

I see -- good catch.  To make it valid, I suppose "memcpy" could be used
instead.

> Correct.  C++ has the notion of dynamic types, so with C++
>
> int i;
> *(foat *)&i = 0.0;
> float f = *(float *)&i;
>-
> is ok (well - it's ok with a placement new, but a pointer cast is all
> GCC sees here).  The store changes the dynamic type of the
> memory stored to and thus further reads are only valid using
> the same type.  GCC implements this also for C, but only starting
> with GCC 4.5.

C++'s notion of a "dynamic type" seems to be the same as what C99 calls
the "effective type."  See C99, Section 6.5, paragraph 6.  Also see
Defect Report #236 where the issue is raised, though the committee's 
reasoning does not make sense to me:
  http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_236.htm

(Thanks to Daniel Berlin for pointing me to the DR).

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-03 Thread Joshua Haberman
By the way, here is one case I tested where I was surprised GCC was not
more aggressive:

  extern void bar();
  int foo(int *i) {
if(*i) bar();
return *i;
  }

With GCC 4.4.1 -O3 (Ubuntu, x86-64) this reloads *i if bar is called.  I
suppose you have to allow that either "i" or "*i" is accessible as a
global.  But this is a pretty big bummer, because it means that any
function call forces reloads of any data that was read from a pointer.
"restrict" doesn't help here, nor does "const".  The only way to prevent
these reloads is to manually read the data into stack variable(s):

  extern void bar();
  int foo(int *i) {
int i_val = *i;
if(i_val) bar();
return i_val;
  }

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-04 Thread Joshua Haberman
Andrew Haley  redhat.com> writes:
> On 01/03/2010 10:14 PM, Joshua Haberman wrote:
> > Andrew Haley  redhat.com> writes:
> "6.3.2.3
>
> "A pointer to an object or incomplete type may be converted to a
> pointer to a different object or incomplete type. If the resulting
> pointer is not correctly aligned for the pointed-to type, the
> behavior is undefined. Otherwise, when converted back again, the
> result shall compare equal to the original pointer."
>
> This is *all* you are allowed to do with the converted pointer.  You
> may not dereference it.

The text you quoted does not contain any "shall not" language about
dereferencing, so this conclusion does not follow.

> [Section 6.3.2.3] is the core rule that governs C's aliasing.

Section 6.5 paragraph 7 contains this footnote:

  The intent of this list is to specify those circumstances in which an
  object may or may not be aliased.

I am not sure why you discard the significance of this section.  Also
under your interpretation memcpy(&some_int, ..., ...) is illegal,
because memcpy() will write to the int's storage with a pointer type
other than int.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-04 Thread Joshua Haberman
Erik Trulsson  student.uu.se> writes:
> On Mon, Jan 04, 2010 at 08:17:00PM +, Joshua Haberman wrote:
> > The text you quoted does not contain any "shall not" language about
> > dereferencing, so this conclusion does not follow.
>
> It doesn't have to use any "shall not" language.  If the standard does not
> say that any particular action is allowed or otherwise defines what it
> does, then that action implicitly has undefined behaviour.

Section 6.5 does define circumstances under which converted pointers may
be dereferenced.  Section 6.3.2.3 does not include any language
prohibiting it, so it does not support the assertion it was quoted to
support, and it is irrelevant in the context of this discussion.

> > > [Section 6.3.2.3] is the core rule that governs C's aliasing.
> >
> > Section 6.5 paragraph 7 contains this footnote:
> >
> >   The intent of this list is to specify those circumstances in which an
> >   object may or may not be aliased.
> >
> > I am not sure why you discard the significance of this section.  Also
> > under your interpretation memcpy(&some_int, ..., ...) is illegal,
> > because memcpy() will write to the int's storage with a pointer type
> > other than int.
>
> Your conclusion does not follow since the standard does not say what (if
> any) pointer type memcpy() will use internally.  It is not even necessary
> that memcpy() is implemented in C.

It says that it will copy characters.  More importantly, you are still 
ignoring section 6.5 without saying why.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-05 Thread Joshua Haberman
Robert Dewar  adacore.com> writes:
> In any case the gcc interpretation is clearly what's
> intended in my view, so if it can be argued that the
> standard is inconsistent with this interpretation (I
> am unconvinced that this burden has been met), then
> the conclusion is to add a clarification to the
> standard, not to modify the gcc behavior.

I agree with you completely on this point.  My position throughout this
thread has been that GCC should keep its existing type-based aliasing
behavior.  I think the GCC policy represents a good trade-off between a
policy that is not aggressive enough and a policy that is too hard to
follow.

In my view the C99 standard quite clearly specifies a policy that is
less aggressive than what GCC implements.  I think it's hard to come to
any other conclusion; section 6.5 paragraph 7 contains a footnote
explicitly stating that the intention is to specify conditions under
which values may be aliased, and one of the rules allows for an aliasing
condition that GCC does not respect.  I have not heard any credible 
rebuttal to this point, or any explanation of what 6.5 paragraph 7 
*does* mean, if it is not intended to specify aliasing rules.

I am guessing that the committee didn't have enough practical experience
with aliasing policies to come to Richard's conclusion that the
standard's policy "removes nearly all advantages of type-based aliasing
that you have when dealing with disambiguating a pointer dereference vs.
an object reference."  In this sense I think this is a bug in the 
standard, and I hope the committee will take it up in C1x.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-05 Thread Joshua Haberman
Andrew Haley  redhat.com> writes:
> but
>
>  (union u*)&i
>
> is not a legal lvalue expression because the dereference is undefined
> behaviour.

Your example does not contain a dereference.

> You may only dereference a pointer as permitted by 6.3.2.3.

6.3.2.3 does not mention dereferencing at all; it only addresses pointer
conversion.  Dereferencing is defined in 6.5.3.2.

But let me ask you this.  What do you think 6.5 paragraph 7 means?  For
example, are you also of the opinion that this is illegal?

  int i;
  unsigned int *pui = (unsigned int*)&i;
  unsigned int ui = *pui;

I believe that GCC allows this (Richard can correct me if I am wrong),
but under your interpretation this would be illegal because "int" and
"unsigned int" are not compatible types.  The only thing that makes this
legal is 6.5 paragraph 7, where one of the allowed aliasing scenarios
is:

  a type that is the signed or unsigned type corresponding to the
  effective type of the object

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-05 Thread Joshua Haberman
Erik Trulsson  student.uu.se> writes:
> On Sun, Jan 03, 2010 at 05:46:48AM +, Joshua Haberman wrote:
> > The aliasing policies that GCC implements seem to be more strict than
> > what is in the C99 standard.  I am wondering if this is true or whether
> > I am mistaken (I am not an expert on the standard, so the latter is
> > definitely possible).
> >-
> > The relevant text is:
> >-
> >   An object shall have its stored value accessed only by an lvalue
> >   expression that has one of the following types:
> >-
> >   * a type compatible with the effective type of the object,
> >   [...]
> >   * an aggregate or union type that includes one of the aforementioned
> > types among its members (including, recursively, a member of a
> > subaggregate or contained union), or
> >-
> > To me this allows the following:
> >-
> >   int i;
> >   union u { int x; } *pu = (union u*)&i;
> >   printf("%d\n", pu->x);
> >-
> > In this example, the object "i", which is of type "int", is having its
> > stored value accessed by an lvalue expression of type "union u", which
> > includes the type "int" among its members.
>-
> Even with your interpretation of the C99 standard that example would be
> allowed only if  '*pu' is a valid lvalue of type  'union u'.  (Since pu->x
> is equivalent to (*pu).x)
>-
> First of all the conversion  (union u*)&i is valid only if the alignment
> of 'i' is suitable for an object of type 'union u'.  Lets assume that is the
> case. (Otherwise just making that conversion would result in undefined
> behaviour.)  (See 6.3.2.3 clause 7.)

This is true.  You could get around this particular point by saying:

  int *i = malloc(sizeof(*i));
  *i = 5;
  union u { int x; } *pu = (union u*)i;
  printf("%d\n", pu->x);

...since the return from malloc() is guaranteed to be suitably aligned for
any object (7.20.3).  But your point is taken.

> There is however no guarantee that the conversion yields a valid "pointer to
> union u".  If not then dereferencing it (with the expression '*pu') has
> undefined behaviour. (See 6.5.3.2 clause 4)

I think this is a bit of a stretch.  It is true that 6.5.3.2 says that
dereferencing invalid values has undefined behavior.  But if you are
saying that the standard has to explicitly say that a pointer conversion
will not result in an invalid value (even when suitably aligned), then
the following is also undefined:

  int i;
  unsigned int *pui = (unsigned int*)&i;
  unsigned int ui = *pui;

Andrew cited 6.3.2.3p2 as support for why this is defined, but that
paragraph deals with qualifiers (const, volatile, and restrict).
"unsigned" is not a qualifier.  There is no part of the standard that
guarantees that a pointer conversion from "int*" to "unsigned int*" will
not result in an invalid value.

> So your example contains undefined behaviour even without considering the
> parts of 6.5 clause 7 that you quoted.
>-
> Moreover I think you are misinterpreting 6.5 clause 7 (which I concede is
> fairly easy since it is not quite as unambiguous as one could wish).
> I believe that paragraph should not be interpreted as automatically allowing
> all accesses that correspond to one of the sorts listed.  Rather it should
> be interpreted as saying that if an access is not included in that list then
> it is not allowed, but even if it is included in that list there could be
> other reasons why it is not allowed.  (I.e.  just as the attached footnote
> suggests it is a list of what types of aliasing are allowed, not of which
> pointers may be dereferenced.)

Interesting.  I think it is plausible that this is what the committee
intended.  It like the committee wanted to give a heads-up to
implementations that pointers to primitive types can alias pointers to
those same types within unions and aggregates, just as a result of
taking the address of a member.

In any case I definitely agree that this could be clarified, and I hope the
standards committee will take this up for C1x.

Thank you for your thoughtful analysis.

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-06 Thread Joshua Haberman
Richard Guenther  gmail.com> writes:
> On Wed, Jan 6, 2010 at 7:20 PM, Nick Stoughton  msbit.com> 
wrote:
> > The C1x timetable has us finishing the draft for an initial ballot this
> > summer (the April Florence meeting being the last chance to submit new
> > material). The best expert I know on the type based aliasing stuff is
> > Clark Nelson at Intel (clark.nelson  intel.com). We did spend a
> > considerable amount of time at the recent Santa Cruz meeting discussing
> > this subject ... see N1409 and N1422 (the minutes including a summary of
> > the discussion on N1409).
> > http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1409.htm
> >
> > http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1422.pdf
> [...]
> I have no idea on how to raise this issue with the std body, somebody
> else might and can pass on the above example please.

In the notes that Nick referenced it says:

  Is there  anybody that thinks the rules are clear enough?  No one is
  really able to interpret them.  So it seems that they are not
  clear enough.  The problem is how to state them.

  [...]

  ACTION: Clark to work on a clearer formulation of what the rules are,
  re: N1409.

So it wounds like Clark is already working on this.  We should just write
to him and point him to this thread.  I can do this.

With regard to a clearer way to state the rules: I think the rules would
be easier to understand if the normative part only addressed the ways in
which object aliases could be *created*.  For example, the rules could
state that:

  struct A { int x; } a;
  int *pi = &a.x;

...is a valid way to create an int* that aliases a struct member, whereas:

  int i;
  struct A { int x; } *a = (struct u*)&i;

...is not.  Then an informative section could address the consequences of
those rules with respect to the assumptions you can make when optimizing
a function in isolation:

  type3 x;
  void foo(type1 *a, type2 *b) {
// can a and b alias each other?  can either of them alias x?
  }

The root of my confusion at least was thinking that the standard was
describing legal ways aliases could be created, but it was actually
describing what aliases could come to exist through other processes.

Since the rules governing how aliases may be created are simpler, it
makes sense to me to have the normative rules about alising describe the
creation of aliases, and then let compilers implement whatever
optimizations they can dream up that still respect those rules.

I also think the aliasing rules should more directly address the issue
raised in DR#236:
  http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_236.htm

Josh



Re: GCC aliasing rules: more aggressive than C99?

2010-01-06 Thread Joshua Haberman
Erik Trulsson  student.uu.se> writes:
> >   int i;
> >   unsigned int *pui = (unsigned int*)&i;
> >   unsigned int ui = *pui;
>
> (First I will assume that 'i' will be assigned some value, to make sure it
> does not contain a trap-representation, or the assignment to 'ui' would have
> undefined behaviour.)
>
> I think 6.2.5 clause 27 is very relevant for this. It says that 'pointer to
> int' and 'pointer to union' do not need to have the same representation as
> each other.  It also seems that 'pointer to int' and 'pointer to unsigned
> int' do not need to have the same representation requirements (at least I
> cannot find anything that says that signed and unsigned variants are
> compatible types.) (Which I must admit comes as a bit of a surprise to me.)
>
> So, yes, that example does technically seem to be undefined (but I don't
> know of any real-world implementation where it would not work as expected.)

I am wondering how, under this interpretation, an "int" and "unsigned
int" could ever alias each other, as 6.5p7 says they can.  I believe now
(especially after the notes Nick pointed us to) that your interpretation
of 6.5p7 is the intended one.  But if that is the case, then we should
expect to find some legal way in which "int" and "unsigned int" could
come to be aliased.  With your interpretation about pointer conversions,
I don't see how this could be.

Josh



useless stores generated when returning a struct -- bug?

2010-05-18 Thread Joshua Haberman
I have a case where I think useless stores are being generated, but I
want to be sure before I file a bug.  This is with gcc 4.4.3 on Ubuntu
10.04, x86-64.

I have been experimenting with returning structs from functions instead
of passing pointers to "out" parameters.  This seems like it should be
more optimizer-friendly because you can avoid taking addresses of local
variables, which prevents them from possibly being aliased.

However in this test case, gcc is generating four stores that appear to
be completely useless:

#include 

struct twoints { uint64_t a, b; } foo();
void bar(uint64_t a, uint64_t b);

void func() {
  struct twoints s = foo();
  bar(s.a, s.b);
}

$ gcc -Wall -c -o testbad.o -msse2 -O3 -fomit-frame-pointer testbad.c-
$ objdump -d -r -M intel testbad.o

testbad.o: file format elf64-x86-64


Disassembly of section .text:

 :
   0:   48 83 ec 28 subrsp,0x28
   4:   31 c0   xoreax,eax
   6:   e8 00 00 00 00  call   b 
7: R_X86_64_PC32foo-0x4
   b:   48 89 04 24 movQWORD PTR [rsp],rax
   f:   48 89 54 24 08  movQWORD PTR [rsp+0x8],rdx
  14:   48 89 d6movrsi,rdx
  17:   48 89 44 24 10  movQWORD PTR [rsp+0x10],rax
  1c:   48 89 54 24 18  movQWORD PTR [rsp+0x18],rdx
  21:   48 89 c7movrdi,rax
  24:   48 83 c4 28 addrsp,0x28
  28:   e9 00 00 00 00  jmp2d 
29: R_X86_64_PC32   bar-0x4

Why is it storing rax and rdx to the stack twice?  These stores are never
used AFAICS.

Josh



Re: useless stores generated when returning a struct -- bug?

2010-05-18 Thread Joshua Haberman
Ian Lance Taylor  google.com> writes:
 
> Joshua Haberman  gmail.com> writes:
> 
> > I have a case where I think useless stores are being generated, but I
> > want to be sure before I file a bug.  This is with gcc 4.4.3 on Ubuntu
> > 10.04, x86-64.
> 
> I concur that this is a missed optimization bug.

Thanks Ian.  I'll file it as a bug.

Josh