Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
angle
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Chen Yisong
angle
Commits
f1d723c6
Commit
f1d723c6
authored
Sep 23, 2013
by
Zhenyao Mo
Committed by
Jamie Madill
Sep 24, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Clamp numeric overflow rather than failing with an error
BUG=249086 ANGLEBUG=468 TEST= R=alokp@chromium.org, kbr@chromium.org Review URL:
https://codereview.appspot.com/13195043
parent
b41ebf57
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
135 additions
and
90 deletions
+135
-90
version.h
src/common/version.h
+1
-1
glslang.l
src/compiler/glslang.l
+30
-10
glslang_lex.cpp
src/compiler/glslang_lex.cpp
+30
-10
ExpressionParser.cpp
src/compiler/preprocessor/ExpressionParser.cpp
+14
-14
Preprocessor.cpp
src/compiler/preprocessor/Preprocessor.cpp
+0
-29
Tokenizer.cpp
src/compiler/preprocessor/Tokenizer.cpp
+23
-11
util.cpp
src/compiler/util.cpp
+29
-6
util.h
src/compiler/util.h
+8
-9
No files found.
src/common/version.h
View file @
f1d723c6
#define MAJOR_VERSION 2
#define MINOR_VERSION 0
#define BUILD_VERSION 0
#define BUILD_REVISION 201
1
#define BUILD_REVISION 201
2
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
...
...
src/compiler/glslang.l
View file @
f1d723c6
...
...
@@ -61,6 +61,8 @@ static int ES2_reserved_ES3_keyword(TParseContext *context, int token);
static int ES2_keyword_ES3_reserved(TParseContext *context, int token);
static int ES2_ident_ES3_keyword(TParseContext *context, int token);
static int uint_constant(TParseContext *context);
static int int_constant(yyscan_t yyscanner);
static int float_constant(yyscan_t yyscanner);
static int floatsuffix_check(TParseContext* context);
%}
...
...
@@ -307,17 +309,17 @@ O [0-7]
return check_type(yyscanner);
}
0[xX]{H}+ {
yylval->lex.i = static_cast<int>(strtol(yytext, 0, 0)); return INTCONSTANT
; }
0{O}+ {
yylval->lex.i = static_cast<int>(strtol(yytext, 0, 0)); return INTCONSTANT
; }
{D}+ {
yylval->lex.i = static_cast<int>(strtol(yytext, 0, 0)); return INTCONSTANT
; }
0[xX]{H}+ {
return int_constant(yyscanner)
; }
0{O}+ {
return int_constant(yyscanner)
; }
{D}+ {
return int_constant(yyscanner)
; }
0[xX]{H}+[uU] { return uint_constant(context); }
0{O}+[uU] { return uint_constant(context); }
{D}+[uU] { return uint_constant(context); }
{D}+{E} {
yylval->lex.f = static_cast<float>(atof_dot(yytext)); return FLOATCONSTANT
; }
{D}+"."{D}*({E})? {
yylval->lex.f = static_cast<float>(atof_dot(yytext)); return FLOATCONSTANT
; }
"."{D}+({E})? {
yylval->lex.f = static_cast<float>(atof_dot(yytext)); return FLOATCONSTANT
; }
{D}+{E} {
return float_constant(yyscanner)
; }
{D}+"."{D}*({E})? {
return float_constant(yyscanner)
; }
"."{D}+({E})? {
return float_constant(yyscanner)
; }
{D}+{E}[fF] { return floatsuffix_check(context); }
{D}+"."{D}*({E})?[fF] { return floatsuffix_check(context); }
...
...
@@ -459,8 +461,6 @@ int uint_constant(TParseContext *context)
struct yyguts_t* yyg = (struct yyguts_t*) context->scanner;
yyscan_t yyscanner = (yyscan_t) context->scanner;
yylval->lex.u = static_cast<unsigned int>(strtol(yytext, 0, 0));
if (context->shaderVersion < 300)
{
context->error(*yylloc, "Unsigned integers are unsupported prior to GLSL ES 3.00", yytext, "");
...
...
@@ -468,6 +468,9 @@ int uint_constant(TParseContext *context)
return 0;
}
if (!atoi_clamp(yytext, &(yylval->lex.i)))
yyextra->warning(*yylloc, "Integer overflow", yytext, "");
return UINTCONSTANT;
}
...
...
@@ -475,8 +478,6 @@ int floatsuffix_check(TParseContext* context)
{
struct yyguts_t* yyg = (struct yyguts_t*) context->scanner;
yylval->lex.f = static_cast<float>(atof_dot(yytext));
if (context->shaderVersion < 300)
{
context->error(*yylloc, "Floating-point suffix unsupported prior to GLSL ES 3.00", yytext);
...
...
@@ -484,6 +485,9 @@ int floatsuffix_check(TParseContext* context)
return 0;
}
if (!atof_clamp(yytext, &(yylval->lex.f)))
yyextra->warning(*yylloc, "Float overflow", yytext, "");
return(FLOATCONSTANT);
}
...
...
@@ -492,6 +496,22 @@ void yyerror(YYLTYPE* lloc, TParseContext* context, const char* reason) {
context->recover();
}
int int_constant(yyscan_t yyscanner) {
struct yyguts_t* yyg = (struct yyguts_t*) yyscanner;
if (!atoi_clamp(yytext, &(yylval->lex.i)))
yyextra->warning(*yylloc, "Integer overflow", yytext, "");
return INTCONSTANT;
}
int float_constant(yyscan_t yyscanner) {
struct yyguts_t* yyg = (struct yyguts_t*) yyscanner;
if (!atof_clamp(yytext, &(yylval->lex.f)))
yyextra->warning(*yylloc, "Float overflow", yytext, "");
return FLOATCONSTANT;
}
int glslang_initialize(TParseContext* context) {
yyscan_t scanner = NULL;
if (yylex_init_extra(context, &scanner))
...
...
src/compiler/glslang_lex.cpp
View file @
f1d723c6
...
...
@@ -1020,6 +1020,8 @@ static int ES2_reserved_ES3_keyword(TParseContext *context, int token);
static
int
ES2_keyword_ES3_reserved
(
TParseContext
*
context
,
int
token
);
static
int
ES2_ident_ES3_keyword
(
TParseContext
*
context
,
int
token
);
static
int
uint_constant
(
TParseContext
*
context
);
static
int
int_constant
(
yyscan_t
yyscanner
);
static
int
float_constant
(
yyscan_t
yyscanner
);
static
int
floatsuffix_check
(
TParseContext
*
context
);
#define INITIAL 0
...
...
@@ -1793,15 +1795,15 @@ YY_RULE_SETUP
YY_BREAK
case
178
:
YY_RULE_SETUP
{
yylval
->
lex
.
i
=
static_cast
<
int
>
(
strtol
(
yytext
,
0
,
0
));
return
INTCONSTANT
;
}
{
return
int_constant
(
yyscanner
)
;
}
YY_BREAK
case
179
:
YY_RULE_SETUP
{
yylval
->
lex
.
i
=
static_cast
<
int
>
(
strtol
(
yytext
,
0
,
0
));
return
INTCONSTANT
;
}
{
return
int_constant
(
yyscanner
)
;
}
YY_BREAK
case
180
:
YY_RULE_SETUP
{
yylval
->
lex
.
i
=
static_cast
<
int
>
(
strtol
(
yytext
,
0
,
0
));
return
INTCONSTANT
;
}
{
return
int_constant
(
yyscanner
)
;
}
YY_BREAK
case
181
:
YY_RULE_SETUP
...
...
@@ -1817,15 +1819,15 @@ YY_RULE_SETUP
YY_BREAK
case
184
:
YY_RULE_SETUP
{
yylval
->
lex
.
f
=
static_cast
<
float
>
(
atof_dot
(
yytext
));
return
FLOATCONSTANT
;
}
{
return
float_constant
(
yyscanner
)
;
}
YY_BREAK
case
185
:
YY_RULE_SETUP
{
yylval
->
lex
.
f
=
static_cast
<
float
>
(
atof_dot
(
yytext
));
return
FLOATCONSTANT
;
}
{
return
float_constant
(
yyscanner
)
;
}
YY_BREAK
case
186
:
YY_RULE_SETUP
{
yylval
->
lex
.
f
=
static_cast
<
float
>
(
atof_dot
(
yytext
));
return
FLOATCONSTANT
;
}
{
return
float_constant
(
yyscanner
)
;
}
YY_BREAK
case
187
:
YY_RULE_SETUP
...
...
@@ -3261,8 +3263,6 @@ int uint_constant(TParseContext *context)
struct
yyguts_t
*
yyg
=
(
struct
yyguts_t
*
)
context
->
scanner
;
yyscan_t
yyscanner
=
(
yyscan_t
)
context
->
scanner
;
yylval
->
lex
.
u
=
static_cast
<
unsigned
int
>
(
strtol
(
yytext
,
0
,
0
));
if
(
context
->
shaderVersion
<
300
)
{
context
->
error
(
*
yylloc
,
"Unsigned integers are unsupported prior to GLSL ES 3.00"
,
yytext
,
""
);
...
...
@@ -3270,6 +3270,9 @@ int uint_constant(TParseContext *context)
return
0
;
}
if
(
!
atoi_clamp
(
yytext
,
&
(
yylval
->
lex
.
i
)))
yyextra
->
warning
(
*
yylloc
,
"Integer overflow"
,
yytext
,
""
);
return
UINTCONSTANT
;
}
...
...
@@ -3277,8 +3280,6 @@ int floatsuffix_check(TParseContext* context)
{
struct
yyguts_t
*
yyg
=
(
struct
yyguts_t
*
)
context
->
scanner
;
yylval
->
lex
.
f
=
static_cast
<
float
>
(
atof_dot
(
yytext
));
if
(
context
->
shaderVersion
<
300
)
{
context
->
error
(
*
yylloc
,
"Floating-point suffix unsupported prior to GLSL ES 3.00"
,
yytext
);
...
...
@@ -3286,6 +3287,9 @@ int floatsuffix_check(TParseContext* context)
return
0
;
}
if
(
!
atof_clamp
(
yytext
,
&
(
yylval
->
lex
.
f
)))
yyextra
->
warning
(
*
yylloc
,
"Float overflow"
,
yytext
,
""
);
return
(
FLOATCONSTANT
);
}
...
...
@@ -3294,6 +3298,22 @@ void yyerror(YYLTYPE* lloc, TParseContext* context, const char* reason) {
context
->
recover
();
}
int
int_constant
(
yyscan_t
yyscanner
)
{
struct
yyguts_t
*
yyg
=
(
struct
yyguts_t
*
)
yyscanner
;
if
(
!
atoi_clamp
(
yytext
,
&
(
yylval
->
lex
.
i
)))
yyextra
->
warning
(
*
yylloc
,
"Integer overflow"
,
yytext
,
""
);
return
INTCONSTANT
;
}
int
float_constant
(
yyscan_t
yyscanner
)
{
struct
yyguts_t
*
yyg
=
(
struct
yyguts_t
*
)
yyscanner
;
if
(
!
atof_clamp
(
yytext
,
&
(
yylval
->
lex
.
f
)))
yyextra
->
warning
(
*
yylloc
,
"Float overflow"
,
yytext
,
""
);
return
FLOATCONSTANT
;
}
int
glslang_initialize
(
TParseContext
*
context
)
{
yyscan_t
scanner
=
NULL
;
if
(
yylex_init_extra
(
context
,
&
scanner
))
...
...
src/compiler/preprocessor/ExpressionParser.cpp
View file @
f1d723c6
/* A Bison parser, made by GNU Bison 2.7. */
/* A Bison parser, made by GNU Bison 2.7.
1.
*/
/* Bison implementation for Yacc-like parsers in C
Copyright (C) 1984, 1989-1990, 2000-201
2
Free Software Foundation, Inc.
Copyright (C) 1984, 1989-1990, 2000-201
3
Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
...
...
@@ -44,7 +44,7 @@
#define YYBISON 1
/* Bison version. */
#define YYBISON_VERSION "2.7"
#define YYBISON_VERSION "2.7
.1
"
/* Skeleton name. */
#define YYSKELETON_NAME "yacc.c"
...
...
@@ -258,6 +258,14 @@ typedef short int yytype_int16;
# endif
#endif
#ifndef __attribute__
/* This feature is available in gcc versions 2.5 and later. */
# if (! defined __GNUC__ || __GNUC__ < 2 \
|| (__GNUC__ == 2 && __GNUC_MINOR__ < 5))
# define __attribute__(Spec)
/* empty */
# endif
#endif
/* Suppress unused-variable warnings by "using" E. */
#if ! defined lint || defined __GNUC__
# define YYUSE(E) ((void) (E))
...
...
@@ -265,6 +273,7 @@ typedef short int yytype_int16;
# define YYUSE(E)
/* empty */
#endif
/* Identity function, used to suppress warnings about constant conditions. */
#ifndef lint
# define YYID(N) (N)
...
...
@@ -762,11 +771,7 @@ yy_symbol_value_print (yyoutput, yytype, yyvaluep, context)
# else
YYUSE
(
yyoutput
);
# endif
switch
(
yytype
)
{
default
:
break
;
}
YYUSE
(
yytype
);
}
...
...
@@ -1160,12 +1165,7 @@ yydestruct (yymsg, yytype, yyvaluep, context)
yymsg
=
"Deleting"
;
YY_SYMBOL_PRINT
(
yymsg
,
yytype
,
yyvaluep
,
yylocationp
);
switch
(
yytype
)
{
default
:
break
;
}
YYUSE
(
yytype
);
}
...
...
src/compiler/preprocessor/Preprocessor.cpp
View file @
f1d723c6
...
...
@@ -95,34 +95,6 @@ void Preprocessor::lex(Token* token)
case
Token
:
:
PP_HASH
:
assert
(
false
);
break
;
case
Token
:
:
CONST_INT
:
{
int
val
=
0
;
if
(
!
token
->
iValue
(
&
val
))
{
// Do not mark the token as invalid.
// Just emit the diagnostic and reset value to 0.
mImpl
->
diagnostics
->
report
(
Diagnostics
::
INTEGER_OVERFLOW
,
token
->
location
,
token
->
text
);
token
->
text
.
assign
(
"0"
);
}
validToken
=
true
;
break
;
}
case
Token
:
:
CONST_FLOAT
:
{
float
val
=
0
;
if
(
!
token
->
fValue
(
&
val
))
{
// Do not mark the token as invalid.
// Just emit the diagnostic and reset value to 0.0.
mImpl
->
diagnostics
->
report
(
Diagnostics
::
FLOAT_OVERFLOW
,
token
->
location
,
token
->
text
);
token
->
text
.
assign
(
"0.0"
);
}
validToken
=
true
;
break
;
}
case
Token
:
:
PP_NUMBER
:
mImpl
->
diagnostics
->
report
(
Diagnostics
::
INVALID_NUMBER
,
token
->
location
,
token
->
text
);
...
...
@@ -139,4 +111,3 @@ void Preprocessor::lex(Token* token)
}
}
// namespace pp
src/compiler/preprocessor/Tokenizer.cpp
View file @
f1d723c6
...
...
@@ -18,7 +18,7 @@
#define FLEX_SCANNER
#define YY_FLEX_MAJOR_VERSION 2
#define YY_FLEX_MINOR_VERSION 5
#define YY_FLEX_SUBMINOR_VERSION 3
5
#define YY_FLEX_SUBMINOR_VERSION 3
7
#if YY_FLEX_SUBMINOR_VERSION > 0
#define FLEX_BETA
#endif
...
...
@@ -64,7 +64,6 @@ typedef int flex_int32_t;
typedef
unsigned
char
flex_uint8_t
;
typedef
unsigned
short
int
flex_uint16_t
;
typedef
unsigned
int
flex_uint32_t
;
#endif
/* ! C99 */
/* Limits of integral types. */
#ifndef INT8_MIN
...
...
@@ -95,6 +94,8 @@ typedef unsigned int flex_uint32_t;
#define UINT32_MAX (4294967295U)
#endif
#endif
/* ! C99 */
#endif
/* ! FLEXINT_H */
#ifdef __cplusplus
...
...
@@ -185,6 +186,11 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE;
typedef
size_t
yy_size_t
;
#endif
#ifndef YY_TYPEDEF_YY_SIZE_T
#define YY_TYPEDEF_YY_SIZE_T
typedef
size_t
yy_size_t
;
#endif
#define EOB_ACT_CONTINUE_SCAN 0
#define EOB_ACT_END_OF_FILE 1
#define EOB_ACT_LAST_MATCH 2
...
...
@@ -335,7 +341,7 @@ void ppfree (void * ,yyscan_t yyscanner );
/* Begin user sect3 */
#define ppwrap(
n
) 1
#define ppwrap(
yyscanner
) 1
#define YY_SKIP_YYWRAP
typedef
unsigned
char
YY_CHAR
;
...
...
@@ -661,6 +667,10 @@ int ppget_lineno (yyscan_t yyscanner );
void
ppset_lineno
(
int
line_number
,
yyscan_t
yyscanner
);
int
ppget_column
(
yyscan_t
yyscanner
);
void
ppset_column
(
int
column_no
,
yyscan_t
yyscanner
);
YYSTYPE
*
ppget_lval
(
yyscan_t
yyscanner
);
void
ppset_lval
(
YYSTYPE
*
yylval_param
,
yyscan_t
yyscanner
);
...
...
@@ -709,7 +719,7 @@ static int input (yyscan_t yyscanner );
/* This used to be an fputs(), but since the string might contain NUL's,
* we now use fwrite().
*/
#define ECHO
fwrite( yytext, yyleng, 1, yyout
)
#define ECHO
do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0
)
#endif
/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
...
...
@@ -720,7 +730,7 @@ static int input (yyscan_t yyscanner );
if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
{ \
int c = '*'; \
yy_
size_t n; \
size_t n; \
for ( n = 0; n < max_size && \
(c = getc( yyin )) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
...
...
@@ -1361,7 +1371,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
{
/* Not enough room in the buffer - grow it. */
/* just a shorter name for the current buffer */
YY_BUFFER_STATE
b
=
YY_CURRENT_BUFFER
;
YY_BUFFER_STATE
b
=
YY_CURRENT_BUFFER
_LVALUE
;
int
yy_c_buf_p_offset
=
(
int
)
(
yyg
->
yy_c_buf_p
-
b
->
yy_ch_buf
);
...
...
@@ -1496,6 +1506,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
yy_current_state
=
yy_nxt
[
yy_base
[
yy_current_state
]
+
(
unsigned
int
)
yy_c
];
yy_is_jam
=
(
yy_current_state
==
97
);
(
void
)
yyg
;
return
yy_is_jam
?
0
:
yy_current_state
;
}
...
...
@@ -1897,8 +1908,8 @@ YY_BUFFER_STATE pp_scan_string (yyconst char * yystr , yyscan_t yyscanner)
/** Setup the input buffer state to scan the given bytes. The next call to pplex() will
* scan from a @e copy of @a bytes.
* @param bytes the byte buffer to scan
* @param len the number of bytes in the buffer pointed to by @a bytes.
* @param
yy
bytes the byte buffer to scan
* @param
_yybytes_
len the number of bytes in the buffer pointed to by @a bytes.
* @param yyscanner The scanner object.
* @return the newly allocated buffer state object.
*/
...
...
@@ -1906,7 +1917,8 @@ YY_BUFFER_STATE pp_scan_bytes (yyconst char * yybytes, yy_size_t _yybytes_len
{
YY_BUFFER_STATE
b
;
char
*
buf
;
yy_size_t
n
,
i
;
yy_size_t
n
;
yy_size_t
i
;
/* Get memory for full buffer, including space for trailing EOB's. */
n
=
_yybytes_len
+
2
;
...
...
@@ -2052,7 +2064,7 @@ void ppset_lineno (int line_number , yyscan_t yyscanner)
/* lineno is only valid if an input buffer exists. */
if
(
!
YY_CURRENT_BUFFER
)
yy_fatal_error
(
"ppset_lineno called with no buffer"
,
yyscanner
);
YY_FATAL_ERROR
(
"ppset_lineno called with no buffer"
);
yylineno
=
line_number
;
}
...
...
@@ -2067,7 +2079,7 @@ void ppset_column (int column_no , yyscan_t yyscanner)
/* column is only valid if an input buffer exists. */
if
(
!
YY_CURRENT_BUFFER
)
yy_fatal_error
(
"ppset_column called with no buffer"
,
yyscanner
);
YY_FATAL_ERROR
(
"ppset_column called with no buffer"
);
yycolumn
=
column_no
;
}
...
...
src/compiler/util.cpp
View file @
f1d723c6
...
...
@@ -7,6 +7,9 @@
#include <math.h>
#include <stdlib.h>
#include <cerrno>
#include <limits>
#include "util.h"
#ifdef _MSC_VER
...
...
@@ -15,19 +18,39 @@
#include <sstream>
#endif
double
atof_dot
(
const
char
*
str
)
bool
atof_clamp
(
const
char
*
str
,
float
*
value
)
{
bool
success
=
true
;
#ifdef _MSC_VER
_locale_t
l
=
_create_locale
(
LC_NUMERIC
,
"C"
);
double
result
=
_atof_l
(
str
,
l
);
double
dvalue
=
_atof_l
(
str
,
l
);
_free_locale
(
l
);
return
result
;
if
(
errno
==
ERANGE
||
dvalue
>
std
::
numeric_limits
<
float
>::
max
())
success
=
false
;
else
*
value
=
static_cast
<
float
>
(
dvalue
);
#else
double
result
;
std
::
istringstream
s
(
str
);
std
::
locale
l
(
"C"
);
s
.
imbue
(
l
);
s
>>
result
;
return
result
;
s
>>
*
value
;
if
(
s
.
fail
())
success
=
false
;
#endif
if
(
!
success
)
*
value
=
std
::
numeric_limits
<
float
>::
max
();
return
success
;
}
bool
atoi_clamp
(
const
char
*
str
,
int
*
value
)
{
long
int
lvalue
=
strtol
(
str
,
0
,
0
);
if
(
errno
==
ERANGE
||
lvalue
>
std
::
numeric_limits
<
int
>::
max
())
{
*
value
=
std
::
numeric_limits
<
int
>::
max
();
return
false
;
}
*
value
=
static_cast
<
int
>
(
lvalue
);
return
true
;
}
src/compiler/util.h
View file @
f1d723c6
...
...
@@ -7,15 +7,14 @@
#ifndef COMPILER_UTIL_H
#define COMPILER_UTIL_H
#ifdef __cplusplus
extern
"C"
{
#endif
// atof_clamp is like atof but
// 1. it forces C locale, i.e. forcing '.' as decimal point.
// 2. it clamps the value to -FLT_MAX or FLT_MAX if overflow happens.
// Return false if overflow happens.
extern
bool
atof_clamp
(
const
char
*
str
,
float
*
value
);
// atof_dot is like atof but forcing C locale, i.e. forcing '.' as decimal point.
double
atof_dot
(
const
char
*
str
);
#ifdef __cplusplus
}
// end extern "C"
#endif
// If overflow happens, clamp the value to INT_MIN or INT_MAX.
// Return false if overflow happens.
extern
bool
atoi_clamp
(
const
char
*
str
,
int
*
value
);
#endif // COMPILER_UTIL_H
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment