Skip to content

Commit

Permalink
[#724] Fix edge cases with $ZATRANSFORM() and make function more robust
Browse files Browse the repository at this point in the history
When reviewing op_zatransform.c to add testing for new functionality added in the recent merge
of the upstream V63010 version into YottaDB, we noted some coding issues that could cause
incorrect operation currently as well as be a source for future problems in the code. The
edge cases are as follows:

1. On line 132 of op_fnzatransform.c, a test is made of (0 == msrc->str.len) before ascertaining
that the mval has MV_STR set in msrc->mvtype. This causes two invocations of $ZATRANSFORM() with
such as 'WRITE $ZATRANSFORM(123,0)' and 'SET X=1 WRITE $ZATRANSFORM(123*x,0)' to display different
output values which should not be possible. This test is changed to also verify that the input
value *IS* a string before checking its length.

2. On lines 157-163 of op_fnzatransform.c, we are setting TREF(transform) based on the value of
the 4th input parameter (forceStr) that denotes whether the input is a string or not. But if one
passes in a string but doesn't set the forceStr to 1 (leaving it to default to 0), this turns off
transformation which doesn't follow logically. Since no tying of forcing the input to be string
to whether or not transformation should be done is documented anywhere, we have removed this tying
in favor of assuming we will always be doing transformation and asserting that is true.

For the robustness aspect, as far as we could ascertain without a deep-dive review of associated
calls, there is nothing dangerously exposed at this time but if code were added in the future
without knowledge of the potential problems this code has, results would not be good and could
be very tricky to track down.

Specifically, this code, early on, makes a copy of the input mval which comes direct from generated
M code. This is a coding no-no because if ANY stringpool allocation happens that could potentially
trigger a garbage collection, the garbage collector does not know about the copied mval so cannot
update any stringpool pointer it has if the data gets moved as a result of the garbage collection.
The result is a local mval copy can end up pointing to garbage.

Right now, there are no references to the copied variable after actions that can cause garbage
collections (of which there are several in this routine) so the design is (probably) safe
though that has not been conclusively proven. Out of an abundance of caution, we are going
to make this safe now and for the future.

To make this module safe for future expansion is fairly simple. One simply needs to use the
PUSH_MV_STENT() macro with the MVST_MVAL type which places an mv_stent block on the M stack
giving one an embedded mval that is known to the garbage collector. Then we just point the
"src" variable at this mval, then place a POP_MV_STENT() at the end of the routine and we
are done. We don't have to worry about error returns because an error always pops the current
M stack frame which would also pop this mv_stent off of the M stack.
  • Loading branch information
estess committed Apr 23, 2021
1 parent 0820f22 commit 0b63ce1
Showing 1 changed file with 24 additions and 33 deletions.
57 changes: 24 additions & 33 deletions sr_port/op_fnzatransform.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2012-2019 Fidelity National Information *
* Services, Inc. and/or its subsidiaries. All rights reserved. *
* *
* Copyright (c) 2020 YottaDB LLC and/or its subsidiaries. *
* Copyright (c) 2020-2021 YottaDB LLC and/or its subsidiaries. *
* All rights reserved. *
* *
* This source code contains the intellectual property *
Expand Down Expand Up @@ -40,13 +40,14 @@
#include "gvsub2str.h"
#include "io.h"
#include "gtm_descript.h"
#include "mv_stent.h"

#define MAX_KEY_SIZE (MAX_KEY_SZ - 4) /* internal and external maximums differ */

GBLREF gv_namehead *gv_target;
GBLREF gv_namehead *reset_gv_target;
GBLREF spdesc stringpool;
GBLREF boolean_t save_transform;
GBLREF mv_stent *mv_chain;

LITREF mval literal_null;

Expand All @@ -65,7 +66,6 @@ CONDITION_HANDLER(op_fnzatransform_ch)
{
START_CH(TRUE);
RESET_GV_TARGET(DO_GVT_GVKEY_CHECK);
TREF(transform) = save_transform;
if (transform_direction)
DEBUG_ONLY(TREF(skip_mv_num_approx_assert) = FALSE);
if (ERR_ZATRANSERR != SIGNAL)
Expand Down Expand Up @@ -109,7 +109,7 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
unsigned char buff[MAX_KEY_SZ + 1], msrcbuff[MAX_KEY_SZ + 1];
collseq *csp;
gv_namehead temp_gv_target;
mval *src, lcl_src;
mval *src;
mstr opstr;
boolean_t coll_noxutil = 0;
boolean_t coll_failxutil = 0;
Expand All @@ -129,19 +129,16 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
} else
csp = NULL; /* Do not issue COLLATIONUNDEF for 0 collation */

if (0 == msrc->str.len)
if (MV_IS_STRING(msrc) && (0 == msrc->str.len))
{ /* Null string, return it back */
*dst=*msrc;
*dst = *msrc;
return;
}
/* Temporarily repoint global variables "gv_target" and "transform".
* They are needed by mval2subsc/gvsub2str "transform" and "gv_target->collseq".
* Note that transform is usually ON, specifying that collation transformation is "enabled",
* and is only shut off for minor periods when something is being critically formatted (like
* we're doing here).
/* Ensure global variables "gv_target" and "transform" are set as they are needed by
* mval2subsc/gvsub2str. "transform" is already set to TRUE (the correct value).
* Assert that. So need to only set "gv_target".
*/
save_transform = TREF(transform);
assert(save_transform);
assert(TREF(transform));
assert(INVALID_GV_TARGET == reset_gv_target);
reset_gv_target = gv_target;
gv_target = &temp_gv_target;
Expand All @@ -151,19 +148,21 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
gvkey->prev = 0;
gvkey->top = DBKEYSIZE(MAX_KEY_SZ);
gvkey->end = 0;
/* Avoid changing the characteristics of the caller's MVAL */
lcl_src = *msrc;
src = &lcl_src;
/* Avoid changing the characteristics of the caller's mval. Protect value of caller's value even after potential
* stringpool garbage collections (which a local copy does not do. Note, we only need to pop the mv_stent we push
* onto the M stack on a normal return. Error returns always unwind the current M stack frame which will also
* unwind this mv_stent.
*/
PUSH_MV_STENT(MVST_MVAL); /* Create a temporary on M stack */
src = &mv_chain->mv_st_cont.mvs_mval;
*src = *msrc;
if (forceStr)
{
TREF(transform) = TRUE;
MV_FORCE_STR(src);
src->mvtype |= MV_NUM_APPROX; /* Force the mval to be treated as a string */
} else
TREF(transform) = FALSE;
src->mvtype |= MV_NUM_APPROX; /* Force the mval to be treated as a string */
}
ESTABLISH(op_fnzatransform_ch);
transform_direction = (0 == reverse);

/* Previously the code relied on all non-zero values triggering reverse mapping.
* We now use a switch to catch explicit values (with a large default for the remaining non-zero cases).
*/
Expand All @@ -178,7 +177,7 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
key = gvkey->base;
*key++ = KEY_DELIMITER;
gvkey->end++;
DEBUG_ONLY(TREF(skip_mv_num_approx_assert) = TRUE;)
DEBUG_ONLY(TREF(skip_mv_num_approx_assert) = TRUE);
if (NULL == (key = mval2subsc(src, gvkey, TRUE)))
rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZATRANSERR);
COPY_ARG_TO_STRINGPOOL(dst, &gvkey->base[gvkey->end], &gvkey->base[1]);
Expand All @@ -199,9 +198,8 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
res = (forceStr) ? zat_mprev_chs(c) : zat_mprev_chn(c);
/* If we can't go down, return null string */
if (-1 == res)
{
*dst = literal_null;
} else
else
{
c = (unsigned char)res;
COPY_ARG_TO_STRINGPOOL(dst, (&c)+1, &c);
Expand Down Expand Up @@ -307,20 +305,15 @@ void op_fnzatransform(mval *msrc, int col, int reverse, int forceStr, mval *dst)
COPY_ARG_TO_STRINGPOOL(dst, key, &buff[0]);
break;
}
/* Restore transform and gv_target */
/* Pop the mv_stent pointed to by "src" then restore gv_target */
REVERT;
POP_MV_STENT();
RESET_GV_TARGET(DO_GVT_GVKEY_CHECK);
TREF(transform) = save_transform;

/* Now that we have restored our state, if we failed due to no xutil helper, or xutil err: invoke an error */
if (coll_failxutil)
{
rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZATRANSCOL);
}
if (coll_noxutil)
{
rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_COLLATIONUNDEF, 1, col);
}
}

/* The following routines implement numeric aware
Expand Down Expand Up @@ -375,9 +368,7 @@ static int zat_mprev_chn(unsigned char c)
} else
{ /* we are a digit */
if ('0' < c)
{
retval = --c;
}
}
return retval;
}
Expand Down

0 comments on commit 0b63ce1

Please sign in to comment.