aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2022-04-12 06:41:46 -0700
committerKaz Kylheku <kaz@kylheku.com>2022-04-12 06:41:46 -0700
commitc176b812d68da2284a221c8533a68a1aa290960b (patch)
tree7de45ed4e6920483fc093c15508ddfc0d242a295
parent51ca241352218e6da8db10fe07c90e229a0bc1f8 (diff)
downloadegawk-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.y25
-rw-r--r--interpret.h21
-rw-r--r--test/let1.awk9
3 files changed, 44 insertions, 11 deletions
diff --git a/awkgram.y b/awkgram.y
index 285c233a..423cf66b 100644
--- a/awkgram.y
+++ b/awkgram.y
@@ -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