summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSøren Sandmann Pedersen <ssp@redhat.com>2012-06-16 22:40:50 -0400
committerSøren Sandmann Pedersen <ssp@redhat.com>2012-06-16 22:40:50 -0400
commit88cc707876ff3299f36e290aa4f691adeb13450a (patch)
tree4e71ed55dadd8ab481805e970b3668fd7525fda9
parenteb483ba44ce9953e0845fe99c452dcfa028a2a0d (diff)
Fix bug with switch statements and goto
Previously, the condition expression in a switch statement would be generated once, and then each case would do "dup; compare". After the switch, the condition expression would be popped. The problem with this is that if you jump out of the switch, the expression would be left on the stack. So instead generate a temporary variable to hold the condition expression, then load that variable for each case.
-rw-r--r--graph.c9
-rw-r--r--parser.c23
-rw-r--r--type-check.c2
3 files changed, 26 insertions, 8 deletions
diff --git a/graph.c b/graph.c
index a7f3b7f..610f29b 100644
--- a/graph.c
+++ b/graph.c
@@ -559,14 +559,18 @@ graph_switch_statement (ast_switch_statement_t *switch_, node_t *node)
for (i = 0; cases[i] != NULL; ++i)
cases[i]->common.label_node = node_new_label (NULL, (ast_t *)cases[i]);
- node = graph_expression (switch_->condition, node);
for (i = 0; cases[i] != NULL; ++i)
{
if (cases[i]->common.type == AST_EXPRESSION_CASE)
{
ast_expression_case_t *case_ = &cases[i]->expression;
- node = node_new_dup (node, ast);
+ /* We code generate the condition expression for each case. This is not
+ * a problem because the parser has made sure that the expression
+ * is just a variable load so there are no issues with the expression
+ * having side-effects.
+ */
+ node = graph_expression (switch_->condition, node);
node = node_new_literal (case_->expr->common.constant_value,
case_->expr->common.type_spec,
node, ast);
@@ -606,7 +610,6 @@ graph_switch_statement (ast_switch_statement_t *switch_, node_t *node)
}
node = node_insert_after (switch_->done_node, node);
- node = node_new_pop (node, ast);
return node;
}
diff --git a/parser.c b/parser.c
index 63d537c..d40558f 100644
--- a/parser.c
+++ b/parser.c
@@ -655,8 +655,6 @@ parse_switch_statement (const token_t *in,
const token_t *t = in;
ast_expression_t *condition;
ast_case_t **cases;
- ast_statement_t *done =
- ast_statement_new_label (unique_name ("break_switch"));
if ((t = parse_token (t, TOKEN_SWITCH)) &&
(t = parse_token (t, TOKEN_LPAREN)) &&
@@ -666,9 +664,26 @@ parse_switch_statement (const token_t *in,
(t = parse_cases (t, &cases)) &&
(t = parse_token (t, TOKEN_RBRACE)))
{
+ /* Switch statements are desugared into a nest of if
+ * statements because it's quite difficult to implement
+ * switch statements without relying
+ */
+ const char *cond_name = unique_name ("switch_condition");
+ ast_definition_t *variable = ast_definition_new_variable (
+ cond_name, ast_type_spec_new (AST_INFERRED_TYPE));
+ ast_expression_t *cond_expr = ast_expression_new_binary (
+ AST_ASSIGN,
+ ast_expression_new_identifier (cond_name),
+ condition);
+
+ condition->common.type_inferrer = &variable->variable;
+
*result = ast_statement_new_compound (
- ast_statement_new_switch (condition, cases),
- done);
+ ast_statement_new_compound (
+ ast_statement_new_definition (variable),
+ ast_statement_new_expression (cond_expr)),
+ ast_statement_new_switch (
+ ast_expression_new_identifier (cond_name), cases));
return t;
}
diff --git a/type-check.c b/type-check.c
index f200d1a..9470237 100644
--- a/type-check.c
+++ b/type-check.c
@@ -870,9 +870,9 @@ type_check_pass2 (ast_t *ast)
}
else if (ast_is (ast, AST_STATEMENT, AST_SWITCH_STATEMENT))
{
- int i;
ast_type_spec_t *cond_type = get_type (ast->statement.switch_.condition);
ast_case_t **cases = ast->statement.switch_.cases;
+ int i;
for (i = 0; cases[i] != NULL; ++i)
{