Commit df1afcca by Harald Anlauf Committed by Harald Anlauf

re PR fortran/90903 (Implement runtime checks for bit manipulation intrinsics)

2019-07-16  Harald Anlauf  <anlauf@gmx.de>

	PR fortran/90903
	* libgfortran.h: Add mask for -fcheck=bits option.
	* options.c (gfc_handle_runtime_check_option): Add option "bits"
	to run-time checks selectable via -fcheck.
	* trans-intrinsic.c (gfc_conv_intrinsic_btest)
	(gfc_conv_intrinsic_singlebitop, gfc_conv_intrinsic_ibits)
	(gfc_conv_intrinsic_shift, gfc_conv_intrinsic_ishft)
	(gfc_conv_intrinsic_ishftc): Implement run-time checks for the
	POS, LEN, SHIFT, and SIZE arguments.
	* gfortran.texi: Document run-time checks for bit manipulation
	intrinsics.
	* invoke.texi: Document new -fcheck=bits option.

	PR fortran/90903
	* gfortran.dg/check_bits_1.f90: New testcase.

From-SVN: r273535
parent 460bf043
2019-07-16 Harald Anlauf <anlauf@gmx.de>
PR fortran/90903
* libgfortran.h: Add mask for -fcheck=bits option.
* options.c (gfc_handle_runtime_check_option): Add option "bits"
to run-time checks selectable via -fcheck.
* trans-intrinsic.c (gfc_conv_intrinsic_btest)
(gfc_conv_intrinsic_singlebitop, gfc_conv_intrinsic_ibits)
(gfc_conv_intrinsic_shift, gfc_conv_intrinsic_ishft)
(gfc_conv_intrinsic_ishftc): Implement run-time checks for the
POS, LEN, SHIFT, and SIZE arguments.
* gfortran.texi: Document run-time checks for bit manipulation
intrinsics.
* invoke.texi: Document new -fcheck=bits option.
2019-07-14 Jerry DeLisle <jvdelisle@gcc.gnu.org> 2019-07-14 Jerry DeLisle <jvdelisle@gcc.gnu.org>
PR fortran/87233 PR fortran/87233
......
...@@ -3790,7 +3790,8 @@ initialization using @code{_gfortran_set_args}. ...@@ -3790,7 +3790,8 @@ initialization using @code{_gfortran_set_args}.
Default: enabled. Default: enabled.
@item @var{option}[6] @tab Enables run-time checking. Possible values @item @var{option}[6] @tab Enables run-time checking. Possible values
are (bitwise or-ed): GFC_RTCHECK_BOUNDS (1), GFC_RTCHECK_ARRAY_TEMPS (2), are (bitwise or-ed): GFC_RTCHECK_BOUNDS (1), GFC_RTCHECK_ARRAY_TEMPS (2),
GFC_RTCHECK_RECURSION (4), GFC_RTCHECK_DO (16), GFC_RTCHECK_POINTER (32). GFC_RTCHECK_RECURSION (4), GFC_RTCHECK_DO (16), GFC_RTCHECK_POINTER (32),
GFC_RTCHECK_BITS (64).
Default: disabled. Default: disabled.
@item @var{option}[7] @tab Unused. @item @var{option}[7] @tab Unused.
@item @var{option}[8] @tab Show a warning when invoking @code{STOP} and @item @var{option}[8] @tab Show a warning when invoking @code{STOP} and
......
...@@ -183,7 +183,7 @@ and warnings}. ...@@ -183,7 +183,7 @@ and warnings}.
@gccoptlist{-faggressive-function-elimination -fblas-matmul-limit=@var{n} @gol @gccoptlist{-faggressive-function-elimination -fblas-matmul-limit=@var{n} @gol
-fbounds-check -ftail-call-workaround -ftail-call-workaround=@var{n} @gol -fbounds-check -ftail-call-workaround -ftail-call-workaround=@var{n} @gol
-fcheck-array-temporaries @gol -fcheck-array-temporaries @gol
-fcheck=@var{<all|array-temps|bounds|do|mem|pointer|recursion>} @gol -fcheck=@var{<all|array-temps|bits|bounds|do|mem|pointer|recursion>} @gol
-fcoarray=@var{<none|single|lib>} -fexternal-blas -ff2c -fcoarray=@var{<none|single|lib>} -fexternal-blas -ff2c
-ffrontend-loop-interchange @gol -ffrontend-loop-interchange @gol
-ffrontend-optimize @gol -ffrontend-optimize @gol
...@@ -1558,6 +1558,7 @@ library needs to be linked. ...@@ -1558,6 +1558,7 @@ library needs to be linked.
@item -fcheck=@var{<keyword>} @item -fcheck=@var{<keyword>}
@opindex @code{fcheck} @opindex @code{fcheck}
@cindex array, bounds checking @cindex array, bounds checking
@cindex bit intrinsics checking
@cindex bounds checking @cindex bounds checking
@cindex pointer checking @cindex pointer checking
@cindex memory checking @cindex memory checking
...@@ -1582,6 +1583,10 @@ sometimes useful in optimization, in order to avoid such temporaries. ...@@ -1582,6 +1583,10 @@ sometimes useful in optimization, in order to avoid such temporaries.
Note: The warning is only printed once per location. Note: The warning is only printed once per location.
@item @samp{bits}
Enable generation of run-time checks for invalid arguments to the bit
manipulation intrinsics.
@item @samp{bounds} @item @samp{bounds}
Enable generation of run-time checks for array subscripts Enable generation of run-time checks for array subscripts
and against the declared minimum and maximum values. It also and against the declared minimum and maximum values. It also
......
...@@ -73,9 +73,11 @@ along with GCC; see the file COPYING3. If not see ...@@ -73,9 +73,11 @@ along with GCC; see the file COPYING3. If not see
#define GFC_RTCHECK_DO (1<<3) #define GFC_RTCHECK_DO (1<<3)
#define GFC_RTCHECK_POINTER (1<<4) #define GFC_RTCHECK_POINTER (1<<4)
#define GFC_RTCHECK_MEM (1<<5) #define GFC_RTCHECK_MEM (1<<5)
#define GFC_RTCHECK_BITS (1<<6)
#define GFC_RTCHECK_ALL (GFC_RTCHECK_BOUNDS | GFC_RTCHECK_ARRAY_TEMPS \ #define GFC_RTCHECK_ALL (GFC_RTCHECK_BOUNDS | GFC_RTCHECK_ARRAY_TEMPS \
| GFC_RTCHECK_RECURSION | GFC_RTCHECK_DO \ | GFC_RTCHECK_RECURSION | GFC_RTCHECK_DO \
| GFC_RTCHECK_POINTER | GFC_RTCHECK_MEM) | GFC_RTCHECK_POINTER | GFC_RTCHECK_MEM \
| GFC_RTCHECK_BITS)
/* Special unit numbers used to convey certain conditions. Numbers -4 /* Special unit numbers used to convey certain conditions. Numbers -4
thru -9 available. NEWUNIT values start at -10. */ thru -9 available. NEWUNIT values start at -10. */
......
...@@ -580,12 +580,12 @@ gfc_handle_runtime_check_option (const char *arg) ...@@ -580,12 +580,12 @@ gfc_handle_runtime_check_option (const char *arg)
int result, pos = 0, n; int result, pos = 0, n;
static const char * const optname[] = { "all", "bounds", "array-temps", static const char * const optname[] = { "all", "bounds", "array-temps",
"recursion", "do", "pointer", "recursion", "do", "pointer",
"mem", NULL }; "mem", "bits", NULL };
static const int optmask[] = { GFC_RTCHECK_ALL, GFC_RTCHECK_BOUNDS, static const int optmask[] = { GFC_RTCHECK_ALL, GFC_RTCHECK_BOUNDS,
GFC_RTCHECK_ARRAY_TEMPS, GFC_RTCHECK_ARRAY_TEMPS,
GFC_RTCHECK_RECURSION, GFC_RTCHECK_DO, GFC_RTCHECK_RECURSION, GFC_RTCHECK_DO,
GFC_RTCHECK_POINTER, GFC_RTCHECK_MEM, GFC_RTCHECK_POINTER, GFC_RTCHECK_MEM,
0 }; GFC_RTCHECK_BITS, 0 };
while (*arg) while (*arg)
{ {
......
...@@ -6166,6 +6166,24 @@ gfc_conv_intrinsic_btest (gfc_se * se, gfc_expr * expr) ...@@ -6166,6 +6166,24 @@ gfc_conv_intrinsic_btest (gfc_se * se, gfc_expr * expr)
gfc_conv_intrinsic_function_args (se, expr, args, 2); gfc_conv_intrinsic_function_args (se, expr, args, 2);
type = TREE_TYPE (args[0]); type = TREE_TYPE (args[0]);
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree below = fold_build2_loc (input_location, LT_EXPR,
logical_type_node, args[1],
build_int_cst (TREE_TYPE (args[1]), 0));
tree nbits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type));
tree above = fold_build2_loc (input_location, GE_EXPR,
logical_type_node, args[1], nbits);
tree scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"POS argument (%ld) out of range 0:%ld "
"in intrinsic BTEST",
fold_convert (long_integer_type_node, args[1]),
fold_convert (long_integer_type_node, nbits));
}
tmp = fold_build2_loc (input_location, LSHIFT_EXPR, type, tmp = fold_build2_loc (input_location, LSHIFT_EXPR, type,
build_int_cst (type, 1), args[1]); build_int_cst (type, 1), args[1]);
tmp = fold_build2_loc (input_location, BIT_AND_EXPR, type, args[0], tmp); tmp = fold_build2_loc (input_location, BIT_AND_EXPR, type, args[0], tmp);
...@@ -6236,6 +6254,32 @@ gfc_conv_intrinsic_singlebitop (gfc_se * se, gfc_expr * expr, int set) ...@@ -6236,6 +6254,32 @@ gfc_conv_intrinsic_singlebitop (gfc_se * se, gfc_expr * expr, int set)
gfc_conv_intrinsic_function_args (se, expr, args, 2); gfc_conv_intrinsic_function_args (se, expr, args, 2);
type = TREE_TYPE (args[0]); type = TREE_TYPE (args[0]);
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree below = fold_build2_loc (input_location, LT_EXPR,
logical_type_node, args[1],
build_int_cst (TREE_TYPE (args[1]), 0));
tree nbits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type));
tree above = fold_build2_loc (input_location, GE_EXPR,
logical_type_node, args[1], nbits);
tree scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
size_t len_name = strlen (expr->value.function.isym->name);
char *name = XALLOCAVEC (char, len_name + 1);
for (size_t i = 0; i < len_name; i++)
name[i] = TOUPPER (expr->value.function.isym->name[i]);
name[len_name] = '\0';
tree iname = gfc_build_addr_expr (pchar_type_node,
gfc_build_cstring_const (name));
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"POS argument (%ld) out of range 0:%ld "
"in intrinsic %s",
fold_convert (long_integer_type_node, args[1]),
fold_convert (long_integer_type_node, nbits),
iname);
}
tmp = fold_build2_loc (input_location, LSHIFT_EXPR, type, tmp = fold_build2_loc (input_location, LSHIFT_EXPR, type,
build_int_cst (type, 1), args[1]); build_int_cst (type, 1), args[1]);
if (set) if (set)
...@@ -6261,6 +6305,42 @@ gfc_conv_intrinsic_ibits (gfc_se * se, gfc_expr * expr) ...@@ -6261,6 +6305,42 @@ gfc_conv_intrinsic_ibits (gfc_se * se, gfc_expr * expr)
gfc_conv_intrinsic_function_args (se, expr, args, 3); gfc_conv_intrinsic_function_args (se, expr, args, 3);
type = TREE_TYPE (args[0]); type = TREE_TYPE (args[0]);
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree tmp1 = fold_convert (long_integer_type_node, args[1]);
tree tmp2 = fold_convert (long_integer_type_node, args[2]);
tree nbits = build_int_cst (long_integer_type_node,
TYPE_PRECISION (type));
tree below = fold_build2_loc (input_location, LT_EXPR,
logical_type_node, args[1],
build_int_cst (TREE_TYPE (args[1]), 0));
tree above = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, tmp1, nbits);
tree scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"POS argument (%ld) out of range 0:%ld "
"in intrinsic IBITS", tmp1, nbits);
below = fold_build2_loc (input_location, LT_EXPR,
logical_type_node, args[2],
build_int_cst (TREE_TYPE (args[2]), 0));
above = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, tmp2, nbits);
scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"LEN argument (%ld) out of range 0:%ld "
"in intrinsic IBITS", tmp2, nbits);
above = fold_build2_loc (input_location, PLUS_EXPR,
long_integer_type_node, tmp1, tmp2);
scond = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, above, nbits);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"POS(%ld)+LEN(%ld)>BIT_SIZE(%ld) "
"in intrinsic IBITS", tmp1, tmp2, nbits);
}
mask = build_int_cst (type, -1); mask = build_int_cst (type, -1);
mask = fold_build2_loc (input_location, LSHIFT_EXPR, type, mask, args[2]); mask = fold_build2_loc (input_location, LSHIFT_EXPR, type, mask, args[2]);
mask = fold_build1_loc (input_location, BIT_NOT_EXPR, type, mask); mask = fold_build1_loc (input_location, BIT_NOT_EXPR, type, mask);
...@@ -6382,6 +6462,32 @@ gfc_conv_intrinsic_shift (gfc_se * se, gfc_expr * expr, bool right_shift, ...@@ -6382,6 +6462,32 @@ gfc_conv_intrinsic_shift (gfc_se * se, gfc_expr * expr, bool right_shift,
gcc requires a shift width < BIT_SIZE(I), so we have to catch this gcc requires a shift width < BIT_SIZE(I), so we have to catch this
special case. */ special case. */
num_bits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type)); num_bits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type));
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree below = fold_build2_loc (input_location, LT_EXPR,
logical_type_node, args[1],
build_int_cst (TREE_TYPE (args[1]), 0));
tree above = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, args[1], num_bits);
tree scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
size_t len_name = strlen (expr->value.function.isym->name);
char *name = XALLOCAVEC (char, len_name + 1);
for (size_t i = 0; i < len_name; i++)
name[i] = TOUPPER (expr->value.function.isym->name[i]);
name[len_name] = '\0';
tree iname = gfc_build_addr_expr (pchar_type_node,
gfc_build_cstring_const (name));
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"SHIFT argument (%ld) out of range 0:%ld "
"in intrinsic %s",
fold_convert (long_integer_type_node, args[1]),
fold_convert (long_integer_type_node, num_bits),
iname);
}
cond = fold_build2_loc (input_location, GE_EXPR, logical_type_node, cond = fold_build2_loc (input_location, GE_EXPR, logical_type_node,
args[1], num_bits); args[1], num_bits);
...@@ -6436,6 +6542,20 @@ gfc_conv_intrinsic_ishft (gfc_se * se, gfc_expr * expr) ...@@ -6436,6 +6542,20 @@ gfc_conv_intrinsic_ishft (gfc_se * se, gfc_expr * expr)
gcc requires a shift width < BIT_SIZE(I), so we have to catch this gcc requires a shift width < BIT_SIZE(I), so we have to catch this
special case. */ special case. */
num_bits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type)); num_bits = build_int_cst (TREE_TYPE (args[1]), TYPE_PRECISION (type));
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree outside = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, width, num_bits);
gfc_trans_runtime_check (true, false, outside, &se->pre, &expr->where,
"SHIFT argument (%ld) out of range -%ld:%ld "
"in intrinsic ISHFT",
fold_convert (long_integer_type_node, args[1]),
fold_convert (long_integer_type_node, num_bits),
fold_convert (long_integer_type_node, num_bits));
}
cond = fold_build2_loc (input_location, GE_EXPR, logical_type_node, width, cond = fold_build2_loc (input_location, GE_EXPR, logical_type_node, width,
num_bits); num_bits);
se->expr = fold_build3_loc (input_location, COND_EXPR, type, cond, se->expr = fold_build3_loc (input_location, COND_EXPR, type, cond,
...@@ -6454,6 +6574,7 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr) ...@@ -6454,6 +6574,7 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr)
tree lrot; tree lrot;
tree rrot; tree rrot;
tree zero; tree zero;
tree nbits;
unsigned int num_args; unsigned int num_args;
num_args = gfc_intrinsic_argument_list_length (expr); num_args = gfc_intrinsic_argument_list_length (expr);
...@@ -6461,12 +6582,14 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr) ...@@ -6461,12 +6582,14 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr)
gfc_conv_intrinsic_function_args (se, expr, args, num_args); gfc_conv_intrinsic_function_args (se, expr, args, num_args);
type = TREE_TYPE (args[0]);
nbits = build_int_cst (long_integer_type_node, TYPE_PRECISION (type));
if (num_args == 3) if (num_args == 3)
{ {
/* Use a library function for the 3 parameter version. */ /* Use a library function for the 3 parameter version. */
tree int4type = gfc_get_int_type (4); tree int4type = gfc_get_int_type (4);
type = TREE_TYPE (args[0]);
/* We convert the first argument to at least 4 bytes, and /* We convert the first argument to at least 4 bytes, and
convert back afterwards. This removes the need for library convert back afterwards. This removes the need for library
functions for all argument sizes, and function will be functions for all argument sizes, and function will be
...@@ -6480,6 +6603,32 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr) ...@@ -6480,6 +6603,32 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr)
args[1] = convert (int4type, args[1]); args[1] = convert (int4type, args[1]);
args[2] = convert (int4type, args[2]); args[2] = convert (int4type, args[2]);
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree size = fold_convert (long_integer_type_node, args[2]);
tree below = fold_build2_loc (input_location, LE_EXPR,
logical_type_node, size,
build_int_cst (TREE_TYPE (args[1]), 0));
tree above = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, size, nbits);
tree scond = fold_build2_loc (input_location, TRUTH_ORIF_EXPR,
logical_type_node, below, above);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"SIZE argument (%ld) out of range 1:%ld "
"in intrinsic ISHFTC", size, nbits);
tree width = fold_convert (long_integer_type_node, args[1]);
width = fold_build1_loc (input_location, ABS_EXPR,
long_integer_type_node, width);
scond = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, width, size);
gfc_trans_runtime_check (true, false, scond, &se->pre, &expr->where,
"SHIFT argument (%ld) out of range -%ld:%ld "
"in intrinsic ISHFTC",
fold_convert (long_integer_type_node, args[1]),
size, size);
}
switch (expr->ts.kind) switch (expr->ts.kind)
{ {
case 1: case 1:
...@@ -6505,12 +6654,26 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr) ...@@ -6505,12 +6654,26 @@ gfc_conv_intrinsic_ishftc (gfc_se * se, gfc_expr * expr)
return; return;
} }
type = TREE_TYPE (args[0]);
/* Evaluate arguments only once. */ /* Evaluate arguments only once. */
args[0] = gfc_evaluate_now (args[0], &se->pre); args[0] = gfc_evaluate_now (args[0], &se->pre);
args[1] = gfc_evaluate_now (args[1], &se->pre); args[1] = gfc_evaluate_now (args[1], &se->pre);
/* Optionally generate code for runtime argument check. */
if (gfc_option.rtcheck & GFC_RTCHECK_BITS)
{
tree width = fold_convert (long_integer_type_node, args[1]);
width = fold_build1_loc (input_location, ABS_EXPR,
long_integer_type_node, width);
tree outside = fold_build2_loc (input_location, GT_EXPR,
logical_type_node, width, nbits);
gfc_trans_runtime_check (true, false, outside, &se->pre, &expr->where,
"SHIFT argument (%ld) out of range -%ld:%ld "
"in intrinsic ISHFTC",
fold_convert (long_integer_type_node, args[1]),
nbits, nbits);
}
/* Rotate left if positive. */ /* Rotate left if positive. */
lrot = fold_build2_loc (input_location, LROTATE_EXPR, type, args[0], args[1]); lrot = fold_build2_loc (input_location, LROTATE_EXPR, type, args[0], args[1]);
......
2019-07-16 Harald Anlauf <anlauf@gmx.de>
PR fortran/90903
* gfortran.dg/check_bits_1.f90: New testcase.
2019-07-16 Jeff Law <law@redhat.com> 2019-07-16 Jeff Law <law@redhat.com>
PR rtl-optimization/91173 PR rtl-optimization/91173
......
! { dg-do run }
! { dg-options "-fcheck=bits -fdump-tree-original" }
! { dg-shouldfail "Fortran runtime error: SIZE argument (0) out of range 1:32 in intrinsic ISHFTC" }
! { dg-output "At line 44 .*" }
!
! Verify that the runtime checks for the bit manipulation intrinsic functions
! do not generate false-positives
program check
implicit none
integer :: i, k, pos, len, shift, size, nb
nb = bit_size (i)
i = 0
do pos = 0, nb-1
k = ibset (i, pos)
i = ibclr (k, pos)
if (btest (i, pos)) stop 1
end do
do pos = 0, nb
do len = 0, nb-pos
i = ibits (i, pos, len)
end do
end do
do shift = 0, nb
k = ishft (i, shift)
i = ishft (k, -shift)
end do
do shift = 0, nb
k = shiftl (i, shift) ! Fortran 2008
i = shiftr (k, shift)
i = shifta (i, shift)
k = lshift (i, shift) ! GNU extensions
i = rshift (k, shift)
end do
do shift = 0, nb
k = ishftc (i, shift)
i = ishftc (k, -shift)
do size = max (1,shift), nb
k = ishftc (i, shift, size)
i = ishftc (k, -shift, size)
end do
end do
size = 0
! The following line should fail with a runtime error:
k = ishftc (i, 0, size)
! Should never get here with -fcheck=bits
stop 2
end program check
! { dg-final { scan-tree-dump-times "_gfortran_runtime_error_at" 21 "original" } }
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