diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2022-04-12 06:41:46 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2022-04-12 06:41:46 -0700 |
commit | c176b812d68da2284a221c8533a68a1aa290960b (patch) | |
tree | 7de45ed4e6920483fc093c15508ddfc0d242a295 | |
parent | 51ca241352218e6da8db10fe07c90e229a0bc1f8 (diff) | |
download | egawk-c176b812d68da2284a221c8533a68a1aa290960b.tar.gz egawk-c176b812d68da2284a221c8533a68a1aa290960b.tar.bz2 egawk-c176b812d68da2284a221c8533a68a1aa290960b.zip |
@let: bug re-using previously array-typed location as scalar.
* awkgram.y (let_var_list): We must generate the Op_clear_var
instruction even for variables which have initializers.
Otherwise the initialization will bomb if the initializer is
scalar, and the location we are re-using held an array previously.
* interpret.h (r_interpret): Do not use the get_lhs function,
which has complex semantics that depend on the existing type,
and may initialize the value, as well as blow up for an array.
We work directly with var->var_value. If var already has the
new variable type, we have nothing to do. We now unref the
prior value only if the prior type is Node_var, because prior
array values and such don't support unref.
* test/let1.awk: Extension of test case which reproduced this
problem, plus additional test cases for coverage of the duplicated
code in the Yacc grammar.
-rw-r--r-- | awkgram.y | 25 | ||||
-rw-r--r-- | interpret.h | 21 | ||||
-rw-r--r-- | test/let1.awk | 9 |
3 files changed, 44 insertions, 11 deletions
@@ -1644,17 +1644,26 @@ let_var_list } | NAME ASSIGN exp { - add_let(in_function, $1); + bool is_reused_location = add_let(in_function, $1); $1->opcode = Op_push; $1->memory = variable($1->source_line, $1->lextok, Node_var_new); $$ = list_append(mk_assignment(list_create($1), $3, $2), instruction(Op_pop)); - + /* Even for initialized variables, we must emit the Op_clear_var + instruction. This is because the variable may previously have + been an array and is now being assigned as a scalar value. + the Op_clear_var will reset the type to allow that. */ + if (in_loop || is_reused_location) { + INSTRUCTION *clr = instruction(Op_clear_var); + UPREF($1->memory); + clr->memory = $1->memory; + $$ = list_prepend($$, clr); + } } | let_var_list comma NAME ASSIGN exp { INSTRUCTION *assn; - add_let(in_function, $3); + bool is_reused_location = add_let(in_function, $3); $3->opcode = Op_push; $3->memory = variable($3->source_line, $3->lextok, Node_var_new); assn = list_append(mk_assignment(list_create($3), $5, $4), @@ -1663,6 +1672,16 @@ let_var_list $$ = assn; else $$ = list_merge($1, assn); + /* Even for initialized variables, we must emit the Op_clear_var + instruction. This is because the variable may previously have + been an array and is now being assigned as a scalar value. + the Op_clear_var will reset the type to allow that. */ + if (in_loop || is_reused_location) { + INSTRUCTION *clr = instruction(Op_clear_var); + UPREF($3->memory); + clr->memory = $3->memory; + $$ = list_prepend($$, clr); + } } | error { $$ = NULL; } diff --git a/interpret.h b/interpret.h index 0dc261d6..f239406f 100644 --- a/interpret.h +++ b/interpret.h @@ -753,16 +753,21 @@ mod: * initialized frame locations. */ NODE *var = pc->memory; - lhs = get_lhs(var, false); - /* - * If it's already clear, nothing to do - */ - if (*lhs != Nnull_string) { - unref(*lhs); - *lhs = dupnode(Nnull_string); + if (var->type != Node_var_new) { + NODETYPE prevtype = var->type; + var->type = Node_var_new; + var->flags &= MALLOC; + + /* + * If it's already clear, nothing to do + */ + if (var->var_value != Nnull_string) { + if (prevtype == Node_var) + unref(var->var_value); + var->var_value = dupnode(Nnull_string); + } } - var->type = Node_var_new; } break; diff --git a/test/let1.awk b/test/let1.awk index 431fb8b8..90b1248b 100644 --- a/test/let1.awk +++ b/test/let1.awk @@ -43,6 +43,15 @@ BEGIN { BEGIN { @let (i = 3) { } @let (a) { a[0] = 42 } + @let (i = 3) { } +} + +# Yacc rules are duplicated for first versus rest, so we need additional coverage. + +BEGIN { + @let (x, i = 3) { } + @let (x, a) { a[0] = 42 } + @let (x, i = 3) { } } # test clearing uninitialized variables |