Commit 01ce6e98 authored by darin@apple.com's avatar darin@apple.com

2010-07-09 Darin Adler <darin@apple.com>

        Reviewed by Geoffrey Garen.

        String to number coercion is not spec compliant
        https://bugs.webkit.org/show_bug.cgi?id=31349

        ToNumber should ignore NBSP (\u00a0)
        https://bugs.webkit.org/show_bug.cgi?id=25490

        * runtime/JSGlobalObjectFunctions.cpp:
        (JSC::parseIntOverflow): Added a version that works on UChar.
        * runtime/JSGlobalObjectFunctions.h: Ditto.

        * runtime/UString.cpp:
        (JSC::isInfinity): Added helper functions.
        (JSC::UString::toDouble): Use isStrWhiteSpace instead of
        isSASCIISpace to define what we should skip. Got rid of the
        code that used CString and UTF8String, instead processing the
        UChar of the string directly, except for when we call strtod.
        For strtod, use our own home-grown conversion function that
        does not try to do any UTF-16 processing. Tidied up the logic
        a bit as well.
2010-07-09  Darin Adler  <darin@apple.com>

        Reviewed by Geoffrey Garen.

        String to number coercion is not spec compliant
        https://bugs.webkit.org/show_bug.cgi?id=31349

        * fast/js/ToNumber-expected.txt: Updated to expect more tests to pass.
        * fast/js/parseFloat-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A2-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T1-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T2-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T10-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T3-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T8-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T9-expected.txt: Ditto.
        * fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A6-expected.txt: Ditto.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@63120 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 839b7439
2010-07-09 Darin Adler <darin@apple.com>
Reviewed by Geoffrey Garen.
String to number coercion is not spec compliant
https://bugs.webkit.org/show_bug.cgi?id=31349
ToNumber should ignore NBSP (\u00a0)
https://bugs.webkit.org/show_bug.cgi?id=25490
* runtime/JSGlobalObjectFunctions.cpp:
(JSC::parseIntOverflow): Added a version that works on UChar.
* runtime/JSGlobalObjectFunctions.h: Ditto.
* runtime/UString.cpp:
(JSC::isInfinity): Added helper functions.
(JSC::UString::toDouble): Use isStrWhiteSpace instead of
isSASCIISpace to define what we should skip. Got rid of the
code that used CString and UTF8String, instead processing the
UChar of the string directly, except for when we call strtod.
For strtod, use our own home-grown conversion function that
does not try to do any UTF-16 processing. Tidied up the logic
a bit as well.
2010-07-12 Martin Robinson <mrobinson@igalia.com>
Reviewed by Xan Lopez.
......
......@@ -196,6 +196,28 @@ double parseIntOverflow(const char* s, int length, int radix)
return number;
}
double parseIntOverflow(const UChar* s, int length, int radix)
{
double number = 0.0;
double radixMultiplier = 1.0;
for (const UChar* p = s + length - 1; p >= s; p--) {
if (radixMultiplier == Inf) {
if (*p != '0') {
number = Inf;
break;
}
} else {
int digit = parseDigit(*p, radix);
number += digit * radixMultiplier;
}
radixMultiplier *= radix;
}
return number;
}
static double parseInt(const UString& s, int radix)
{
int length = s.size();
......
/*
* Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
* Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2007 Cameron Zwarich (cwzwarich@uwaterloo.ca)
* Copyright (C) 2007 Maks Orlovich
*
......@@ -53,6 +53,7 @@ namespace JSC {
static const double mantissaOverflowLowerBound = 9007199254740992.0;
double parseIntOverflow(const char*, int length, int radix);
double parseIntOverflow(const UChar*, int length, int radix);
bool isStrWhiteSpace(UChar);
} // namespace JSC
......
......@@ -242,13 +242,33 @@ UChar UString::operator[](unsigned pos) const
return data()[pos];
}
static inline bool isInfinity(double number)
{
return number == Inf || number == -Inf;
}
static bool isInfinity(const UChar* data, const UChar* end)
{
return data + 7 < end
&& data[0] == 'I'
&& data[1] == 'n'
&& data[2] == 'f'
&& data[3] == 'i'
&& data[4] == 'n'
&& data[5] == 'i'
&& data[6] == 't'
&& data[7] == 'y';
}
double UString::toDouble(bool tolerateTrailingJunk, bool tolerateEmptyString) const
{
if (size() == 1) {
unsigned size = this->size();
if (size == 1) {
UChar c = data()[0];
if (isASCIIDigit(c))
return c - '0';
if (isASCIISpace(c) && tolerateEmptyString)
if (isStrWhiteSpace(c) && tolerateEmptyString)
return 0;
return NaN;
}
......@@ -264,77 +284,90 @@ double UString::toDouble(bool tolerateTrailingJunk, bool tolerateEmptyString) co
// need to skip all StrWhiteSpace. The isStrWhiteSpace function does the
// right thing but requires UChar, not char, for its argument.
CString s = UTF8String();
if (s.isNull())
return NaN;
const char* c = s.data();
const UChar* data = this->data();
const UChar* end = data + size;
// skip leading white space
while (isASCIISpace(*c))
c++;
// Skip leading white space.
for (; data < end; ++data) {
if (!isStrWhiteSpace(*data))
break;
}
// empty string ?
if (*c == '\0')
// Empty string.
if (data == end)
return tolerateEmptyString ? 0.0 : NaN;
double d;
// hex number ?
if (*c == '0' && (*(c + 1) == 'x' || *(c + 1) == 'X')) {
const char* firstDigitPosition = c + 2;
c++;
d = 0.0;
while (*(++c)) {
if (*c >= '0' && *c <= '9')
d = d * 16.0 + *c - '0';
else if ((*c >= 'A' && *c <= 'F') || (*c >= 'a' && *c <= 'f'))
d = d * 16.0 + (*c & 0xdf) - 'A' + 10.0;
else
double number;
if (data[0] == '0' && data + 2 < end && (data[1] | 0x20) == 'x' && isASCIIHexDigit(data[2])) {
// Hex number.
data += 2;
const UChar* firstDigitPosition = data;
number = 0;
while (true) {
number = number * 16 + toASCIIHexValue(*data);
++data;
if (data == end)
break;
if (!isASCIIHexDigit(*data))
break;
}
if (d >= mantissaOverflowLowerBound)
d = parseIntOverflow(firstDigitPosition, c - firstDigitPosition, 16);
if (number >= mantissaOverflowLowerBound)
number = parseIntOverflow(firstDigitPosition, data - firstDigitPosition, 16);
} else {
// regular number ?
char* end;
d = WTF::strtod(c, &end);
if ((d != 0.0 || end != c) && d != Inf && d != -Inf) {
c = end;
} else {
double sign = 1.0;
if (*c == '+')
c++;
else if (*c == '-') {
sign = -1.0;
c++;
}
// Decimal number.
// Put into a null-terminated byte buffer.
Vector<char, 32> byteBuffer;
for (const UChar* characters = data; characters < end; ++characters) {
UChar character = *characters;
byteBuffer.append(isASCII(character) ? character : 0);
}
byteBuffer.append(0);
char* byteBufferEnd;
number = WTF::strtod(byteBuffer.data(), &byteBufferEnd);
const UChar* pastNumber = data + (byteBufferEnd - byteBuffer.data());
if ((number || pastNumber != data) && !isInfinity(number))
data = pastNumber;
else {
// We used strtod() to do the conversion. However, strtod() handles
// infinite values slightly differently than JavaScript in that it
// converts the string "inf" with any capitalization to infinity,
// whereas the ECMA spec requires that it be converted to NaN.
if (c[0] == 'I' && c[1] == 'n' && c[2] == 'f' && c[3] == 'i' && c[4] == 'n' && c[5] == 'i' && c[6] == 't' && c[7] == 'y') {
d = sign * Inf;
c += 8;
} else if ((d == Inf || d == -Inf) && *c != 'I' && *c != 'i')
c = end;
double signedInfinity = Inf;
if (data < end) {
if (*data == '+')
data++;
else if (*data == '-') {
signedInfinity = -Inf;
data++;
}
}
if (isInfinity(data, end)) {
number = signedInfinity;
data += 8;
} else if (isInfinity(number) && data < end && (*data | 0x20) != 'i')
data = pastNumber;
else
return NaN;
}
}
// Look for trailing junk.
if (!tolerateTrailingJunk) {
// allow trailing white space
while (isASCIISpace(*c))
c++;
if (c != s.data() + s.length())
d = NaN;
// Allow trailing white space.
for (; data < end; ++data) {
if (!isStrWhiteSpace(*data))
break;
}
if (data != end)
return NaN;
}
return d;
return number;
}
double UString::toDouble(bool tolerateTrailingJunk) const
......
2010-07-09 Darin Adler <darin@apple.com>
Reviewed by Geoffrey Garen.
String to number coercion is not spec compliant
https://bugs.webkit.org/show_bug.cgi?id=31349
* fast/js/ToNumber-expected.txt: Updated to expect more tests to pass.
* fast/js/parseFloat-expected.txt: Ditto.
* fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A2-expected.txt: Ditto.
* fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T1-expected.txt: Ditto.
* fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T2-expected.txt: Ditto.
* fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T10-expected.txt: Ditto.
* fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T3-expected.txt: Ditto.
* fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T8-expected.txt: Ditto.
* fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T9-expected.txt: Ditto.
* fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A6-expected.txt: Ditto.
2010-07-12 Eric Carlson <eric.carlson@apple.com>
Reviewed by Darin Adler.
......@@ -16,7 +16,7 @@ PASS +'x1' is NaN
PASS +'1x' is NaN
PASS +'0x1' is 1
PASS +'1x0' is NaN
FAIL +(nullCharacter + '1') should be NaN. Was 0.
PASS +(nullCharacter + '1') is NaN
PASS +('1' + nullCharacter) is NaN
PASS +('1' + nullCharacter + '1') is NaN
PASS +(nonASCIICharacter + '1') is NaN
......@@ -46,77 +46,77 @@ PASS +'0xAB' is 171
PASS +'1e1' is 10
PASS +'1E1' is 10
PASS +tab is 0
FAIL +nbsp should be 0. Was NaN.
PASS +nbsp is 0
PASS +ff is 0
PASS +vt is 0
PASS +cr is 0
PASS +lf is 0
FAIL +ls should be 0. Was NaN.
FAIL +ps should be 0. Was NaN.
FAIL +oghamSpaceMark should be 0. Was NaN.
FAIL +mongolianVowelSeparator should be 0. Was NaN.
FAIL +enQuad should be 0. Was NaN.
FAIL +emQuad should be 0. Was NaN.
FAIL +enSpace should be 0. Was NaN.
FAIL +emSpace should be 0. Was NaN.
FAIL +threePerEmSpace should be 0. Was NaN.
FAIL +fourPerEmSpace should be 0. Was NaN.
FAIL +sixPerEmSpace should be 0. Was NaN.
FAIL +figureSpace should be 0. Was NaN.
FAIL +punctuationSpace should be 0. Was NaN.
FAIL +thinSpace should be 0. Was NaN.
FAIL +hairSpace should be 0. Was NaN.
FAIL +narrowNoBreakSpace should be 0. Was NaN.
FAIL +mediumMathematicalSpace should be 0. Was NaN.
FAIL +ideographicSpace should be 0. Was NaN.
PASS +ls is 0
PASS +ps is 0
PASS +oghamSpaceMark is 0
PASS +mongolianVowelSeparator is 0
PASS +enQuad is 0
PASS +emQuad is 0
PASS +enSpace is 0
PASS +emSpace is 0
PASS +threePerEmSpace is 0
PASS +fourPerEmSpace is 0
PASS +sixPerEmSpace is 0
PASS +figureSpace is 0
PASS +punctuationSpace is 0
PASS +thinSpace is 0
PASS +hairSpace is 0
PASS +narrowNoBreakSpace is 0
PASS +mediumMathematicalSpace is 0
PASS +ideographicSpace is 0
PASS +(tab + '1') is 1
FAIL +(nbsp + '1') should be 1. Was NaN.
PASS +(nbsp + '1') is 1
PASS +(ff + '1') is 1
PASS +(vt + '1') is 1
PASS +(cr + '1') is 1
PASS +(lf + '1') is 1
FAIL +(ls + '1') should be 1. Was NaN.
FAIL +(ps + '1') should be 1. Was NaN.
FAIL +(oghamSpaceMark + '1') should be 1. Was NaN.
FAIL +(mongolianVowelSeparator + '1') should be 1. Was NaN.
FAIL +(enQuad + '1') should be 1. Was NaN.
FAIL +(emQuad + '1') should be 1. Was NaN.
FAIL +(enSpace + '1') should be 1. Was NaN.
FAIL +(emSpace + '1') should be 1. Was NaN.
FAIL +(threePerEmSpace + '1') should be 1. Was NaN.
FAIL +(fourPerEmSpace + '1') should be 1. Was NaN.
FAIL +(sixPerEmSpace + '1') should be 1. Was NaN.
FAIL +(figureSpace + '1') should be 1. Was NaN.
FAIL +(punctuationSpace + '1') should be 1. Was NaN.
FAIL +(thinSpace + '1') should be 1. Was NaN.
FAIL +(hairSpace + '1') should be 1. Was NaN.
FAIL +(narrowNoBreakSpace + '1') should be 1. Was NaN.
FAIL +(mediumMathematicalSpace + '1') should be 1. Was NaN.
FAIL +(ideographicSpace + '1') should be 1. Was NaN.
PASS +(ls + '1') is 1
PASS +(ps + '1') is 1
PASS +(oghamSpaceMark + '1') is 1
PASS +(mongolianVowelSeparator + '1') is 1
PASS +(enQuad + '1') is 1
PASS +(emQuad + '1') is 1
PASS +(enSpace + '1') is 1
PASS +(emSpace + '1') is 1
PASS +(threePerEmSpace + '1') is 1
PASS +(fourPerEmSpace + '1') is 1
PASS +(sixPerEmSpace + '1') is 1
PASS +(figureSpace + '1') is 1
PASS +(punctuationSpace + '1') is 1
PASS +(thinSpace + '1') is 1
PASS +(hairSpace + '1') is 1
PASS +(narrowNoBreakSpace + '1') is 1
PASS +(mediumMathematicalSpace + '1') is 1
PASS +(ideographicSpace + '1') is 1
PASS +('1' + tab) is 1
FAIL +('1' + nbsp) should be 1. Was NaN.
PASS +('1' + nbsp) is 1
PASS +('1' + ff) is 1
PASS +('1' + vt) is 1
PASS +('1' + cr) is 1
PASS +('1' + lf) is 1
FAIL +('1' + ls) should be 1. Was NaN.
FAIL +('1' + ps) should be 1. Was NaN.
FAIL +('1' + oghamSpaceMark) should be 1. Was NaN.
FAIL +('1' + mongolianVowelSeparator) should be 1. Was NaN.
FAIL +('1' + enQuad) should be 1. Was NaN.
FAIL +('1' + emQuad) should be 1. Was NaN.
FAIL +('1' + enSpace) should be 1. Was NaN.
FAIL +('1' + emSpace) should be 1. Was NaN.
FAIL +('1' + threePerEmSpace) should be 1. Was NaN.
FAIL +('1' + fourPerEmSpace) should be 1. Was NaN.
FAIL +('1' + sixPerEmSpace) should be 1. Was NaN.
FAIL +('1' + figureSpace) should be 1. Was NaN.
FAIL +('1' + punctuationSpace) should be 1. Was NaN.
FAIL +('1' + thinSpace) should be 1. Was NaN.
FAIL +('1' + hairSpace) should be 1. Was NaN.
FAIL +('1' + narrowNoBreakSpace) should be 1. Was NaN.
FAIL +('1' + mediumMathematicalSpace) should be 1. Was NaN.
FAIL +('1' + ideographicSpace) should be 1. Was NaN.
PASS +('1' + ls) is 1
PASS +('1' + ps) is 1
PASS +('1' + oghamSpaceMark) is 1
PASS +('1' + mongolianVowelSeparator) is 1
PASS +('1' + enQuad) is 1
PASS +('1' + emQuad) is 1
PASS +('1' + enSpace) is 1
PASS +('1' + emSpace) is 1
PASS +('1' + threePerEmSpace) is 1
PASS +('1' + fourPerEmSpace) is 1
PASS +('1' + sixPerEmSpace) is 1
PASS +('1' + figureSpace) is 1
PASS +('1' + punctuationSpace) is 1
PASS +('1' + thinSpace) is 1
PASS +('1' + hairSpace) is 1
PASS +('1' + narrowNoBreakSpace) is 1
PASS +('1' + mediumMathematicalSpace) is 1
PASS +('1' + ideographicSpace) is 1
PASS +('1' + tab + '1') is NaN
PASS +('1' + nbsp + '1') is NaN
PASS +('1' + ff + '1') is NaN
......
......@@ -21,32 +21,32 @@ PASS parseFloat('2.3x') is 2.3
PASS parseFloat('0x2') is 0
PASS parseFloat('1' + nonASCIINonSpaceCharacter) is 1
PASS parseFloat(nonASCIINonSpaceCharacter + '1') is NaN
FAIL parseFloat('1' + illegalUTF16Sequence) should be 1. Was NaN.
PASS parseFloat('1' + illegalUTF16Sequence) is 1
PASS parseFloat(illegalUTF16Sequence + '1') is NaN
PASS parseFloat(tab + '1') is 1
FAIL parseFloat(nbsp + '1') should be 1. Was NaN.
PASS parseFloat(nbsp + '1') is 1
PASS parseFloat(ff + '1') is 1
PASS parseFloat(vt + '1') is 1
PASS parseFloat(cr + '1') is 1
PASS parseFloat(lf + '1') is 1
FAIL parseFloat(ls + '1') should be 1. Was NaN.
FAIL parseFloat(ps + '1') should be 1. Was NaN.
FAIL parseFloat(oghamSpaceMark + '1') should be 1. Was NaN.
FAIL parseFloat(mongolianVowelSeparator + '1') should be 1. Was NaN.
FAIL parseFloat(enQuad + '1') should be 1. Was NaN.
FAIL parseFloat(emQuad + '1') should be 1. Was NaN.
FAIL parseFloat(enSpace + '1') should be 1. Was NaN.
FAIL parseFloat(emSpace + '1') should be 1. Was NaN.
FAIL parseFloat(threePerEmSpace + '1') should be 1. Was NaN.
FAIL parseFloat(fourPerEmSpace + '1') should be 1. Was NaN.
FAIL parseFloat(sixPerEmSpace + '1') should be 1. Was NaN.
FAIL parseFloat(figureSpace + '1') should be 1. Was NaN.
FAIL parseFloat(punctuationSpace + '1') should be 1. Was NaN.
FAIL parseFloat(thinSpace + '1') should be 1. Was NaN.
FAIL parseFloat(hairSpace + '1') should be 1. Was NaN.
FAIL parseFloat(narrowNoBreakSpace + '1') should be 1. Was NaN.
FAIL parseFloat(mediumMathematicalSpace + '1') should be 1. Was NaN.
FAIL parseFloat(ideographicSpace + '1') should be 1. Was NaN.
PASS parseFloat(ls + '1') is 1
PASS parseFloat(ps + '1') is 1
PASS parseFloat(oghamSpaceMark + '1') is 1
PASS parseFloat(mongolianVowelSeparator + '1') is 1
PASS parseFloat(enQuad + '1') is 1
PASS parseFloat(emQuad + '1') is 1
PASS parseFloat(enSpace + '1') is 1
PASS parseFloat(emSpace + '1') is 1
PASS parseFloat(threePerEmSpace + '1') is 1
PASS parseFloat(fourPerEmSpace + '1') is 1
PASS parseFloat(sixPerEmSpace + '1') is 1
PASS parseFloat(figureSpace + '1') is 1
PASS parseFloat(punctuationSpace + '1') is 1
PASS parseFloat(thinSpace + '1') is 1
PASS parseFloat(hairSpace + '1') is 1
PASS parseFloat(narrowNoBreakSpace + '1') is 1
PASS parseFloat(mediumMathematicalSpace + '1') is 1
PASS parseFloat(ideographicSpace + '1') is 1
PASS successfullyParsed is true
TEST COMPLETE
......
S9.3.1_A2
FAIL SputnikError: #1.1: Number("\u0009\u000C\u0020\u00A0\u000B\u000A\u000D\u2028\u2029\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000") === 0. Actual: NaN
PASS
TEST COMPLETE
S9.3.1_A3_T1
FAIL SputnikError: #1: Number("\u0009\u000C\u0020\u00A0\u000B\u000A\u000D\u2028\u2029\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000") === Number("")
PASS
TEST COMPLETE
S9.3.1_A3_T2
FAIL SputnikError: #1: Number("\u0009\u000C\u0020\u00A0\u000B"+"\u000A\u000D\u2028\u2029\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000") === Number("")
PASS
TEST COMPLETE
S15.1.2.3_A2_T3
FAIL SputnikError: #1: parseFloat("\u00A01.1") === parseFloat("1.1"). Actual: NaN
PASS
TEST COMPLETE
S15.1.2.3_A2_T8
FAIL SputnikError: #1: parseFloat("\u20281.1") === parseFloat("1.1"). Actual: NaN
PASS
TEST COMPLETE
S15.1.2.3_A2_T9
FAIL SputnikError: #1: parseFloat("\u20291.1") === parseFloat("1.1"). Actual: NaN
PASS
TEST COMPLETE
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