Skip to content

Instantly share code, notes, and snippets.

@t-sin
Last active August 23, 2021 16:08
Show Gist options
  • Save t-sin/ccd7a0c5e841fca6467d8e100b8c25ac to your computer and use it in GitHub Desktop.
Save t-sin/ccd7a0c5e841fca6467d8e100b8c25ac to your computer and use it in GitHub Desktop.
maiyamaさんのc-lessonの12_continuationsで気付いたこと

maiyamaさんのc-lessonの12_continuationsのコードを読んでいて気付いたこと

テスト実行毎にスタックがクリアされていない問題

スタックまわりの挙動によって、テストで積まれたスタックの要素が次に残っているようでした。

各ユニットテストの実行前にスタックの初期化(stack.cstatic int top-1に設定しなおすこと)をしていないので、前のテスト実行後の状態が残っています。だいたいのテストはスタックが空になって終了するので問題ないですが、一部(発見したのはtest_eval_num_pop())のテストではスタックに値が残るので、以降はテスト開始時常にその値が入った状態でテストが始まっているようです。ただ、テストではスタックトップの状態をexpects分だけポップして比較しているので問題は起こっていなさそうでした。

具体的には以下のテストが値3をスタックに残します。

// https://github.com/maiyama18/c-lesson/blob/b254d29944211a2ff863a3c3e61cd39f86f02b01/sources/forth_modoki/interpreter/eval.c#L671
static void test_eval_num_pop() {
    char* input = "3 4 pop";
    int expects[1] = { 3 };

    cl_getc_set_src(input);

    eval();

    verify_stack_pop_number_eq(expects, 1);
}

対応策

もしテストの前提として「スタックは空の状態」があるのであれば、スタックの長さをテスト時にassertしておくといい気がします。

なんで気づいたか

ネストしたexec_arrayの実行でなぜ落ちそうなコードが動くのかを調査していました。こんなテスト

{ 3 1 { 1 add 100 } { 2 add } ifelse 200 } exec 300

で動作を確認しようとしました。ちなみに上のコード実行後のスタックの状態は4, 100, 200, 300を想定していました。

eval.cを以下のように変更して

