Commit ce64a8b4 by Ian Lance Taylor

compiler, reflect: fix struct field names for embedded aliases

    
    This adds much of https://golang.org/cl/35731 and
    https://golang.org/cl/35732 to the gofrontend code.
    
    This is a step toward updating libgo to the 1.9 release.  The
    gofrontend already supports type aliases, and this is required for
    correct support of type aliases when used as embedded fields.
    
    The change to expressions.cc is to handle the << 1, used for the
    newly renamed offsetAnon field, in the constant context used for type
    descriptor initialization.
    
    Reviewed-on: https://go-review.googlesource.com/62710

From-SVN: r252746
parent ed52163b
8c6d9ff6f60b737d1e96c0dab0b4e67402bf3316 b0a46c2cdb915ddc4a4e401af9ef6eb2bcd4d4ea
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -6044,35 +6044,46 @@ Binary_expression::do_get_backend(Translate_context* context) ...@@ -6044,35 +6044,46 @@ Binary_expression::do_get_backend(Translate_context* context)
{ {
go_assert(left_type->integer_type() != NULL); go_assert(left_type->integer_type() != NULL);
mpz_t bitsval;
int bits = left_type->integer_type()->bits(); int bits = left_type->integer_type()->bits();
mpz_init_set_ui(bitsval, bits);
Bexpression* bits_expr = Numeric_constant nc;
gogo->backend()->integer_constant_expression(right_btype, bitsval); unsigned long ul;
Bexpression* compare = if (!this->right_->numeric_constant_value(&nc)
gogo->backend()->binary_expression(OPERATOR_LT, || nc.to_unsigned_long(&ul) != Numeric_constant::NC_UL_VALID
right, bits_expr, loc); || ul >= static_cast<unsigned long>(bits))
Bexpression* zero_expr =
gogo->backend()->integer_constant_expression(left_btype, zero);
overflow = zero_expr;
Bfunction* bfn = context->function()->func_value()->get_decl();
if (this->op_ == OPERATOR_RSHIFT
&& !left_type->integer_type()->is_unsigned())
{ {
Bexpression* neg_expr = mpz_t bitsval;
gogo->backend()->binary_expression(OPERATOR_LT, left, mpz_init_set_ui(bitsval, bits);
zero_expr, loc); Bexpression* bits_expr =
Bexpression* neg_one_expr = gogo->backend()->integer_constant_expression(right_btype, bitsval);
gogo->backend()->integer_constant_expression(left_btype, neg_one); Bexpression* compare =
overflow = gogo->backend()->conditional_expression(bfn, gogo->backend()->binary_expression(OPERATOR_LT,
btype, neg_expr, right, bits_expr, loc);
neg_one_expr,
zero_expr, loc); Bexpression* zero_expr =
gogo->backend()->integer_constant_expression(left_btype, zero);
overflow = zero_expr;
Bfunction* bfn = context->function()->func_value()->get_decl();
if (this->op_ == OPERATOR_RSHIFT
&& !left_type->integer_type()->is_unsigned())
{
Bexpression* neg_expr =
gogo->backend()->binary_expression(OPERATOR_LT, left,
zero_expr, loc);
Bexpression* neg_one_expr =
gogo->backend()->integer_constant_expression(left_btype,
neg_one);
overflow = gogo->backend()->conditional_expression(bfn,
btype,
neg_expr,
neg_one_expr,
zero_expr,
loc);
}
ret = gogo->backend()->conditional_expression(bfn, btype, compare,
ret, overflow, loc);
mpz_clear(bitsval);
} }
ret = gogo->backend()->conditional_expression(bfn, btype, compare, ret,
overflow, loc);
mpz_clear(bitsval);
} }
// Add checks for division by zero and division overflow as needed. // Add checks for division by zero and division overflow as needed.
......
...@@ -6372,7 +6372,7 @@ Struct_type::make_struct_type_descriptor_type() ...@@ -6372,7 +6372,7 @@ Struct_type::make_struct_type_descriptor_type()
"pkgPath", pointer_string_type, "pkgPath", pointer_string_type,
"typ", ptdt, "typ", ptdt,
"tag", pointer_string_type, "tag", pointer_string_type,
"offset", uintptr_type); "offsetAnon", uintptr_type);
Type* nsf = Type::make_builtin_named_type("structField", sf); Type* nsf = Type::make_builtin_named_type("structField", sf);
Type* slice_type = Type::make_array_type(nsf, NULL); Type* slice_type = Type::make_array_type(nsf, NULL);
...@@ -6429,14 +6429,9 @@ Struct_type::do_type_descriptor(Gogo* gogo, Named_type* name) ...@@ -6429,14 +6429,9 @@ Struct_type::do_type_descriptor(Gogo* gogo, Named_type* name)
Struct_field_list::const_iterator q = f->begin(); Struct_field_list::const_iterator q = f->begin();
go_assert(q->is_field_name("name")); go_assert(q->is_field_name("name"));
if (pf->is_anonymous()) std::string n = Gogo::unpack_hidden_name(pf->field_name());
fvals->push_back(Expression::make_nil(bloc)); Expression* s = Expression::make_string(n, bloc);
else fvals->push_back(Expression::make_unary(OPERATOR_AND, s, bloc));
{
std::string n = Gogo::unpack_hidden_name(pf->field_name());
Expression* s = Expression::make_string(n, bloc);
fvals->push_back(Expression::make_unary(OPERATOR_AND, s, bloc));
}
++q; ++q;
go_assert(q->is_field_name("pkgPath")); go_assert(q->is_field_name("pkgPath"));
...@@ -6469,8 +6464,15 @@ Struct_type::do_type_descriptor(Gogo* gogo, Named_type* name) ...@@ -6469,8 +6464,15 @@ Struct_type::do_type_descriptor(Gogo* gogo, Named_type* name)
} }
++q; ++q;
go_assert(q->is_field_name("offset")); go_assert(q->is_field_name("offsetAnon"));
fvals->push_back(Expression::make_struct_field_offset(this, &*pf)); Type* uintptr_type = Type::lookup_integer_type("uintptr");
Expression* o = Expression::make_struct_field_offset(this, &*pf);
Expression* one = Expression::make_integer_ul(1, uintptr_type, bloc);
o = Expression::make_binary(OPERATOR_LSHIFT, o, one, bloc);
int av = pf->is_anonymous() ? 1 : 0;
Expression* anon = Expression::make_integer_ul(av, uintptr_type, bloc);
o = Expression::make_binary(OPERATOR_OR, o, anon, bloc);
fvals->push_back(o);
Expression* v = Expression::make_struct_composite_literal(element_type, Expression* v = Expression::make_struct_composite_literal(element_type,
fvals, bloc); fvals, bloc);
......
...@@ -4184,50 +4184,58 @@ func TestStructOfExportRules(t *testing.T) { ...@@ -4184,50 +4184,58 @@ func TestStructOfExportRules(t *testing.T) {
f() f()
} }
for i, test := range []struct { tests := []struct {
field StructField field StructField
mustPanic bool mustPanic bool
exported bool exported bool
}{ }{
{ {
field: StructField{Name: "", Type: TypeOf(S1{})}, field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{})},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "", Type: TypeOf((*S1)(nil))}, field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil))},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "", Type: TypeOf(s2{})}, field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{})},
mustPanic: false, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "", Type: TypeOf((*s2)(nil))}, field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil))},
mustPanic: false, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, field: StructField{Name: "Name", Type: nil, PkgPath: ""},
mustPanic: true, mustPanic: true,
exported: true,
}, },
{ {
field: StructField{Name: "", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: ""},
mustPanic: true,
},
{
field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{}), PkgPath: "other/pkg"},
mustPanic: true,
},
{
field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"},
mustPanic: true,
},
{
field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{}), PkgPath: "other/pkg"},
mustPanic: true, mustPanic: true,
exported: true,
}, },
{ {
field: StructField{Name: "", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"},
mustPanic: true,
},
{
field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "S", Type: TypeOf(S1{})}, field: StructField{Name: "S", Type: TypeOf(S1{})},
...@@ -4235,81 +4243,68 @@ func TestStructOfExportRules(t *testing.T) { ...@@ -4235,81 +4243,68 @@ func TestStructOfExportRules(t *testing.T) {
exported: true, exported: true,
}, },
{ {
field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, field: StructField{Name: "S", Type: TypeOf((*S1)(nil))},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "S", Type: TypeOf(s2{})}, field: StructField{Name: "S", Type: TypeOf(s2{})},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, field: StructField{Name: "S", Type: TypeOf((*s2)(nil))},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf(S1{})}, field: StructField{Name: "s", Type: TypeOf(S1{})},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, field: StructField{Name: "s", Type: TypeOf((*S1)(nil))},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf(s2{})}, field: StructField{Name: "s", Type: TypeOf(s2{})},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, field: StructField{Name: "s", Type: TypeOf((*s2)(nil))},
mustPanic: true, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
}, },
{ {
field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
}, },
{ {
field: StructField{Name: "", Type: TypeOf(ΦType{})}, field: StructField{Name: "", Type: TypeOf(ΦType{})},
mustPanic: false, mustPanic: true,
exported: true,
}, },
{ {
field: StructField{Name: "", Type: TypeOf(φType{})}, field: StructField{Name: "", Type: TypeOf(φType{})},
mustPanic: false, mustPanic: true,
exported: false,
}, },
{ {
field: StructField{Name: "Φ", Type: TypeOf(0)}, field: StructField{Name: "Φ", Type: TypeOf(0)},
mustPanic: false, exported: true,
exported: true,
}, },
{ {
field: StructField{Name: "φ", Type: TypeOf(0)}, field: StructField{Name: "φ", Type: TypeOf(0)},
mustPanic: false, exported: false,
exported: false,
}, },
} { }
for i, test := range tests {
testPanic(i, test.mustPanic, func() { testPanic(i, test.mustPanic, func() {
typ := StructOf([]StructField{test.field}) typ := StructOf([]StructField{test.field})
if typ == nil { if typ == nil {
...@@ -4319,7 +4314,7 @@ func TestStructOfExportRules(t *testing.T) { ...@@ -4319,7 +4314,7 @@ func TestStructOfExportRules(t *testing.T) {
field := typ.Field(0) field := typ.Field(0)
n := field.Name n := field.Name
if n == "" { if n == "" {
n = field.Type.Name() panic("field.Name must not be empty")
} }
exported := isExported(n) exported := isExported(n)
if exported != test.exported { if exported != test.exported {
......
...@@ -370,11 +370,19 @@ type sliceType struct { ...@@ -370,11 +370,19 @@ type sliceType struct {
// Struct field // Struct field
type structField struct { type structField struct {
name *string // nil for embedded fields name *string // name is always non-empty
pkgPath *string // nil for exported Names; otherwise import path pkgPath *string // nil for exported Names; otherwise import path
typ *rtype // type of field typ *rtype // type of field
tag *string // nil if no tag tag *string // nil if no tag
offset uintptr // byte offset of field within struct offsetAnon uintptr // byte offset of field<<1 | isAnonymous
}
func (f *structField) offset() uintptr {
return f.offsetAnon >> 1
}
func (f *structField) anon() bool {
return f.offsetAnon&1 != 0
} }
// structType represents a struct type. // structType represents a struct type.
...@@ -880,23 +888,15 @@ func (t *structType) Field(i int) (f StructField) { ...@@ -880,23 +888,15 @@ func (t *structType) Field(i int) (f StructField) {
} }
p := &t.fields[i] p := &t.fields[i]
f.Type = toType(p.typ) f.Type = toType(p.typ)
if p.name != nil { f.Name = *p.name
f.Name = *p.name f.Anonymous = p.anon()
} else {
t := f.Type
if t.Kind() == Ptr {
t = t.Elem()
}
f.Name = t.Name()
f.Anonymous = true
}
if p.pkgPath != nil { if p.pkgPath != nil {
f.PkgPath = *p.pkgPath f.PkgPath = *p.pkgPath
} }
if p.tag != nil { if p.tag != nil {
f.Tag = StructTag(*p.tag) f.Tag = StructTag(*p.tag)
} }
f.Offset = p.offset f.Offset = p.offset()
// NOTE(rsc): This is the only allocation in the interface // NOTE(rsc): This is the only allocation in the interface
// presented by a reflect.Type. It would be nice to avoid, // presented by a reflect.Type. It would be nice to avoid,
...@@ -984,18 +984,15 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel ...@@ -984,18 +984,15 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel
for i := range t.fields { for i := range t.fields {
f := &t.fields[i] f := &t.fields[i]
// Find name and type for field f. // Find name and type for field f.
var fname string fname := *f.name
var ntyp *rtype var ntyp *rtype
if f.name != nil { if f.anon() {
fname = *f.name
} else {
// Anonymous field of type T or *T. // Anonymous field of type T or *T.
// Name taken from type. // Name taken from type.
ntyp = f.typ ntyp = f.typ
if ntyp.Kind() == Ptr { if ntyp.Kind() == Ptr {
ntyp = ntyp.Elem().common() ntyp = ntyp.Elem().common()
} }
fname = ntyp.Name()
} }
// Does it match? // Does it match?
...@@ -1053,13 +1050,12 @@ func (t *structType) FieldByName(name string) (f StructField, present bool) { ...@@ -1053,13 +1050,12 @@ func (t *structType) FieldByName(name string) (f StructField, present bool) {
if name != "" { if name != "" {
for i := range t.fields { for i := range t.fields {
tf := &t.fields[i] tf := &t.fields[i]
if tf.name == nil {
hasAnon = true
continue
}
if *tf.name == name { if *tf.name == name {
return t.Field(i), true return t.Field(i), true
} }
if tf.anon() {
hasAnon = true
}
} }
} }
if !hasAnon { if !hasAnon {
...@@ -1390,7 +1386,7 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool { ...@@ -1390,7 +1386,7 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool {
if cmpTags && tf.tag != vf.tag && (tf.tag == nil || vf.tag == nil || *tf.tag != *vf.tag) { if cmpTags && tf.tag != vf.tag && (tf.tag == nil || vf.tag == nil || *tf.tag != *vf.tag) {
return false return false
} }
if tf.offset != vf.offset { if tf.offsetAnon != vf.offsetAnon {
return false return false
} }
} }
...@@ -1946,11 +1942,10 @@ func StructOf(fields []StructField) Type { ...@@ -1946,11 +1942,10 @@ func StructOf(fields []StructField) Type {
hasPtr = true hasPtr = true
} }
name := ""
// Update string and hash // Update string and hash
name := *f.name
hash = (hash << 1) + ft.hash hash = (hash << 1) + ft.hash
if f.name != nil { if !f.anon() {
name = *f.name
repr = append(repr, (" " + name)...) repr = append(repr, (" " + name)...)
} else { } else {
// Embedded field // Embedded field
...@@ -2009,11 +2004,12 @@ func StructOf(fields []StructField) Type { ...@@ -2009,11 +2004,12 @@ func StructOf(fields []StructField) Type {
comparable = comparable && (ft.equalfn != nil) comparable = comparable && (ft.equalfn != nil)
hashable = hashable && (ft.hashfn != nil) hashable = hashable && (ft.hashfn != nil)
f.offset = align(size, uintptr(ft.fieldAlign)) offset := align(size, uintptr(ft.fieldAlign))
if int8(ft.fieldAlign) > typalign { if int8(ft.fieldAlign) > typalign {
typalign = int8(ft.fieldAlign) typalign = int8(ft.fieldAlign)
} }
size = f.offset + ft.size size = offset + ft.size
f.offsetAnon |= offset << 1
if ft.size == 0 { if ft.size == 0 {
lastzero = size lastzero = size
...@@ -2148,7 +2144,7 @@ func StructOf(fields []StructField) Type { ...@@ -2148,7 +2144,7 @@ func StructOf(fields []StructField) Type {
typ.hashfn = func(p unsafe.Pointer, seed uintptr) uintptr { typ.hashfn = func(p unsafe.Pointer, seed uintptr) uintptr {
o := seed o := seed
for _, ft := range typ.fields { for _, ft := range typ.fields {
pi := unsafe.Pointer(uintptr(p) + ft.offset) pi := unsafe.Pointer(uintptr(p) + ft.offset())
o = ft.typ.hashfn(pi, o) o = ft.typ.hashfn(pi, o)
} }
return o return o
...@@ -2160,8 +2156,8 @@ func StructOf(fields []StructField) Type { ...@@ -2160,8 +2156,8 @@ func StructOf(fields []StructField) Type {
if comparable { if comparable {
typ.equalfn = func(p, q unsafe.Pointer) bool { typ.equalfn = func(p, q unsafe.Pointer) bool {
for _, ft := range typ.fields { for _, ft := range typ.fields {
pi := unsafe.Pointer(uintptr(p) + ft.offset) pi := unsafe.Pointer(uintptr(p) + ft.offset())
qi := unsafe.Pointer(uintptr(q) + ft.offset) qi := unsafe.Pointer(uintptr(q) + ft.offset())
if !ft.typ.equalfn(pi, qi) { if !ft.typ.equalfn(pi, qi) {
return false return false
} }
...@@ -2196,6 +2192,11 @@ func runtimeStructField(field StructField) structField { ...@@ -2196,6 +2192,11 @@ func runtimeStructField(field StructField) structField {
} }
} }
offsetAnon := uintptr(0)
if field.Anonymous {
offsetAnon |= 1
}
var pkgPath *string var pkgPath *string
if field.PkgPath != "" { if field.PkgPath != "" {
s := field.PkgPath s := field.PkgPath
...@@ -2212,11 +2213,11 @@ func runtimeStructField(field StructField) structField { ...@@ -2212,11 +2213,11 @@ func runtimeStructField(field StructField) structField {
} }
return structField{ return structField{
name: name, name: name,
pkgPath: pkgPath, pkgPath: pkgPath,
typ: field.Type.common(), typ: field.Type.common(),
tag: tag, tag: tag,
offset: 0, offsetAnon: offsetAnon,
} }
} }
...@@ -2239,7 +2240,7 @@ func typeptrdata(t *rtype) uintptr { ...@@ -2239,7 +2240,7 @@ func typeptrdata(t *rtype) uintptr {
} }
} }
f := st.fields[field] f := st.fields[field]
return f.offset + f.typ.ptrdata return f.offset() + f.typ.ptrdata
default: default:
panic("reflect.typeptrdata: unexpected type, " + t.String()) panic("reflect.typeptrdata: unexpected type, " + t.String())
...@@ -2513,7 +2514,7 @@ func addTypeBits(bv *bitVector, offset uintptr, t *rtype) { ...@@ -2513,7 +2514,7 @@ func addTypeBits(bv *bitVector, offset uintptr, t *rtype) {
tt := (*structType)(unsafe.Pointer(t)) tt := (*structType)(unsafe.Pointer(t))
for i := range tt.fields { for i := range tt.fields {
f := &tt.fields[i] f := &tt.fields[i]
addTypeBits(bv, offset+f.offset, f.typ) addTypeBits(bv, offset+f.offset(), f.typ)
} }
} }
} }
...@@ -625,7 +625,7 @@ func (v Value) Field(i int) Value { ...@@ -625,7 +625,7 @@ func (v Value) Field(i int) Value {
fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
// Using an unexported field forces flagRO. // Using an unexported field forces flagRO.
if field.pkgPath != nil { if field.pkgPath != nil {
if field.name == nil { if field.anon() {
fl |= flagEmbedRO fl |= flagEmbedRO
} else { } else {
fl |= flagStickyRO fl |= flagStickyRO
...@@ -636,7 +636,7 @@ func (v Value) Field(i int) Value { ...@@ -636,7 +636,7 @@ func (v Value) Field(i int) Value {
// In the former case, we want v.ptr + offset. // In the former case, we want v.ptr + offset.
// In the latter case, we must have field.offset = 0, // In the latter case, we must have field.offset = 0,
// so v.ptr + field.offset is still okay. // so v.ptr + field.offset is still okay.
ptr := unsafe.Pointer(uintptr(v.ptr) + field.offset) ptr := unsafe.Pointer(uintptr(v.ptr) + field.offset())
return Value{typ, ptr, fl} return Value{typ, ptr, fl}
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment