Commit a87bb3a0 authored by cutealien's avatar cutealien

- Fix a bunch of off-by one errors in irr::core::string in functions...

- Fix a bunch of off-by one errors in irr::core::string in functions equals_substring_ignore_case, findFirst, findFirstChar, findNext, findLast, findLastChar, replace, remove and removeChars. This prevents some potential memory access errors, find functions no longer try to find the \0, replace no longer replaces the \0 and remove no longer tries to remove it (which did remove the last character instead).
- Fix a few new warnings in gcc.



git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@4308 dfc29bdd-3216-0410-991c-e03cc46cb475
parent 74d18ec6
Changes in 1.8 (??.??.2011) Changes in 1.8 (??.??.2011)
- Fix a bunch of off-by one errors in irr::core::string in functions equals_substring_ignore_case, findFirst, findFirstChar, findNext, findLast, findLastChar, replace, remove and removeChars.
This prevents some potential memory access errors, find functions no longer try to find the \0, replace no longer replaces the \0 and remove no longer tries to remove it (which did remove the last character instead).
- matrix4::setRotationAxisRadians added - matrix4::setRotationAxisRadians added
- quaternion conversions to and from matrix4 no longer invert rotations. - quaternion conversions to and from matrix4 no longer invert rotations.
......
...@@ -501,7 +501,7 @@ public: ...@@ -501,7 +501,7 @@ public:
//! Makes the string lower case. //! Makes the string lower case.
string<T,TAlloc>& make_lower() string<T,TAlloc>& make_lower()
{ {
for (u32 i=0; i<used; ++i) for (u32 i=0; array[i]; ++i)
array[i] = locale_lower ( array[i] ); array[i] = locale_lower ( array[i] );
return *this; return *this;
} }
...@@ -510,7 +510,7 @@ public: ...@@ -510,7 +510,7 @@ public:
//! Makes the string upper case. //! Makes the string upper case.
string<T,TAlloc>& make_upper() string<T,TAlloc>& make_upper()
{ {
for (u32 i=0; i<used; ++i) for (u32 i=0; array[i]; ++i)
array[i] = locale_upper ( array[i] ); array[i] = locale_upper ( array[i] );
return *this; return *this;
} }
...@@ -534,7 +534,7 @@ public: ...@@ -534,7 +534,7 @@ public:
\return True if the strings are equal ignoring case. */ \return True if the strings are equal ignoring case. */
bool equals_substring_ignore_case(const string<T,TAlloc>&other, const s32 sourcePos = 0 ) const bool equals_substring_ignore_case(const string<T,TAlloc>&other, const s32 sourcePos = 0 ) const
{ {
if ( (u32) sourcePos > used ) if ( (u32) sourcePos >= used )
return false; return false;
u32 i; u32 i;
...@@ -717,7 +717,7 @@ public: ...@@ -717,7 +717,7 @@ public:
or -1 if not found. */ or -1 if not found. */
s32 findFirst(T c) const s32 findFirst(T c) const
{ {
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
if (array[i] == c) if (array[i] == c)
return i; return i;
...@@ -736,7 +736,7 @@ public: ...@@ -736,7 +736,7 @@ public:
if (!c || !count) if (!c || !count)
return -1; return -1;
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
for (u32 j=0; j<count; ++j) for (u32 j=0; j<count; ++j)
if (array[i] == c[j]) if (array[i] == c[j])
return i; return i;
...@@ -806,7 +806,7 @@ public: ...@@ -806,7 +806,7 @@ public:
or -1 if not found. */ or -1 if not found. */
s32 findNext(T c, u32 startPos) const s32 findNext(T c, u32 startPos) const
{ {
for (u32 i=startPos; i<used; ++i) for (u32 i=startPos; i<used-1; ++i)
if (array[i] == c) if (array[i] == c)
return i; return i;
...@@ -821,7 +821,7 @@ public: ...@@ -821,7 +821,7 @@ public:
or -1 if not found. */ or -1 if not found. */
s32 findLast(T c, s32 start = -1) const s32 findLast(T c, s32 start = -1) const
{ {
start = core::clamp ( start < 0 ? (s32)(used) - 1 : start, 0, (s32)(used) - 1 ); start = core::clamp ( start < 0 ? (s32)(used) - 2 : start, 0, (s32)(used) - 2 );
for (s32 i=start; i>=0; --i) for (s32 i=start; i>=0; --i)
if (array[i] == c) if (array[i] == c)
return i; return i;
...@@ -841,7 +841,7 @@ public: ...@@ -841,7 +841,7 @@ public:
if (!c || !count) if (!c || !count)
return -1; return -1;
for (s32 i=used-1; i>=0; --i) for (s32 i=(s32)used-2; i>=0; --i)
for (u32 j=0; j<count; ++j) for (u32 j=0; j<count; ++j)
if (array[i] == c[j]) if (array[i] == c[j])
return i; return i;
...@@ -1006,7 +1006,7 @@ public: ...@@ -1006,7 +1006,7 @@ public:
\param replaceWith Character replacing the old one. */ \param replaceWith Character replacing the old one. */
string<T,TAlloc>& replace(T toReplace, T replaceWith) string<T,TAlloc>& replace(T toReplace, T replaceWith)
{ {
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
if (array[i] == toReplace) if (array[i] == toReplace)
array[i] = replaceWith; array[i] = replaceWith;
return *this; return *this;
...@@ -1128,7 +1128,7 @@ public: ...@@ -1128,7 +1128,7 @@ public:
{ {
u32 pos = 0; u32 pos = 0;
u32 found = 0; u32 found = 0;
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
{ {
if (array[i] == c) if (array[i] == c)
{ {
...@@ -1153,7 +1153,7 @@ public: ...@@ -1153,7 +1153,7 @@ public:
return *this; return *this;
u32 pos = 0; u32 pos = 0;
u32 found = 0; u32 found = 0;
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
{ {
u32 j = 0; u32 j = 0;
while (j < size) while (j < size)
...@@ -1186,7 +1186,7 @@ public: ...@@ -1186,7 +1186,7 @@ public:
u32 pos = 0; u32 pos = 0;
u32 found = 0; u32 found = 0;
for (u32 i=0; i<used; ++i) for (u32 i=0; i<used-1; ++i)
{ {
// Don't use characters.findFirst as it finds the \0, // Don't use characters.findFirst as it finds the \0,
// causing used to become incorrect. // causing used to become incorrect.
......
...@@ -143,14 +143,14 @@ bool CColladaMeshWriterProperties::useNodeMaterial(const scene::ISceneNode* node ...@@ -143,14 +143,14 @@ bool CColladaMeshWriterProperties::useNodeMaterial(const scene::ISceneNode* node
return false; return false;
// TODO: we need some ISceneNode::hasType() function to get rid of those checks // TODO: we need some ISceneNode::hasType() function to get rid of those checks
bool useMeshMaterial = ( (node->getType() == ESNT_MESH || bool useMeshMaterial = ( (node->getType() == ESNT_MESH ||
node->getType() == ESNT_CUBE || node->getType() == ESNT_CUBE ||
node->getType() == ESNT_SPHERE || node->getType() == ESNT_SPHERE ||
node->getType() == ESNT_WATER_SURFACE || node->getType() == ESNT_WATER_SURFACE ||
node->getType() == ESNT_Q3SHADER_SCENE_NODE) node->getType() == ESNT_Q3SHADER_SCENE_NODE)
&& static_cast<const IMeshSceneNode*>(node)->isReadOnlyMaterials()) && static_cast<const IMeshSceneNode*>(node)->isReadOnlyMaterials())
|| (node->getType() == ESNT_ANIMATED_MESH || (node->getType() == ESNT_ANIMATED_MESH
&& static_cast<const IAnimatedMeshSceneNode*>(node)->isReadOnlyMaterials() ); && static_cast<const IAnimatedMeshSceneNode*>(node)->isReadOnlyMaterials() );
return !useMeshMaterial; return !useMeshMaterial;
...@@ -180,7 +180,7 @@ irr::core::stringw CColladaMeshWriterNames::nameForNode(const scene::ISceneNode* ...@@ -180,7 +180,7 @@ irr::core::stringw CColladaMeshWriterNames::nameForNode(const scene::ISceneNode*
irr::core::stringw name; irr::core::stringw name;
// Prefix, because xs::ID can't start with a number, also nicer name // Prefix, because xs::ID can't start with a number, also nicer name
if ( node && node->getType() == ESNT_LIGHT ) if ( node && node->getType() == ESNT_LIGHT )
name = L"light"; name = L"light";
else else
name = L"node"; name = L"node";
name += nameForPtr(node); name += nameForPtr(node);
...@@ -368,7 +368,7 @@ bool CColladaMeshWriter::writeScene(io::IWriteFile* file, scene::ISceneNode* roo ...@@ -368,7 +368,7 @@ bool CColladaMeshWriter::writeScene(io::IWriteFile* file, scene::ISceneNode* roo
if ( root->getType() != ESNT_SCENE_MANAGER ) if ( root->getType() != ESNT_SCENE_MANAGER )
{ {
// TODO: Not certain if we should really write the root or if we should just always only write the children. // TODO: Not certain if we should really write the root or if we should just always only write the children.
// For now writing root to keep backward compatibility for this case, but if anyone needs to _not_ write // For now writing root to keep backward compatibility for this case, but if anyone needs to _not_ write
// that root-node we can add a parameter for this later on in writeScene. // that root-node we can add a parameter for this later on in writeScene.
writeSceneNode(root); writeSceneNode(root);
} }
...@@ -378,7 +378,7 @@ bool CColladaMeshWriter::writeScene(io::IWriteFile* file, scene::ISceneNode* roo ...@@ -378,7 +378,7 @@ bool CColladaMeshWriter::writeScene(io::IWriteFile* file, scene::ISceneNode* roo
// so we do not write the root itself if it points to the scenemanager. // so we do not write the root itself if it points to the scenemanager.
const core::list<ISceneNode*>& rootChildren = root->getChildren(); const core::list<ISceneNode*>& rootChildren = root->getChildren();
for ( core::list<ISceneNode*>::ConstIterator it = rootChildren.begin(); for ( core::list<ISceneNode*>::ConstIterator it = rootChildren.begin();
it != rootChildren.end(); it != rootChildren.end();
++ it ) ++ it )
{ {
writeSceneNode(*it); writeSceneNode(*it);
...@@ -444,7 +444,7 @@ void CColladaMeshWriter::writeNodeMaterials(irr::scene::ISceneNode * node) ...@@ -444,7 +444,7 @@ void CColladaMeshWriter::writeNodeMaterials(irr::scene::ISceneNode * node)
IMesh* mesh = getProperties()->getMesh(node); IMesh* mesh = getProperties()->getMesh(node);
if ( mesh ) if ( mesh )
{ {
MeshNode * n = Meshes.find(mesh); MeshNode * n = Meshes.find(mesh);
if ( !getProperties()->useNodeMaterial(node) ) if ( !getProperties()->useNodeMaterial(node) )
{ {
// no material overrides - write mesh materials // no material overrides - write mesh materials
...@@ -452,7 +452,7 @@ void CColladaMeshWriter::writeNodeMaterials(irr::scene::ISceneNode * node) ...@@ -452,7 +452,7 @@ void CColladaMeshWriter::writeNodeMaterials(irr::scene::ISceneNode * node)
{ {
writeMeshMaterials(mesh, getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL ? &materialNames : NULL); writeMeshMaterials(mesh, getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL ? &materialNames : NULL);
n->getValue().MaterialsWritten = true; n->getValue().MaterialsWritten = true;
} }
} }
else else
{ {
...@@ -617,6 +617,8 @@ void CColladaMeshWriter::writeNodeLights(irr::scene::ISceneNode * node) ...@@ -617,6 +617,8 @@ void CColladaMeshWriter::writeNodeLights(irr::scene::ISceneNode * node)
Writer->writeClosingTag(L"directional"); Writer->writeClosingTag(L"directional");
Writer->writeLineBreak(); Writer->writeLineBreak();
break; break;
default:
break;
} }
Writer->writeClosingTag(L"technique_common"); Writer->writeClosingTag(L"technique_common");
...@@ -684,7 +686,7 @@ void CColladaMeshWriter::writeNodeCameras(irr::scene::ISceneNode * node) ...@@ -684,7 +686,7 @@ void CColladaMeshWriter::writeNodeCameras(irr::scene::ISceneNode * node)
Writer->writeClosingTag(L"technique_common"); Writer->writeClosingTag(L"technique_common");
Writer->writeLineBreak(); Writer->writeLineBreak();
Writer->writeClosingTag(L"optics"); Writer->writeClosingTag(L"optics");
Writer->writeLineBreak(); Writer->writeLineBreak();
...@@ -732,7 +734,7 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node ) ...@@ -732,7 +734,7 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node )
Writer->writeLineBreak(); Writer->writeLineBreak();
// DummyTransformationSceneNode don't have rotation, position, scale information // DummyTransformationSceneNode don't have rotation, position, scale information
// But also don't always export the transformation matrix as that forces us creating // But also don't always export the transformation matrix as that forces us creating
// new DummyTransformationSceneNode's on import. // new DummyTransformationSceneNode's on import.
if ( node->getType() == ESNT_DUMMY_TRANSFORMATION ) if ( node->getType() == ESNT_DUMMY_TRANSFORMATION )
{ {
...@@ -740,7 +742,7 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node ) ...@@ -740,7 +742,7 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node )
} }
else else
{ {
irr::core::vector3df rot(node->getRotation()); irr::core::vector3df rot(node->getRotation());
if ( isCamera(node) && !static_cast<ICameraSceneNode*>(node)->getTargetAndRotationBinding() ) if ( isCamera(node) && !static_cast<ICameraSceneNode*>(node)->getTargetAndRotationBinding() )
{ {
ICameraSceneNode * camNode = static_cast<ICameraSceneNode*>(node); ICameraSceneNode * camNode = static_cast<ICameraSceneNode*>(node);
...@@ -748,10 +750,10 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node ) ...@@ -748,10 +750,10 @@ void CColladaMeshWriter::writeSceneNode(irr::scene::ISceneNode * node )
rot = toTarget.getHorizontalAngle(); rot = toTarget.getHorizontalAngle();
} }
writeTranslateElement( node->getPosition() ); writeTranslateElement( node->getPosition() );
writeRotateElement( irr::core::vector3df(1.f, 0.f, 0.f), rot.X ); writeRotateElement( irr::core::vector3df(1.f, 0.f, 0.f), rot.X );
writeRotateElement( irr::core::vector3df(0.f, 1.f, 0.f), rot.Y ); writeRotateElement( irr::core::vector3df(0.f, 1.f, 0.f), rot.Y );
writeRotateElement( irr::core::vector3df(0.f, 0.f, 1.f), rot.Z ); writeRotateElement( irr::core::vector3df(0.f, 0.f, 1.f), rot.Z );
writeScaleElement( node->getScale() ); writeScaleElement( node->getScale() );
} }
...@@ -1528,7 +1530,7 @@ void CColladaMeshWriter::writeMeshGeometry(const irr::core::stringw& meshname, s ...@@ -1528,7 +1530,7 @@ void CColladaMeshWriter::writeMeshGeometry(const irr::core::stringw& meshname, s
// write texture coordinates // write texture coordinates
core::stringw meshTexCoord0Id(meshId); core::stringw meshTexCoord0Id(meshId);
meshTexCoord0Id += L"-TexCoord0"; meshTexCoord0Id += L"-TexCoord0";
Writer->writeElement(L"source", false, L"id", meshTexCoord0Id.c_str()); Writer->writeElement(L"source", false, L"id", meshTexCoord0Id.c_str());
Writer->writeLineBreak(); Writer->writeLineBreak();
...@@ -2226,7 +2228,7 @@ void CColladaMeshWriter::writeMatrixElement(const irr::core::matrix4& matrix) ...@@ -2226,7 +2228,7 @@ void CColladaMeshWriter::writeMatrixElement(const irr::core::matrix4& matrix)
if ( b > 0 ) if ( b > 0 )
txt += " "; txt += " ";
// row-column switched compared to Irrlicht // row-column switched compared to Irrlicht
txt += irr::core::stringw(matrix[b*4+a]); txt += irr::core::stringw(matrix[b*4+a]);
} }
Writer->writeText(txt.c_str()); Writer->writeText(txt.c_str());
Writer->writeLineBreak(); Writer->writeLineBreak();
......
...@@ -69,6 +69,8 @@ void CLightSceneNode::render() ...@@ -69,6 +69,8 @@ void CLightSceneNode::render()
LightData.Direction * LightData.Radius, LightData.Direction * LightData.Radius,
LightData.DiffuseColor.toSColor()); LightData.DiffuseColor.toSColor());
break; break;
default:
break;
} }
} }
......
...@@ -3448,6 +3448,8 @@ void COpenGLDriver::assignHardwareLight(u32 lightIndex) ...@@ -3448,6 +3448,8 @@ void COpenGLDriver::assignHardwareLight(u32 lightIndex)
glLightf(lidx, GL_SPOT_EXPONENT, 0.0f); glLightf(lidx, GL_SPOT_EXPONENT, 0.0f);
glLightf(lidx, GL_SPOT_CUTOFF, 180.0f); glLightf(lidx, GL_SPOT_CUTOFF, 180.0f);
break; break;
default:
break;
} }
// set diffuse color // set diffuse color
......
...@@ -1942,6 +1942,8 @@ s32 CBurningVideoDriver::addDynamicLight(const SLight& dl) ...@@ -1942,6 +1942,8 @@ s32 CBurningVideoDriver::addDynamicLight(const SLight& dl)
l.linearAttenuation = 1.f / dl.Radius; l.linearAttenuation = 1.f / dl.Radius;
l.quadraticAttenuation = dl.Attenuation.Z; l.quadraticAttenuation = dl.Attenuation.Z;
break;
default:
break; break;
} }
...@@ -2162,6 +2164,8 @@ void CBurningVideoDriver::lightVertex ( s4DVertex *dest, u32 vertexargb ) ...@@ -2162,6 +2164,8 @@ void CBurningVideoDriver::lightVertex ( s4DVertex *dest, u32 vertexargb )
// diffuse component // diffuse component
diffuse.mulAdd ( light.DiffuseColor, dot ); diffuse.mulAdd ( light.DiffuseColor, dot );
break; break;
default:
break;
} }
} }
...@@ -2228,7 +2232,7 @@ void CBurningVideoDriver::draw2DImage(const video::ITexture* texture, const core ...@@ -2228,7 +2232,7 @@ void CBurningVideoDriver::draw2DImage(const video::ITexture* texture, const core
os::Printer::log("Fatal Error: Tried to copy from a surface not owned by this driver.", ELL_ERROR); os::Printer::log("Fatal Error: Tried to copy from a surface not owned by this driver.", ELL_ERROR);
return; return;
} }
if (useAlphaChannelOfTexture) if (useAlphaChannelOfTexture)
StretchBlit(BLITTER_TEXTURE_ALPHA_BLEND, RenderTargetSurface, &destRect, &sourceRect, StretchBlit(BLITTER_TEXTURE_ALPHA_BLEND, RenderTargetSurface, &destRect, &sourceRect,
((CSoftwareTexture2*)texture)->getImage(), (colors ? colors[0].color : 0)); ((CSoftwareTexture2*)texture)->getImage(), (colors ? colors[0].color : 0));
...@@ -2237,7 +2241,7 @@ void CBurningVideoDriver::draw2DImage(const video::ITexture* texture, const core ...@@ -2237,7 +2241,7 @@ void CBurningVideoDriver::draw2DImage(const video::ITexture* texture, const core
((CSoftwareTexture2*)texture)->getImage(), (colors ? colors[0].color : 0)); ((CSoftwareTexture2*)texture)->getImage(), (colors ? colors[0].color : 0));
} }
} }
//! Draws a 2d line. //! Draws a 2d line.
void CBurningVideoDriver::draw2DLine(const core::position2d<s32>& start, void CBurningVideoDriver::draw2DLine(const core::position2d<s32>& start,
const core::position2d<s32>& end, const core::position2d<s32>& end,
......
...@@ -180,6 +180,104 @@ bool testAppendStringc() ...@@ -180,6 +180,104 @@ bool testAppendStringc()
return true; return true;
} }
bool testLowerUpper()
{
irr::core::array <irr::core::stringc> stringsOrig, targetLower, targetUpper;
stringsOrig.push_back("abc");
targetLower.push_back("abc");
targetUpper.push_back("ABC");
stringsOrig.push_back("ABC");
targetLower.push_back("abc");
targetUpper.push_back("ABC");
stringsOrig.push_back("Abc");
targetLower.push_back("abc");
targetUpper.push_back("ABC");
stringsOrig.push_back("aBBc");
targetLower.push_back("abbc");
targetUpper.push_back("ABBC");
stringsOrig.push_back("abC");
targetLower.push_back("abc");
targetUpper.push_back("ABC");
stringsOrig.push_back("");
targetLower.push_back("");
targetUpper.push_back("");
/* TODO: those are not supported so far
stringsOrig.push_back("ßäöü");
targetLower.push_back("ßäöü");
targetUpper.push_back("ßÄÖÜ");
stringsOrig.push_back("ßÄÖÜ");
targetLower.push_back("ßäöü");
targetUpper.push_back("ßÄÖÜ");
*/
for ( irr::u32 i=0; i<stringsOrig.size(); ++i )
{
irr::core::stringc c = stringsOrig[i];
c.make_lower();
if ( c != targetLower[i] )
{
logTestString("make_lower for stringc failed in test %d %s\n", i, stringsOrig[i].c_str());
return false;
}
c = stringsOrig[i];
c.make_upper();
if ( c != targetUpper[i] )
{
logTestString("make_upper for stringc failed in test %d %s %s\n", i, stringsOrig[i].c_str(), c.c_str());
return false;
}
irr::core::stringw w = irr::core::stringw(stringsOrig[i]);
c.make_lower();
if ( c != irr::core::stringw(targetLower[i]) )
{
logTestString("make_lower for stringw failed in test %d %s\n", i, stringsOrig[i].c_str());
return false;
}
c = irr::core::stringw(stringsOrig[i]);
c.make_upper();
if ( c != irr::core::stringw(targetUpper[i]) )
{
logTestString("make_upper for stringw failed in test %d %s\n", i, stringsOrig[i].c_str());
return false;
}
}
return true;
}
bool testFindFunctions()
{
irr::core::stringc dot(".");
irr::s32 p = dot.findFirst(0);
if ( p >= 0 )
return false;
irr::core::stringc empty("");
p = empty.findLastCharNotInList("x",1);
if ( p >= 0 )
return false;
p = empty.findLast('x');
if ( p >= 0 )
return false;
p = dot.findLast('.');
if ( p != 0 )
return false;
p = empty.findLastChar("ab", 2);
if ( p >= 0 )
return false;
p = dot.findLastChar("-.", 2);
if ( p != 0 )
return false;
return true;
}
// Test the functionality of irrString // Test the functionality of irrString
/** Validation is done with assert_log() against expected results. */ /** Validation is done with assert_log() against expected results. */
...@@ -243,6 +341,12 @@ bool testIrrString(void) ...@@ -243,6 +341,12 @@ bool testIrrString(void)
logTestString("test replace\n"); logTestString("test replace\n");
allExpected &= testReplace(); allExpected &= testReplace();
logTestString("test make_lower and make_uppers\n");
allExpected &= testLowerUpper();
logTestString("test find functions\n");
allExpected &= testFindFunctions();
if(allExpected) if(allExpected)
logTestString("\nAll tests passed\n"); logTestString("\nAll tests passed\n");
else else
......
Tests finished. 1 test of 1 passed. Tests finished. 1 test of 1 passed.
Compiled as DEBUG Compiled as DEBUG
Test suite pass at GMT Sat Jul 7 10:28:50 2012 Test suite pass at GMT Thu Sep 6 19:39:44 2012
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