//
diff --git a/sources/forth_modoki/interpreter/eval.c b/sources/forth_modoki/interpreter/eval.c
index f84d4fb..c2e0f81 100644
--- a/sources/forth_modoki/interpreter/eval.c
+++ b/sources/forth_modoki/interpreter/eval.c
@@ -878,14 +878,14 @@ static void test_eval_ifelse_false() {
 }

 static void test_eval_ifelse_true_in_executable_array() {
-    char* input = "{ 3 1 { 1 add } { 2 add } ifelse } exec";
-    int expects[1] = { 4 };
+    char* input = "{ 3 1 { 1 add 100 } { 2 add } ifelse 200 } exec 300";
+    int expects[4] = { 4 , 100, 200, 300 };

     cl_getc_set_src(input);

     eval();
-
-    verify_stack_pop_number_eq(expects, 1);
+    stack_print_all();
+    verify_stack_pop_number_eq(expects, 4);
 }

 static void test_eval_ifelse_false_in_executable_array() {

スタックの中身を出力すると以下のようになりました。

$ gcc *.c && ./a.out
--- stack ---
4: ET_NUMBER 300
3: ET_NUMBER 200
2: ET_NUMBER 100
1: ET_NUMBER 4
0: ET_NUMBER 3
-------------
a.out: eval.c:381: verify_stack_pop_number_eq: Assertion `actual.u.number == expects[i]' failed.

実行順は正しいのですが、なにか余計な3が入っています(余計なものは入っているのですが実行順は正しそうなので本来の目的は達成していません)。addが複数回実行されたりとかしてるんだろうか、と思ってadd_opのところにprintf仕込んでも1回しか実行されておりませんでした。

そこでこのユニットテスト以外をコメントアウトして実行してみました。以下のような変更です。

diff --git a/sources/forth_modoki/interpreter/eval.c b/sources/forth_modoki/interpreter/eval.c
index f84d4fb..7353eea 100644
--- a/sources/forth_modoki/interpreter/eval.c
+++ b/sources/forth_modoki/interpreter/eval.c
@@ -878,14 +878,14 @@ static void test_eval_ifelse_false() {
 }
 
 static void test_eval_ifelse_true_in_executable_array() {
-    char* input = "{ 3 1 { 1 add } { 2 add } ifelse } exec";
-    int expects[1] = { 4 };
+    char* input = "{ 3 1 { 1 add 100 } { 2 add } ifelse 200 } exec 300";
+    int expects[4] = { 4 , 100, 200, 300 };
 
     cl_getc_set_src(input);
 
     eval();
-
-    verify_stack_pop_number_eq(expects, 1);
+    stack_print_all();
+    verify_stack_pop_number_eq(expects, 4);
 }
 
 static void test_eval_ifelse_false_in_executable_array() {
@@ -1090,71 +1090,73 @@ static void test_dict_overwritten_not_head_name_found() {
 void exec_tests() {
     register_primitives();
 
-    test_eval_num_one();
-    test_eval_num_two();
-    test_eval_num_two_separated_by_newline();
-    test_eval_num_add();
-    test_eval_num_add_many();
-    test_eval_num_sub();
-    test_eval_num_mul();
-    test_eval_num_div();
-    test_eval_num_mod();
-    test_eval_num_eq_true();
-    test_eval_num_eq_false();
-    test_eval_num_neq_true();
-    test_eval_num_neq_false();
-    test_eval_num_gt_true_when_greater();
-    test_eval_num_gt_false_when_equal();
-    test_eval_num_gt_false_when_less();
-    test_eval_num_ge_true_when_greater();
-    test_eval_num_ge_true_when_equal();
-    test_eval_num_ge_false_when_less();
-    test_eval_num_lt_false_when_greater();
-    test_eval_num_lt_false_when_equal();
-    test_eval_num_lt_true_when_less();
-    test_eval_num_le_false_when_greater();
-    test_eval_num_le_true_when_equal();
-    test_eval_num_le_true_when_less();
-
-    test_eval_num_pop();
-    test_eval_num_exch();
-    test_eval_num_dup();
-    test_eval_num_index();
-    test_eval_num_index_top();
-    test_eval_num_roll();
-
-    test_eval_num_def();
-
-    test_eval_exec_array_num();
-    test_eval_exec_array_num_many();
-    test_eval_exec_array_func();
-    test_eval_exec_array_num_nested();
-    test_eval_exec_array_func_nested();
-    test_eval_exec_array_exec_nested();
-
-    test_eval_exec_nums();
-    test_eval_exec_func();
-    test_eval_exec_func_in_executable_array();
-    test_eval_if_true();
-    test_eval_if_false();
-    test_eval_ifelse_true();
-    test_eval_ifelse_false();
+    // test_eval_num_one();
+    // test_eval_num_two();
+    // test_eval_num_two_separated_by_newline();
+    // test_eval_num_add();
+    // test_eval_num_add_many();
+    // test_eval_num_sub();
+    // test_eval_num_mul();
+    // test_eval_num_div();
+    // test_eval_num_mod();
+    // test_eval_num_eq_true();
+
+    // test_eval_num_eq_false();
+    // test_eval_num_neq_true();
+    // test_eval_num_neq_false();
+    // test_eval_num_gt_true_when_greater();
+    // test_eval_num_gt_false_when_equal();
+    // test_eval_num_gt_false_when_less();
+    // test_eval_num_ge_true_when_greater();
+    // test_eval_num_ge_true_when_equal();
+    // test_eval_num_ge_false_when_less();
+    // test_eval_num_lt_false_when_greater();
+
+    // test_eval_num_lt_false_when_equal();
+    // test_eval_num_lt_true_when_less();
+    // test_eval_num_le_false_when_greater();
+    // test_eval_num_le_true_when_equal();
+    // test_eval_num_le_true_when_less();
+
+    // test_eval_num_pop();
+    // test_eval_num_exch();
+    // test_eval_num_dup();
+    // test_eval_num_index();
+    // test_eval_num_index_top();
+    // test_eval_num_roll();
+
+    // test_eval_num_def();
+
+    // test_eval_exec_array_num();
+    // test_eval_exec_array_num_many();
+    // test_eval_exec_array_func();
+    // test_eval_exec_array_num_nested();
+    // test_eval_exec_array_func_nested();
+    // test_eval_exec_array_exec_nested();
+
+    // test_eval_exec_nums();
+    // test_eval_exec_func();
+    // test_eval_exec_func_in_executable_array();
+    // test_eval_if_true();
+    // test_eval_if_false();
+    // test_eval_ifelse_true();
+    // test_eval_ifelse_false();
     test_eval_ifelse_true_in_executable_array();
-    test_eval_ifelse_false_in_executable_array();
-    test_eval_ifelse_then_num();
-    test_eval_ifelse_incomplete_executable_array();
-    test_eval_while();
-    test_eval_while_in_executable_array();
-    test_eval_while_then_num();
-
-    test_eval_comments();
-
-    // test_eval_factorial();
-
-    test_dict_no_element_name_not_found();
-    test_dict_name_found();
-    test_dict_name_found_when_hash_collides();
-    test_dict_name_not_found();
-    test_dict_overwritten_name_found();
-    test_dict_overwritten_not_head_name_found();
+    // test_eval_ifelse_false_in_executable_array();
+    // test_eval_ifelse_then_num();
+    // test_eval_ifelse_incomplete_executable_array();
+    // test_eval_while();
+    // test_eval_while_in_executable_array();
+    // test_eval_while_then_num();
+
+    // test_eval_comments();
+
+    // // test_eval_factorial();
+
+    // test_dict_no_element_name_not_found();
+    // test_dict_name_found();
+    // test_dict_name_found_when_hash_collides();
+    // test_dict_name_not_found();
+    // test_dict_overwritten_name_found();
+    // test_dict_overwritten_not_head_name_found();
 }
\ No newline at end of file

するとスタックの内容が以下のようになりました。3消えました! expectsのスタックの順が逆なのでassertで落ちてますね🦀

$ gcc *.c && ./a.out
--- stack ---
3: ET_NUMBER 300
2: ET_NUMBER 200
1: ET_NUMBER 100
0: ET_NUMBER 4
-------------
a.out: eval.c:381: verify_stack_pop_number_eq: Assertion `actual.u.number == expects[i]' failed.

スタックにどこかでゴミが残ってそうだなーと考えてスタックのコードを見てみたところ、スタックをテスト毎に初期化するコードがなさげだったので、前のテストの状態が残っていそうだな、と気づいたのでした。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment