diff --git a/.ci/check-format.sh b/.ci/check-format.sh new file mode 100755 index 000000000..a90113e60 --- /dev/null +++ b/.ci/check-format.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +SOURCES=$(find $(git rev-parse --show-toplevel) | egrep "\.(cpp|h)\$") + +set -x + +for file in ${SOURCES}; +do + clang-format-12 ${file} > expected-format + diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format +done +exit $(clang-format-12 --output-replacements-xml ${SOURCES} | egrep -c "") diff --git a/.ci/check-sanity.sh b/.ci/check-sanity.sh new file mode 100755 index 000000000..6fd38faa3 --- /dev/null +++ b/.ci/check-sanity.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +SHA1SUM=$(which sha1sum) +if [ $? -ne 0 ]; then + SHA1SUM=shasum +fi + +$SHA1SUM -c scripts/checksums +if [ $? -ne 0 ]; then + echo "[!] You are not allowed to change the header file queue.h or list.h" >&2 + exit 1 +fi diff --git a/.clang-format b/.clang-format index 9705d27f7..30c5a3f49 100644 --- a/.clang-format +++ b/.clang-format @@ -13,3 +13,14 @@ UseTab: Never IndentWidth: 4 BreakBeforeBraces: Linux AccessModifierOffset: -4 +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH + - list_for_each + - list_for_each_safe + - list_for_each_entry + - list_for_each_entry_safe + - hlist_for_each_entry + - rb_list_foreach + - rb_list_foreach_safe diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..c48bd9c7e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,30 @@ +name: CI + +on: [push, pull_request] + +jobs: + lab0-c: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2.4.0 + - name: install-dependencies + run: | + .ci/check-sanity.sh + sudo apt-get update + sudo apt-get -q -y install build-essential cppcheck + - name: make + run: make + - name: make check + run: make check + - name: make test + run: make test + + coding-style: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2.4.0 + - name: coding convention + run: | + sudo apt-get install -q -y clang-format-12 + sh .ci/check-format.sh + shell: bash diff --git a/README.md b/README.md index 8a4d557ba..5de1b2b2b 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ of the skills tested are: * Implementing robust code that operates correctly with invalid arguments, including NULL pointers. The lab involves implementing a queue, supporting both last-in, first-out (LIFO) and first-in-first-out (FIFO) -queueing disciplines. The underlying data structure is a singly-linked list, enhanced to make some of the -operations more efficient. +queueing disciplines. The underlying data structure is a circular doubly-linked list, enhanced to make some of +the operations more efficient. ## Prerequisites diff --git a/console.c b/console.c index 57a8d07d3..6ad338ac9 100644 --- a/console.c +++ b/console.c @@ -69,14 +69,6 @@ static bool has_infile = false; static cmd_function quit_helpers[MAXQUIT]; static int quit_helper_cnt = 0; -static bool do_quit_cmd(int argc, char *argv[]); -static bool do_help_cmd(int argc, char *argv[]); -static bool do_option_cmd(int argc, char *argv[]); -static bool do_source_cmd(int argc, char *argv[]); -static bool do_log_cmd(int argc, char *argv[]); -static bool do_time_cmd(int argc, char *argv[]); -static bool do_comment_cmd(int argc, char *argv[]); - static void init_in(); static bool push_file(char *fname); @@ -84,33 +76,6 @@ static void pop_file(); static bool interpret_cmda(int argc, char *argv[]); -/* Initialize interpreter */ -void init_cmd() -{ - cmd_list = NULL; - param_list = NULL; - err_cnt = 0; - quit_flag = false; - - add_cmd("help", do_help_cmd, " | Show documentation"); - add_cmd("option", do_option_cmd, - " [name val] | Display or set options"); - add_cmd("quit", do_quit_cmd, " | Exit program"); - add_cmd("source", do_source_cmd, - " file | Read commands from source file"); - add_cmd("log", do_log_cmd, " file | Copy output to file"); - add_cmd("time", do_time_cmd, " cmd arg ... | Time command execution"); - add_cmd("#", do_comment_cmd, " ... | Display comment"); - add_param("simulation", &simulation, "Start/Stop simulation mode", NULL); - add_param("verbose", &verblevel, "Verbosity level", NULL); - add_param("error", &err_limit, "Number of errors until exit", NULL); - add_param("echo", &echo, "Do/don't echo commands", NULL); - - init_in(); - init_time(&last_time); - first_time = last_time; -} - /* Add a new command */ void add_cmd(char *name, cmd_function operation, char *documentation) { @@ -264,7 +229,7 @@ void set_echo(bool on) } /* Built-in commands */ -static bool do_quit_cmd(int argc, char *argv[]) +static bool do_quit(int argc, char *argv[]) { cmd_ptr c = cmd_list; bool ok = true; @@ -292,7 +257,7 @@ static bool do_quit_cmd(int argc, char *argv[]) return ok; } -static bool do_help_cmd(int argc, char *argv[]) +static bool do_help(int argc, char *argv[]) { cmd_ptr clist = cmd_list; report(1, "Commands:", argv[0]); @@ -336,7 +301,7 @@ bool get_int(char *vname, int *loc) return true; } -static bool do_option_cmd(int argc, char *argv[]) +static bool do_option(int argc, char *argv[]) { if (argc == 1) { param_ptr plist = param_list; @@ -383,7 +348,7 @@ static bool do_option_cmd(int argc, char *argv[]) return true; } -static bool do_source_cmd(int argc, char *argv[]) +static bool do_source(int argc, char *argv[]) { if (argc < 2) { report(1, "No source file given"); @@ -398,7 +363,7 @@ static bool do_source_cmd(int argc, char *argv[]) return true; } -static bool do_log_cmd(int argc, char *argv[]) +static bool do_log(int argc, char *argv[]) { if (argc < 2) { report(1, "No log file given"); @@ -412,7 +377,7 @@ static bool do_log_cmd(int argc, char *argv[]) return result; } -static bool do_time_cmd(int argc, char *argv[]) +static bool do_time(int argc, char *argv[]) { double delta = delta_time(&last_time); bool ok = true; @@ -432,6 +397,31 @@ static bool do_time_cmd(int argc, char *argv[]) return ok; } +/* Initialize interpreter */ +void init_cmd() +{ + cmd_list = NULL; + param_list = NULL; + err_cnt = 0; + quit_flag = false; + + ADD_COMMAND(help, " | Show documentation"); + ADD_COMMAND(option, " [name val] | Display or set options"); + ADD_COMMAND(quit, " | Exit program"); + ADD_COMMAND(source, " file | Read commands from source file"); + ADD_COMMAND(log, " file | Copy output to file"); + ADD_COMMAND(time, " cmd arg ... | Time command execution"); + add_cmd("#", do_comment_cmd, " ... | Display comment"); + add_param("simulation", &simulation, "Start/Stop simulation mode", NULL); + add_param("verbose", &verblevel, "Verbosity level", NULL); + add_param("error", &err_limit, "Number of errors until exit", NULL); + add_param("echo", &echo, "Do/don't echo commands", NULL); + + init_in(); + init_time(&last_time); + first_time = last_time; +} + /* Create new buffer for named file. * Name == NULL for stdin. * Return true if successful. @@ -600,7 +590,7 @@ bool finish_cmd() { bool ok = true; if (!quit_flag) - ok = ok && do_quit_cmd(0, NULL); + ok = ok && do_quit(0, NULL); has_infile = false; return ok && err_cnt == 0; } diff --git a/console.h b/console.h index 51e42f97b..9a1b61199 100644 --- a/console.h +++ b/console.h @@ -43,6 +43,7 @@ void init_cmd(); /* Add a new command */ void add_cmd(char *name, cmd_function operation, char *documentation); +#define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg) /* Add a new parameter */ void add_param(char *name, diff --git a/dudect/constant.c b/dudect/constant.c index f511e41b6..13c607ce3 100644 --- a/dudect/constant.c +++ b/dudect/constant.c @@ -10,42 +10,53 @@ #include "queue.h" #include "random.h" -#define NR_MEASURE 150 +#define N_MEASURE 150 + /* Allow random number range from 0 to 65535 */ const size_t chunk_size = 16; + /* Number of measurements per test */ -const size_t number_measurements = NR_MEASURE; +const size_t n_measure = N_MEASURE; + const int drop_size = 20; + /* Maintain a queue independent from the qtest since * we do not want the test to affect the original functionality */ -static queue_t *q = NULL; -static char random_string[NR_MEASURE][8]; +static struct list_head *l = NULL; + +static char random_string[N_MEASURE][8]; static int random_string_iter = 0; -enum { test_insert_tail, test_size }; + +enum { + test_insert_head, + test_insert_tail, + test_remove_head, + test_remove_tail, +}; /* Implement the necessary queue interface to simulation */ void init_dut(void) { - q = NULL; + l = NULL; } char *get_random_string(void) { - random_string_iter = (random_string_iter + 1) % NR_MEASURE; + random_string_iter = (random_string_iter + 1) % N_MEASURE; return random_string[random_string_iter]; } void prepare_inputs(uint8_t *input_data, uint8_t *classes) { - randombytes(input_data, number_measurements * chunk_size); - for (size_t i = 0; i < number_measurements; i++) { + randombytes(input_data, n_measure * chunk_size); + for (size_t i = 0; i < n_measure; i++) { classes[i] = randombit(); if (classes[i] == 0) memset(input_data + (size_t) i * chunk_size, 0, chunk_size); } - for (size_t i = 0; i < NR_MEASURE; ++i) { + for (size_t i = 0; i < N_MEASURE; ++i) { /* Generate random string */ randombytes((uint8_t *) random_string[i], 7); random_string[i][7] = 0; @@ -57,9 +68,25 @@ void measure(int64_t *before_ticks, uint8_t *input_data, int mode) { - assert(mode == test_insert_tail || mode == test_size); - if (mode == test_insert_tail) { - for (size_t i = drop_size; i < number_measurements - drop_size; i++) { + assert(mode == test_insert_head || mode == test_insert_tail || + mode == test_remove_head || mode == test_remove_tail); + + switch (mode) { + case test_insert_head: + for (size_t i = drop_size; i < n_measure - drop_size; i++) { + char *s = get_random_string(); + dut_new(); + dut_insert_head( + get_random_string(), + *(uint16_t *) (input_data + i * chunk_size) % 10000); + before_ticks[i] = cpucycles(); + dut_insert_head(s, 1); + after_ticks[i] = cpucycles(); + dut_free(); + } + break; + case test_insert_tail: + for (size_t i = drop_size; i < n_measure - drop_size; i++) { char *s = get_random_string(); dut_new(); dut_insert_head( @@ -70,8 +97,37 @@ void measure(int64_t *before_ticks, after_ticks[i] = cpucycles(); dut_free(); } - } else { - for (size_t i = drop_size; i < number_measurements - drop_size; i++) { + break; + case test_remove_head: + for (size_t i = drop_size; i < n_measure - drop_size; i++) { + dut_new(); + dut_insert_head( + get_random_string(), + *(uint16_t *) (input_data + i * chunk_size) % 10000); + before_ticks[i] = cpucycles(); + element_t *e = q_remove_head(l, NULL, 0); + after_ticks[i] = cpucycles(); + if (e) + q_release_element(e); + dut_free(); + } + break; + case test_remove_tail: + for (size_t i = drop_size; i < n_measure - drop_size; i++) { + dut_new(); + dut_insert_head( + get_random_string(), + *(uint16_t *) (input_data + i * chunk_size) % 10000); + before_ticks[i] = cpucycles(); + element_t *e = q_remove_tail(l, NULL, 0); + after_ticks[i] = cpucycles(); + if (e) + q_release_element(e); + dut_free(); + } + break; + default: + for (size_t i = drop_size; i < n_measure - drop_size; i++) { dut_new(); dut_insert_head( get_random_string(), diff --git a/dudect/constant.h b/dudect/constant.h index 81ed0928e..a5b820846 100644 --- a/dudect/constant.h +++ b/dudect/constant.h @@ -2,29 +2,29 @@ #define DUDECT_CONSTANT_H #include -#define dut_new() ((void) (q = q_new())) +#define dut_new() ((void) (l = q_new())) #define dut_size(n) \ do { \ for (int __iter = 0; __iter < n; ++__iter) \ - q_size(q); \ + q_size(l); \ } while (0) #define dut_insert_head(s, n) \ do { \ int j = n; \ while (j--) \ - q_insert_head(q, s); \ + q_insert_head(l, s); \ } while (0) #define dut_insert_tail(s, n) \ do { \ int j = n; \ while (j--) \ - q_insert_tail(q, s); \ + q_insert_tail(l, s); \ } while (0) -#define dut_free() ((void) (q_free(q))) +#define dut_free() ((void) (q_free(l))) void init_dut(); void prepare_inputs(uint8_t *input_data, uint8_t *classes); diff --git a/dudect/fixture.c b/dudect/fixture.c index cfc7f3605..5c8082169 100644 --- a/dudect/fixture.c +++ b/dudect/fixture.c @@ -24,7 +24,6 @@ * * - as long as any of the different test fails, the code will be deemed * variable time. - * */ #include "fixture.h" @@ -39,19 +38,19 @@ #include "constant.h" #include "ttest.h" -#define enough_measurements 10000 +#define enough_measure 10000 #define test_tries 10 extern const int drop_size; extern const size_t chunk_size; -extern const size_t number_measurements; +extern const size_t n_measure; static t_ctx *t; /* threshold values for Welch's t-test */ -#define t_threshold_bananas \ - 500 /* Test failed with overwhelming probability \ - */ -#define t_threshold_moderate 10 /* Test failed */ +enum { + t_threshold_bananas = 500, /* Test failed with overwhelming probability */ + t_threshold_moderate = 10, /* Test failed */ +}; static void __attribute__((noreturn)) die(void) { @@ -62,19 +61,18 @@ static void differentiate(int64_t *exec_times, const int64_t *before_ticks, const int64_t *after_ticks) { - for (size_t i = 0; i < number_measurements; i++) { + for (size_t i = 0; i < n_measure; i++) exec_times[i] = after_ticks[i] - before_ticks[i]; - } } static void update_statistics(const int64_t *exec_times, uint8_t *classes) { - for (size_t i = 0; i < number_measurements; i++) { + for (size_t i = 0; i < n_measure; i++) { int64_t difference = exec_times[i]; - /* Cpu cycle counter overflowed or dropped measurement */ - if (difference <= 0) { + /* CPU cycle counter overflowed or dropped measurement */ + if (difference <= 0) continue; - } + /* do a t-test on the execution time */ t_push(t, difference, classes[i]); } @@ -88,14 +86,13 @@ static bool report(void) printf("\033[A\033[2K"); printf("meas: %7.2lf M, ", (number_traces_max_t / 1e6)); - if (number_traces_max_t < enough_measurements) { + if (number_traces_max_t < enough_measure) { printf("not enough measurements (%.0f still to go).\n", - enough_measurements - number_traces_max_t); + enough_measure - number_traces_max_t); return false; } - /* - * max_t: the t statistic value + /* max_t: the t statistic value * max_tau: a t value normalized by sqrt(number of measurements). * this way we can compare max_tau taken with different * number of measurements. This is sort of "distance @@ -108,23 +105,25 @@ static bool report(void) printf("max t: %+7.2f, max tau: %.2e, (5/tau)^2: %.2e.\n", max_t, max_tau, (double) (5 * 5) / (double) (max_tau * max_tau)); - if (max_t > t_threshold_bananas) { + /* Definitely not constant time */ + if (max_t > t_threshold_bananas) return false; - } else if (max_t > t_threshold_moderate) { + + /* Probably not constant time. */ + if (max_t > t_threshold_moderate) return false; - } else { /* max_t < t_threshold_moderate */ - return true; - } + + /* For the moment, maybe constant time. */ + return true; } static bool doit(int mode) { - int64_t *before_ticks = calloc(number_measurements + 1, sizeof(int64_t)); - int64_t *after_ticks = calloc(number_measurements + 1, sizeof(int64_t)); - int64_t *exec_times = calloc(number_measurements, sizeof(int64_t)); - uint8_t *classes = calloc(number_measurements, sizeof(uint8_t)); - uint8_t *input_data = - calloc(number_measurements * chunk_size, sizeof(uint8_t)); + int64_t *before_ticks = calloc(n_measure + 1, sizeof(int64_t)); + int64_t *after_ticks = calloc(n_measure + 1, sizeof(int64_t)); + int64_t *exec_times = calloc(n_measure, sizeof(int64_t)); + uint8_t *classes = calloc(n_measure, sizeof(uint8_t)); + uint8_t *input_data = calloc(n_measure * chunk_size, sizeof(uint8_t)); if (!before_ticks || !after_ticks || !exec_times || !classes || !input_data) { @@ -153,19 +152,17 @@ static void init_once(void) t_init(t); } -bool is_insert_tail_const(void) +static bool TEST_CONST(char *text, int mode) { bool result = false; t = malloc(sizeof(t_ctx)); for (int cnt = 0; cnt < test_tries; ++cnt) { - printf("Testing insert_tail...(%d/%d)\n\n", cnt, test_tries); + printf("Testing %s...(%d/%d)\n\n", text, cnt, test_tries); init_once(); - for (int i = 0; - i < - enough_measurements / (number_measurements - drop_size * 2) + 1; + for (int i = 0; i < enough_measure / (n_measure - drop_size * 2) + 1; ++i) - result = doit(0); + result = doit(mode); printf("\033[A\033[2K\033[A\033[2K"); if (result == true) break; @@ -174,22 +171,22 @@ bool is_insert_tail_const(void) return result; } -bool is_size_const(void) +bool is_insert_head_const(void) { - bool result = false; - t = malloc(sizeof(t_ctx)); - for (int cnt = 0; cnt < test_tries; ++cnt) { - printf("Testing size...(%d/%d)\n\n", cnt, test_tries); - init_once(); - for (int i = 0; - i < - enough_measurements / (number_measurements - drop_size * 2) + 1; - ++i) - result = doit(1); - printf("\033[A\033[2K\033[A\033[2K"); - if (result == true) - break; - } - free(t); - return result; + return TEST_CONST("insert_head", 0); +} + +bool is_insert_tail_const(void) +{ + return TEST_CONST("insert_tail", 1); +} + +bool is_remove_head_const(void) +{ + return TEST_CONST("remove_head", 2); +} + +bool is_remove_tail_const(void) +{ + return TEST_CONST("remove_tail", 3); } diff --git a/dudect/fixture.h b/dudect/fixture.h index 296a6897c..2234efca4 100644 --- a/dudect/fixture.h +++ b/dudect/fixture.h @@ -5,7 +5,9 @@ #include "constant.h" /* Interface to test if function is constant */ +bool is_insert_head_const(void); bool is_insert_tail_const(void); -bool is_size_const(void); +bool is_remove_head_const(void); +bool is_remove_tail_const(void); #endif diff --git a/dudect/ttest.c b/dudect/ttest.c index fdb877f1b..d8cb73ce6 100644 --- a/dudect/ttest.c +++ b/dudect/ttest.c @@ -5,8 +5,7 @@ * This is basically Student's t-test for unequal * variances and unequal sample sizes. * - * see https://en.wikipedia.org/wiki/Welch%27s_t-test - * + * See https://en.wikipedia.org/wiki/Welch%27s_t-test */ #include "ttest.h" @@ -20,6 +19,7 @@ void t_push(t_ctx *ctx, double x, uint8_t class) { assert(class == 0 || class == 1); ctx->n[class]++; + /* Welford method for computing online variance * in a numerically stable way. */ diff --git a/linenoise.h b/linenoise.h index 382de1dcc..174887333 100644 --- a/linenoise.h +++ b/linenoise.h @@ -49,6 +49,7 @@ typedef struct linenoiseCompletions { char **cvec; } linenoiseCompletions; +/* clang-format off */ typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char *(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); @@ -56,6 +57,7 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); +/* clang-format on */ char *linenoise(const char *prompt); void linenoiseFree(void *ptr); diff --git a/list.h b/list.h new file mode 100644 index 000000000..5c8400ac3 --- /dev/null +++ b/list.h @@ -0,0 +1,435 @@ +/* Linux-like doubly-linked list implementation */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* "typeof" is a GNU extension. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Typeof.html + */ +#if defined(__GNUC__) +#define __LIST_HAVE_TYPEOF 1 +#endif + +/** + * struct list_head - Head and node of a doubly-linked list + * @prev: pointer to the previous node in the list + * @next: pointer to the next node in the list + * + * The simple doubly-linked list consists of a head and nodes attached to + * this head. Both node and head share the same struct type. The list_* + * functions and macros can be used to access and modify this data structure. + * + * The @prev pointer of the list head points to the last list node of the + * list and @next points to the first list node of the list. For an empty list, + * both member variables point to the head. + * + * The list nodes are usually embedded in a container structure which holds the + * actual data. Such an container object is called entry. The helper list_entry + * can be used to calculate the object address from the address of the node. + */ +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +/** + * container_of() - Calculate address of object that contains address ptr + * @ptr: pointer to member variable + * @type: type of the structure containing ptr + * @member: name of the member variable in struct @type + * + * Return: @type pointer of object containing ptr + */ +#ifndef container_of +#ifdef __LIST_HAVE_TYPEOF +#define container_of(ptr, type, member) \ + __extension__({ \ + const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ + (type *) ((char *) __pmember - offsetof(type, member)); \ + }) +#else +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) -offsetof(type, member))) +#endif +#endif + +/** + * LIST_HEAD - Declare list head and initialize it + * @head: name of the new object + */ +#define LIST_HEAD(head) struct list_head head = {&(head), &(head)} + +/** + * INIT_LIST_HEAD() - Initialize empty list head + * @head: pointer to list head + * + * This can also be used to initialize a unlinked list node. + * + * A node is usually linked inside a list, will be added to a list in + * the near future or the entry containing the node will be free'd soon. + * + * But an unlinked node may be given to a function which uses list_del(_init) + * before it ends up in a previously mentioned state. The list_del(_init) on an + * initialized node is well defined and safe. But the result of a + * list_del(_init) on an uninitialized node is undefined (unrelated memory is + * modified, crashes, ...). + */ +static inline void INIT_LIST_HEAD(struct list_head *head) +{ + head->next = head; + head->prev = head; +} + +/** + * list_add() - Add a list node to the beginning of the list + * @node: pointer to the new node + * @head: pointer to the head of the list + */ +static inline void list_add(struct list_head *node, struct list_head *head) +{ + struct list_head *next = head->next; + + next->prev = node; + node->next = next; + node->prev = head; + head->next = node; +} + +/** + * list_add_tail() - Add a list node to the end of the list + * @node: pointer to the new node + * @head: pointer to the head of the list + */ +static inline void list_add_tail(struct list_head *node, struct list_head *head) +{ + struct list_head *prev = head->prev; + + prev->next = node; + node->next = head; + node->prev = prev; + head->prev = node; +} + +/** + * list_del() - Remove a list node from the list + * @node: pointer to the node + * + * The node is only removed from the list. Neither the memory of the removed + * node nor the memory of the entry containing the node is free'd. The node + * has to be handled like an uninitialized node. Accessing the next or prev + * pointer of the node is not safe. + * + * Unlinked, initialized nodes are also uninitialized after list_del. + * + * LIST_POISONING can be enabled during build-time to provoke an invalid memory + * access when the memory behind the next/prev pointer is used after a list_del. + * This only works on systems which prohibit access to the predefined memory + * addresses. + */ +static inline void list_del(struct list_head *node) +{ + struct list_head *next = node->next; + struct list_head *prev = node->prev; + + next->prev = prev; + prev->next = next; + +#ifdef LIST_POISONING + node->prev = (struct list_head *) (0x00100100); + node->next = (struct list_head *) (0x00200200); +#endif +} + +/** + * list_del_init() - Remove a list node from the list and reinitialize it + * @node: pointer to the node + * + * The removed node will not end up in an uninitialized state like when using + * list_del. Instead the node is initialized again to the unlinked state. + */ +static inline void list_del_init(struct list_head *node) +{ + list_del(node); + INIT_LIST_HEAD(node); +} + +/** + * list_empty() - Check if list head has no nodes attached + * @head: pointer to the head of the list + * + * Return: 0 - list is not empty !0 - list is empty + */ +static inline int list_empty(const struct list_head *head) +{ + return (head->next == head); +} + +/** + * list_is_singular() - Check if list head has exactly one node attached + * @head: pointer to the head of the list + * + * Return: 0 - list is not singular !0 -list has exactly one entry + */ +static inline int list_is_singular(const struct list_head *head) +{ + return (!list_empty(head) && head->prev == head->next); +} + +/** + * list_splice() - Add list nodes from a list to beginning of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the beginning of the list of @head. + * It is similar to list_add but for multiple nodes. The @list head is not + * modified and has to be initialized to be used as a valid list head/node + * again. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *head_first = head->next; + struct list_head *list_first = list->next; + struct list_head *list_last = list->prev; + + if (list_empty(list)) + return; + + head->next = list_first; + list_first->prev = head; + + list_last->next = head_first; + head_first->prev = list_last; +} + +/** + * list_splice_tail() - Add list nodes from a list to end of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the end of the list of @head. + * It is similar to list_add_tail but for multiple nodes. The @list head is not + * modified and has to be initialized to be used as a valid list head/node + * again. + */ +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + struct list_head *head_last = head->prev; + struct list_head *list_first = list->next; + struct list_head *list_last = list->prev; + + if (list_empty(list)) + return; + + head->prev = list_last; + list_last->next = head; + + list_first->prev = head_last; + head_last->next = list_first; +} + +/** + * list_splice_init() - Move list nodes from a list to beginning of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the beginning of the list of @head. + * It is similar to list_add but for multiple nodes. + * + * The @list head will not end up in an uninitialized state like when using + * list_splice. Instead the @list is initialized again to the an empty + * list/unlinked state. + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + list_splice(list, head); + INIT_LIST_HEAD(list); +} + +/** + * list_splice_tail_init() - Move list nodes from a list to end of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the end of the list of @head. + * It is similar to list_add_tail but for multiple nodes. + * + * The @list head will not end up in an uninitialized state like when using + * list_splice. Instead the @list is initialized again to the an empty + * list/unlinked state. + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + list_splice_tail(list, head); + INIT_LIST_HEAD(list); +} + +/** + * list_cut_position() - Move beginning of a list to another list + * @head_to: pointer to the head of the list which receives nodes + * @head_from: pointer to the head of the list + * @node: pointer to the node in which defines the cutting point + * + * All entries from the beginning of the list @head_from to (including) the + * @node is moved to @head_to. + * + * @head_to is replaced when @head_from is not empty. @node must be a real + * list node from @head_from or the behavior is undefined. + */ +static inline void list_cut_position(struct list_head *head_to, + struct list_head *head_from, + struct list_head *node) +{ + struct list_head *head_from_first = head_from->next; + + if (list_empty(head_from)) + return; + + if (head_from == node) { + INIT_LIST_HEAD(head_to); + return; + } + + head_from->next = node->next; + head_from->next->prev = head_from; + + head_to->prev = node; + node->next = head_to; + head_to->next = head_from_first; + head_to->next->prev = head_to; +} + +/** + * list_move() - Move a list node to the beginning of the list + * @node: pointer to the node + * @head: pointer to the head of the list + * + * The @node is removed from its old position/node and add to the beginning of + * @head + */ +static inline void list_move(struct list_head *node, struct list_head *head) +{ + list_del(node); + list_add(node, head); +} + +/** + * list_move_tail() - Move a list node to the end of the list + * @node: pointer to the node + * @head: pointer to the head of the list + * + * The @node is removed from its old position/node and add to the end of @head + */ +static inline void list_move_tail(struct list_head *node, + struct list_head *head) +{ + list_del(node); + list_add_tail(node, head); +} + +/** + * list_entry() - Calculate address of entry that contains list node + * @node: pointer to list node + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of entry containing node + */ +#define list_entry(node, type, member) container_of(node, type, member) + +/** + * list_first_entry() - get first entry of the list + * @head: pointer to the head of the list + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of first entry in list + */ +#define list_first_entry(head, type, member) \ + list_entry((head)->next, type, member) + +/** + * list_last_entry() - get last entry of the list + * @head: pointer to the head of the list + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of last entry in list + */ +#define list_last_entry(head, type, member) \ + list_entry((head)->prev, type, member) + +/** + * list_for_each - iterate over list nodes + * @node: list_head pointer used as iterator + * @head: pointer to the head of the list + * + * The nodes and the head of the list must must be kept unmodified while + * iterating through it. Any modifications to the the list will cause undefined + * behavior. + */ +#define list_for_each(node, head) \ + for (node = (head)->next; node != (head); node = node->next) + +/** + * list_for_each_entry - iterate over list entries + * @entry: pointer used as iterator + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * The nodes and the head of the list must must be kept unmodified while + * iterating through it. Any modifications to the the list will cause undefined + * behavior. + * + * FIXME: remove dependency of __typeof__ extension + */ +#ifdef __LIST_HAVE_TYPEOF +#define list_for_each_entry(entry, head, member) \ + for (entry = list_entry((head)->next, __typeof__(*entry), member); \ + &entry->member != (head); \ + entry = list_entry(entry->member.next, __typeof__(*entry), member)) +#endif + +/** + * list_for_each_safe - iterate over list nodes and allow deletes + * @node: list_head pointer used as iterator + * @safe: list_head pointer used to store info for next entry in list + * @head: pointer to the head of the list + * + * The current node (iterator) is allowed to be removed from the list. Any + * other modifications to the the list will cause undefined behavior. + */ +#define list_for_each_safe(node, safe, head) \ + for (node = (head)->next, safe = node->next; node != (head); \ + node = safe, safe = node->next) + +/** + * list_for_each_entry_safe - iterate over list entries and allow deletes + * @entry: pointer used as iterator + * @safe: @type pointer used to store info for next entry in list + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * The current node (iterator) is allowed to be removed from the list. Any + * other modifications to the the list will cause undefined behavior. + * + * FIXME: remove dependency of __typeof__ extension + */ +#define list_for_each_entry_safe(entry, safe, head, member) \ + for (entry = list_entry((head)->next, __typeof__(*entry), member), \ + safe = list_entry(entry->member.next, __typeof__(*entry), member); \ + &entry->member != (head); entry = safe, \ + safe = list_entry(safe->member.next, __typeof__(*entry), member)) + +#undef __LIST_HAVE_TYPEOF + +#ifdef __cplusplus +} +#endif diff --git a/qtest.c b/qtest.c index e92565a94..dd4097281 100644 --- a/qtest.c +++ b/qtest.c @@ -13,6 +13,7 @@ #include #include #include "dudect/fixture.h" +#include "list.h" /* Our program needs to use regular malloc/free */ #define INTERNAL 1 @@ -42,21 +43,28 @@ /* * How large is a queue before it's considered big. * This affects how it gets printed - * and whether cautious mode is used when freeing the list + * and whether cautious mode is used when freeing the queue */ -#define BIG_QUEUE 30 -static int big_queue_size = BIG_QUEUE; +#define BIG_LIST 30 +static int big_list_size = BIG_LIST; + /* Global variables */ -/* Queue being tested */ -static queue_t *q = NULL; +/* List being tested */ +typedef struct { + struct list_head *l; + /* meta data of list */ + int size; +} list_head_meta_t; + +static list_head_meta_t l_meta; /* Number of elements in queue */ -static size_t qcnt = 0; +static size_t lcnt = 0; /* How many times can queue operations fail */ -static int fail_limit = BIG_QUEUE; +static int fail_limit = BIG_LIST; static int fail_count = 0; static int string_length = MAXSTRING; @@ -67,49 +75,8 @@ static const char charset[] = "abcdefghijklmnopqrstuvwxyz"; /* Forward declarations */ static bool show_queue(int vlevel); -static bool do_new(int argc, char *argv[]); -static bool do_free(int argc, char *argv[]); -static bool do_insert_head(int argc, char *argv[]); -static bool do_insert_tail(int argc, char *argv[]); -static bool do_remove_head(int argc, char *argv[]); -static bool do_remove_head_quiet(int argc, char *argv[]); -static bool do_reverse(int argc, char *argv[]); -static bool do_size(int argc, char *argv[]); -static bool do_sort(int argc, char *argv[]); -static bool do_show(int argc, char *argv[]); - -static void queue_init(); - -static void console_init() -{ - add_cmd("new", do_new, " | Create new queue"); - add_cmd("free", do_free, " | Delete queue"); - add_cmd("ih", do_insert_head, - " str [n] | Insert string str at head of queue n times. " - "Generate random string(s) if str equals RAND. (default: n == 1)"); - add_cmd("it", do_insert_tail, - " str [n] | Insert string str at tail of queue n times. " - "Generate random string(s) if str equals RAND. (default: n == 1)"); - add_cmd("rh", do_remove_head, - " [str] | Remove from head of queue. Optionally compare " - "to expected value str"); - add_cmd( - "rhq", do_remove_head_quiet, - " | Remove from head of queue without reporting value."); - add_cmd("reverse", do_reverse, " | Reverse queue"); - add_cmd("sort", do_sort, " | Sort queue in ascending order"); - add_cmd("size", do_size, - " [n] | Compute queue size n times (default: n == 1)"); - add_cmd("show", do_show, " | Show queue contents"); - add_param("length", &string_length, "Maximum length of displayed string", - NULL); - add_param("malloc", &fail_probability, "Malloc failure probability percent", - NULL); - add_param("fail", &fail_limit, - "Number of times allow queue operations to return false", NULL); -} -static bool do_new(int argc, char *argv[]) +static bool do_free(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); @@ -117,22 +84,33 @@ static bool do_new(int argc, char *argv[]) } bool ok = true; - if (q) { - report(3, "Freeing old queue"); - ok = do_free(argc, argv); - } + if (!l_meta.l) + report(3, "Warning: Calling free on null queue"); error_check(); + if (lcnt > big_list_size) + set_cautious_mode(false); if (exception_setup(true)) - q = q_new(); + q_free(l_meta.l); exception_cancel(); - qcnt = 0; + set_cautious_mode(true); + + l_meta.size = 0; + l_meta.l = NULL; + lcnt = 0; show_queue(3); + size_t bcnt = allocation_check(); + if (bcnt > 0) { + report(1, "ERROR: Freed queue, but %lu blocks are still allocated", + bcnt); + ok = false; + } + return ok && !error_check(); } -static bool do_free(int argc, char *argv[]) +static bool do_new(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); @@ -140,30 +118,23 @@ static bool do_free(int argc, char *argv[]) } bool ok = true; - if (!q) - report(3, "Warning: Calling free on null queue"); + if (l_meta.l) { + report(3, "Freeing old queue"); + ok = do_free(argc, argv); + } error_check(); - if (qcnt > big_queue_size) - set_cautious_mode(false); - if (exception_setup(true)) - q_free(q); + if (exception_setup(true)) { + l_meta.l = q_new(); + l_meta.size = 0; + } exception_cancel(); - set_cautious_mode(true); - - q = NULL; - qcnt = 0; + lcnt = 0; show_queue(3); - size_t bcnt = allocation_check(); - if (bcnt > 0) { - report(1, "ERROR: Freed queue, but %lu blocks are still allocated", - bcnt); - ok = false; - } - return ok && !error_check(); } + /* * TODO: Add a buf_size check of if the buf_size may be less * than MIN_RANDSTR_LEN. @@ -180,8 +151,23 @@ static void fill_rand_string(char *buf, size_t buf_size) buf[len] = '\0'; } -static bool do_insert_head(int argc, char *argv[]) +/* insert head */ +static bool do_ih(int argc, char *argv[]) { + if (simulation) { + if (argc != 1) { + report(1, "%s does not need arguments in simulation mode", argv[0]); + return false; + } + bool ok = is_insert_head_const(); + if (!ok) { + report(1, "ERROR: Probably not constant time"); + return false; + } + report(1, "Probably constant time"); + return ok; + } + char *lasts = NULL; char randstr_buf[MAX_RANDSTR_LEN]; int reps = 1; @@ -204,7 +190,7 @@ static bool do_insert_head(int argc, char *argv[]) inserts = randstr_buf; } - if (!q) + if (!l_meta.l) report(3, "Warning: Calling insert head on null queue"); error_check(); @@ -212,26 +198,29 @@ static bool do_insert_head(int argc, char *argv[]) for (int r = 0; ok && r < reps; r++) { if (need_rand) fill_rand_string(randstr_buf, sizeof(randstr_buf)); - bool rval = q_insert_head(q, inserts); + bool rval = q_insert_head(l_meta.l, inserts); if (rval) { - qcnt++; - if (!q->head->value) { - report(1, "ERROR: Failed to save copy of string in list"); + lcnt++; + l_meta.size++; + char *cur_inserts = + list_entry(l_meta.l->next, element_t, list)->value; + if (!cur_inserts) { + report(1, "ERROR: Failed to save copy of string in queue"); ok = false; - } else if (r == 0 && inserts == q->head->value) { + } else if (r == 0 && inserts == cur_inserts) { report(1, "ERROR: Need to allocate and copy string for new " - "list element"); + "queue element"); ok = false; break; - } else if (r == 1 && lasts == q->head->value) { + } else if (r == 1 && lasts == cur_inserts) { report(1, "ERROR: Need to allocate separate string for each " - "list element"); + "queue element"); ok = false; break; } - lasts = q->head->value; + lasts = cur_inserts; } else { fail_count++; if (fail_count < fail_limit) @@ -252,7 +241,8 @@ static bool do_insert_head(int argc, char *argv[]) return ok; } -static bool do_insert_tail(int argc, char *argv[]) +/* insert tail */ +static bool do_it(int argc, char *argv[]) { if (simulation) { if (argc != 1) { @@ -289,7 +279,7 @@ static bool do_insert_tail(int argc, char *argv[]) inserts = randstr_buf; } - if (!q) + if (!l_meta.l) report(3, "Warning: Calling insert tail on null queue"); error_check(); @@ -297,11 +287,14 @@ static bool do_insert_tail(int argc, char *argv[]) for (int r = 0; ok && r < reps; r++) { if (need_rand) fill_rand_string(randstr_buf, sizeof(randstr_buf)); - bool rval = q_insert_tail(q, inserts); + bool rval = q_insert_tail(l_meta.l, inserts); if (rval) { - qcnt++; - if (!q->head->value) { - report(1, "ERROR: Failed to save copy of string in list"); + lcnt++; + l_meta.size++; + char *cur_inserts = + list_entry(l_meta.l->prev, element_t, list)->value; + if (!cur_inserts) { + report(1, "ERROR: Failed to save copy of string in queue"); ok = false; } } else { @@ -323,8 +316,30 @@ static bool do_insert_tail(int argc, char *argv[]) return ok; } -static bool do_remove_head(int argc, char *argv[]) +static bool do_remove(int option, int argc, char *argv[]) { + // option 0 is for remove head; option 1 is for remove tail + + /* FIXME: It is known that both functions is_remove_tail_const() and + * is_remove_head_const() can not pass dudect on Arm64. We shall figure + * out the exact reasons and resolve later. + */ +#if !defined(__aarch64__) + if (simulation) { + if (argc != 1) { + report(1, "%s does not need arguments in simulation mode", argv[0]); + return false; + } + bool ok = option ? is_remove_tail_const() : is_remove_head_const(); + if (!ok) { + report(1, "ERROR: Probably not constant time"); + return false; + } + report(1, "Probably constant time"); + return ok; + } +#endif + if (argc != 1 && argc != 2) { report(1, "%s needs 0-1 arguments", argv[0]); return false; @@ -356,18 +371,23 @@ static bool do_remove_head(int argc, char *argv[]) memset(removes + 1, 'X', string_length + STRINGPAD - 1); removes[string_length + STRINGPAD] = '\0'; - if (!q) - report(3, "Warning: Calling remove head on null queue"); - else if (!q->head) + if (!l_meta.size) report(3, "Warning: Calling remove head on empty queue"); error_check(); - bool rval = false; + element_t *re = NULL; if (exception_setup(true)) - rval = q_remove_head(q, removes, string_length + 1); + re = option ? q_remove_tail(l_meta.l, removes, string_length + 1) + : q_remove_head(l_meta.l, removes, string_length + 1); exception_cancel(); - if (rval) { + bool is_null = re ? false : true; + + if (!is_null) { + // q_remove_head and q_remove_tail are not responsible for releasing + // node + q_release_element(re); + removes[string_length + STRINGPAD] = '\0'; if (removes[0] == '\0') { report(1, "ERROR: Failed to store removed value"); @@ -389,7 +409,8 @@ static bool do_remove_head(int argc, char *argv[]) } else { report(2, "Removed %s from queue", removes); } - qcnt--; + lcnt--; + l_meta.size--; } else { fail_count++; if (!check && fail_count < fail_limit) { @@ -414,7 +435,18 @@ static bool do_remove_head(int argc, char *argv[]) return ok && !error_check(); } -static bool do_remove_head_quiet(int argc, char *argv[]) +static inline bool do_rh(int argc, char *argv[]) +{ + return do_remove(0, argc, argv); +} + +static inline bool do_rt(int argc, char *argv[]) +{ + return do_remove(1, argc, argv); +} + +/* remove head quietly */ +static bool do_rhq(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); @@ -422,20 +454,24 @@ static bool do_remove_head_quiet(int argc, char *argv[]) } bool ok = true; - if (!q) - report(3, "Warning: Calling remove head on null queue"); - else if (!q->head) + if (!l_meta.size) report(3, "Warning: Calling remove head on empty queue"); error_check(); - bool rval = false; + element_t *re = NULL; + if (exception_setup(true)) - rval = q_remove_head(q, NULL, 0); + re = q_remove_head(l_meta.l, NULL, 0); exception_cancel(); - if (rval) { + if (re) { + // q_remove_head and q_remove_tail are not responsible for releasing + // node + q_release_element(re); + report(2, "Removed element from queue"); - qcnt--; + lcnt--; + l_meta.size--; } else { fail_count++; if (fail_count < fail_limit) @@ -450,6 +486,47 @@ static bool do_remove_head_quiet(int argc, char *argv[]) return ok && !error_check(); } +static bool do_dedup(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + + bool ok = true; + // set_noallocate_mode(true); + if (exception_setup(true)) + ok = q_delete_dup(l_meta.l); + exception_cancel(); + + // set_noallocate_mode(false); + + if (!ok) { + report(1, "ERROR: Calling delete duplicate on null queue"); + return false; + } + + element_t *item = NULL; + if (l_meta.size) { + list_for_each_entry (item, l_meta.l, list) { + element_t *next_item; + if (item->list.next == l_meta.l) + break; + next_item = list_entry(item->list.next, element_t, list); + + // assume queue has been sorted + if (strcmp(item->value, next_item->value) == 0) { + report(1, "ERROR: Contain duplicate string on queue"); + ok = false; + break; + } + } + } + show_queue(3); + + return ok && !error_check(); +} + static bool do_reverse(int argc, char *argv[]) { if (argc != 1) { @@ -457,13 +534,13 @@ static bool do_reverse(int argc, char *argv[]) return false; } - if (!q) + if (!l_meta.l) report(3, "Warning: Calling reverse on null queue"); error_check(); set_noallocate_mode(true); if (exception_setup(true)) - q_reverse(q); + q_reverse(l_meta.l); exception_cancel(); set_noallocate_mode(false); @@ -473,20 +550,6 @@ static bool do_reverse(int argc, char *argv[]) static bool do_size(int argc, char *argv[]) { - if (simulation) { - if (argc != 1) { - report(1, "%s does not need arguments in simulation mode", argv[0]); - return false; - } - bool ok = is_size_const(); - if (!ok) { - report(1, "ERROR: Probably not constant time"); - return false; - } - report(1, "Probably constant time"); - return ok; - } - if (argc != 1 && argc != 2) { report(1, "%s takes 0-1 arguments", argv[0]); return false; @@ -506,25 +569,25 @@ static bool do_size(int argc, char *argv[]) } int cnt = 0; - if (!q) + if (!l_meta.l) report(3, "Warning: Calling size on null queue"); error_check(); if (exception_setup(true)) { for (int r = 0; ok && r < reps; r++) { - cnt = q_size(q); + cnt = q_size(l_meta.l); ok = ok && !error_check(); } } exception_cancel(); if (ok) { - if (qcnt == cnt) { + if (lcnt == cnt) { report(2, "Queue size = %d", cnt); } else { report(1, "ERROR: Computed queue size as %d, but correct value is %d", - cnt, (int) qcnt); + cnt, (int) lcnt); ok = false; } } @@ -541,27 +604,31 @@ bool do_sort(int argc, char *argv[]) return false; } - if (!q) + if (!l_meta.l) report(3, "Warning: Calling sort on null queue"); error_check(); - int cnt = q_size(q); + int cnt = q_size(l_meta.l); if (cnt < 2) report(3, "Warning: Calling sort on single node"); error_check(); set_noallocate_mode(true); if (exception_setup(true)) - q_sort(q); + q_sort(l_meta.l); exception_cancel(); set_noallocate_mode(false); bool ok = true; - if (q) { - for (list_ele_t *e = q->head; e && --cnt; e = e->next) { + if (l_meta.size) { + for (struct list_head *cur_l = l_meta.l->next; + cur_l != l_meta.l && --cnt; cur_l = cur_l->next) { /* Ensure each element in ascending order */ /* FIXME: add an option to specify sorting order */ - if (strcasecmp(e->value, e->next->value) > 0) { + element_t *item, *next_item; + item = list_entry(cur_l, element_t, list); + next_item = list_entry(cur_l->next, element_t, list); + if (strcasecmp(item->value, next_item->value) > 0) { report(1, "ERROR: Not sorted in ascending order"); ok = false; break; @@ -573,6 +640,66 @@ bool do_sort(int argc, char *argv[]) return ok && !error_check(); } +static bool do_dm(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + + if (!l_meta.l) + report(3, "Warning: Try to access null queue"); + error_check(); + + bool ok = true; + if (exception_setup(true)) + ok = q_delete_mid(l_meta.l); + exception_cancel(); + + show_queue(3); + return ok && !error_check(); +} + +static bool do_swap(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + + if (!l_meta.l) + report(3, "Warning: Try to access null queue"); + error_check(); + + set_noallocate_mode(true); + if (exception_setup(true)) + q_swap(l_meta.l); + exception_cancel(); + + set_noallocate_mode(false); + + show_queue(3); + return !error_check(); +} + +static bool is_circular() +{ + struct list_head *cur = l_meta.l->next; + while (cur != l_meta.l) { + if (!cur) + return false; + cur = cur->next; + } + + cur = l_meta.l->prev; + while (cur != l_meta.l) { + if (!cur) + return false; + cur = cur->prev; + } + return true; +} + static bool show_queue(int vlevel) { bool ok = true; @@ -580,19 +707,28 @@ static bool show_queue(int vlevel) return true; int cnt = 0; - if (!q) { - report(vlevel, "q = NULL"); + if (!l_meta.l) { + report(vlevel, "l = NULL"); return true; } - report_noreturn(vlevel, "q = ["); - list_ele_t *e = q->head; + if (!is_circular()) { + report(vlevel, "ERROR: Queue is not doubly circular"); + return false; + } + + report_noreturn(vlevel, "l = ["); + + struct list_head *ori = l_meta.l; + struct list_head *cur = l_meta.l->next; + if (exception_setup(true)) { - while (ok && e && cnt < qcnt) { - if (cnt < big_queue_size) + while (ok && ori != cur && cnt < lcnt) { + element_t *e = list_entry(cur, element_t, list); + if (cnt < big_list_size) report_noreturn(vlevel, cnt == 0 ? "%s" : " %s", e->value); - e = e->next; cnt++; + cur = cur->next; ok = ok && !error_check(); } } @@ -603,17 +739,14 @@ static bool show_queue(int vlevel) return false; } - if (!e) { - if (cnt <= big_queue_size) + if (cur == ori) { + if (cnt <= big_list_size) report(vlevel, "]"); else report(vlevel, " ... ]"); } else { report(vlevel, " ... ]"); - report( - vlevel, - "ERROR: Either list has cycle, or queue has more than %d elements", - qcnt); + report(vlevel, "ERROR: Queue has more than %d elements", lcnt); ok = false; } @@ -629,6 +762,47 @@ static bool do_show(int argc, char *argv[]) return show_queue(0); } +static void console_init() +{ + ADD_COMMAND(new, " | Create new queue"); + ADD_COMMAND(free, " | Delete queue"); + ADD_COMMAND( + ih, + " str [n] | Insert string str at head of queue n times. " + "Generate random string(s) if str equals RAND. (default: n == 1)"); + ADD_COMMAND( + it, + " str [n] | Insert string str at tail of queue n times. " + "Generate random string(s) if str equals RAND. (default: n == 1)"); + ADD_COMMAND( + rh, + " [str] | Remove from head of queue. Optionally compare " + "to expected value str"); + ADD_COMMAND( + rt, + " [str] | Remove from tail of queue. Optionally compare " + "to expected value str"); + ADD_COMMAND( + rhq, + " | Remove from head of queue without reporting value."); + ADD_COMMAND(reverse, " | Reverse queue"); + ADD_COMMAND(sort, " | Sort queue in ascending order"); + ADD_COMMAND( + size, " [n] | Compute queue size n times (default: n == 1)"); + ADD_COMMAND(show, " | Show queue contents"); + ADD_COMMAND(dm, " | Delete middle node in queue"); + ADD_COMMAND( + dedup, " | Delete all nodes that have duplicate string"); + ADD_COMMAND(swap, + " | Swap every two adjacent nodes in queue"); + add_param("length", &string_length, "Maximum length of displayed string", + NULL); + add_param("malloc", &fail_probability, "Malloc failure probability percent", + NULL); + add_param("fail", &fail_limit, + "Number of times allow queue operations to return false", NULL); +} + /* Signal handlers */ static void sigsegvhandler(int sig) { @@ -649,7 +823,7 @@ static void sigalrmhandler(int sig) static void queue_init() { fail_count = 0; - q = NULL; + l_meta.l = NULL; signal(SIGSEGV, sigsegvhandler); signal(SIGALRM, sigalrmhandler); } @@ -657,11 +831,11 @@ static void queue_init() static bool queue_quit(int argc, char *argv[]) { report(3, "Freeing queue"); - if (qcnt > big_queue_size) + if (lcnt > big_list_size) set_cautious_mode(false); if (exception_setup(true)) - q_free(q); + q_free(l_meta.l); exception_cancel(); set_cautious_mode(true); diff --git a/queue.c b/queue.c index a77dbf715..a1489bd4d 100644 --- a/queue.c +++ b/queue.c @@ -5,25 +5,23 @@ #include "harness.h" #include "queue.h" +/* Notice: sometimes, Cppcheck would find the potential NULL pointer bugs, + * but some of them cannot occur. You can suppress them by adding the + * following line. + * cppcheck-suppress nullPointer + */ + /* * Create empty queue. * Return NULL if could not allocate space. */ -queue_t *q_new() +struct list_head *q_new() { - queue_t *q = malloc(sizeof(queue_t)); - /* TODO: What if malloc returned NULL? */ - q->head = NULL; - return q; + return NULL; } /* Free all storage used by queue */ -void q_free(queue_t *q) -{ - /* TODO: How about freeing the list elements and the strings? */ - /* Free queue structure */ - free(q); -} +void q_free(struct list_head *l) {} /* * Attempt to insert element at head of queue. @@ -32,15 +30,8 @@ void q_free(queue_t *q) * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. */ -bool q_insert_head(queue_t *q, char *s) +bool q_insert_head(struct list_head *head, char *s) { - list_ele_t *newh; - /* TODO: What should you do if the q is NULL? */ - newh = malloc(sizeof(list_ele_t)); - /* Don't forget to allocate space for the string and copy it */ - /* What if either call to malloc returns NULL? */ - newh->next = q->head; - q->head = newh; return true; } @@ -51,40 +42,92 @@ bool q_insert_head(queue_t *q, char *s) * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. */ -bool q_insert_tail(queue_t *q, char *s) +bool q_insert_tail(struct list_head *head, char *s) { - /* TODO: You need to write the complete code for this function */ - /* Remember: It should operate in O(1) time */ - /* TODO: Remove the above comment when you are about to implement. */ - return false; + return true; } /* * Attempt to remove element from head of queue. - * Return true if successful. - * Return false if queue is NULL or empty. + * Return target element. + * Return NULL if queue is NULL or empty. * If sp is non-NULL and an element is removed, copy the removed string to *sp * (up to a maximum of bufsize-1 characters, plus a null terminator.) - * The space used by the list element and the string should be freed. + * + * NOTE: "remove" is different from "delete" + * The space used by the list element and the string should not be freed. + * The only thing "remove" need to do is unlink it. + * + * REF: + * https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove */ -bool q_remove_head(queue_t *q, char *sp, size_t bufsize) +element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { - /* TODO: You need to fix up this code. */ - /* TODO: Remove the above comment when you are about to implement. */ - q->head = q->head->next; - return true; + return NULL; +} + +/* + * Attempt to remove element from tail of queue. + * Other attribute is as same as q_remove_head. + */ +element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) +{ + return NULL; +} + +/* + * WARN: This is for external usage, don't modify it + * Attempt to release element. + */ +void q_release_element(element_t *e) +{ + free(e->value); + free(e); } /* * Return number of elements in queue. * Return 0 if q is NULL or empty */ -int q_size(queue_t *q) +int q_size(struct list_head *head) { - /* TODO: You need to write the code for this function */ - /* Remember: It should operate in O(1) time */ - /* TODO: Remove the above comment when you are about to implement. */ - return 0; + return -1; +} + +/* + * Delete the middle node in list. + * The middle node of a linked list of size n is the + * ⌊n / 2⌋th node from the start using 0-based indexing. + * If there're six element, the third member should be return. + * Return NULL if list is NULL or empty. + */ +bool q_delete_mid(struct list_head *head) +{ + // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ + return true; +} + +/* + * Delete all nodes that have duplicate string, + * leaving only distinct strings from the original list. + * Return true if successful. + * Return false if list is NULL. + * + * Note: this function always be called after sorting, in other words, + * list is guaranteed to be sorted in ascending order. + */ +bool q_delete_dup(struct list_head *head) +{ + // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ + return true; +} + +/* + * Attempt to swap every two adjacent nodes. + */ +void q_swap(struct list_head *head) +{ + // https://leetcode.com/problems/swap-nodes-in-pairs/ } /* @@ -94,19 +137,11 @@ int q_size(queue_t *q) * (e.g., by calling q_insert_head, q_insert_tail, or q_remove_head). * It should rearrange the existing ones. */ -void q_reverse(queue_t *q) -{ - /* TODO: You need to write the code for this function */ - /* TODO: Remove the above comment when you are about to implement. */ -} +void q_reverse(struct list_head *head) {} /* * Sort elements of queue in ascending order * No effect if q is NULL or empty. In addition, if q has only one * element, do nothing. */ -void q_sort(queue_t *q) -{ - /* TODO: You need to write the code for this function */ - /* TODO: Remove the above comment when you are about to implement. */ -} +void q_sort(struct list_head *head) {} diff --git a/queue.h b/queue.h index 3b48875af..5a578792b 100644 --- a/queue.h +++ b/queue.h @@ -5,31 +5,21 @@ * This program implements a queue supporting both FIFO and LIFO * operations. * - * It uses a singly-linked list to represent the set of queue elements + * It uses a circular doubly-linked list to represent the set of queue elements */ #include #include +#include "list.h" -/* Data structure declarations */ - -/* Linked list element (You shouldn't need to change this) */ -typedef struct ELE { +/* Linked list element */ +typedef struct { /* Pointer to array holding string. * This array needs to be explicitly allocated and freed */ char *value; - struct ELE *next; -} list_ele_t; - -/* Queue structure */ -typedef struct { - list_ele_t *head; /* Linked list of elements */ - /* TODO: You will need to add more fields to this structure - * to efficiently implement q_size and q_insert_tail. - */ - /* TODO: Remove the above comment when you are about to implement. */ -} queue_t; + struct list_head list; +} element_t; /* Operations on queue */ @@ -37,13 +27,13 @@ typedef struct { * Create empty queue. * Return NULL if could not allocate space. */ -queue_t *q_new(); +struct list_head *q_new(); /* * Free ALL storage used by queue. * No effect if q is NULL */ -void q_free(queue_t *q); +void q_free(struct list_head *head); /* * Attempt to insert element at head of queue. @@ -52,7 +42,7 @@ void q_free(queue_t *q); * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. */ -bool q_insert_head(queue_t *q, char *s); +bool q_insert_head(struct list_head *head, char *s); /* * Attempt to insert element at tail of queue. @@ -61,23 +51,68 @@ bool q_insert_head(queue_t *q, char *s); * Argument s points to the string to be stored. * The function must explicitly allocate space and copy the string into it. */ -bool q_insert_tail(queue_t *q, char *s); +bool q_insert_tail(struct list_head *head, char *s); /* * Attempt to remove element from head of queue. - * Return true if successful. - * Return false if queue is NULL or empty. + * Return target element. + * Return NULL if queue is NULL or empty. * If sp is non-NULL and an element is removed, copy the removed string to *sp * (up to a maximum of bufsize-1 characters, plus a null terminator.) - * The space used by the list element and the string should be freed. + * + * NOTE: "remove" is different from "delete" + * The space used by the list element and the string should not be freed. + * The only thing "remove" need to do is unlink it. + * + * REF: + * https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove + */ +element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize); + +/* + * Attempt to remove element from tail of queue. + * Other attribute is as same as q_remove_head. + */ +element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize); + +/* + * Attempt to release element. */ -bool q_remove_head(queue_t *q, char *sp, size_t bufsize); +void q_release_element(element_t *e); /* * Return number of elements in queue. * Return 0 if q is NULL or empty */ -int q_size(queue_t *q); +int q_size(struct list_head *head); + +/* + * Delete the middle node in list. + * The middle node of a linked list of size n is the + * ⌊n / 2⌋th node from the start using 0-based indexing. + * If there're six element, the third member should be return. + * Return NULL if lm is NULL or empty. + * + * Ref: https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ + */ +bool q_delete_mid(struct list_head *head); + +/* + * Delete all nodes that have duplicate string, + * leaving only distinct strings from the original list. + * Return true if successful. + * Return false if list is NULL. + * + * Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ + */ +bool q_delete_dup(struct list_head *head); + +/* + * Attempt to swap every two adjacent nodes. + * + * Ref: https://leetcode.com/problems/swap-nodes-in-pairs/ + */ +void q_swap(struct list_head *head); /* * Reverse elements in queue @@ -86,13 +121,13 @@ int q_size(queue_t *q); * (e.g., by calling q_insert_head, q_insert_tail, or q_remove_head). * It should rearrange the existing ones. */ -void q_reverse(queue_t *q); +void q_reverse(struct list_head *head); /* * Sort elements of queue in ascending order * No effect if q is NULL or empty. In addition, if q has only one * element, do nothing. */ -void q_sort(queue_t *q); +void q_sort(struct list_head *head); #endif /* LAB0_QUEUE_H */ diff --git a/random.c b/random.c index cd57066d5..9cb027a26 100644 --- a/random.c +++ b/random.c @@ -37,10 +37,3 @@ void randombytes(uint8_t *x, size_t how_much) xlen -= i; } } - -uint8_t randombit(void) -{ - uint8_t ret = 0; - randombytes(&ret, 1); - return (ret & 1); -} diff --git a/random.h b/random.h index d1aae5189..6bac953cd 100644 --- a/random.h +++ b/random.h @@ -3,7 +3,14 @@ #include #include + void randombytes(uint8_t *x, size_t xlen); -uint8_t randombit(void); + +static inline uint8_t randombit(void) +{ + uint8_t ret = 0; + randombytes(&ret, 1); + return ret & 1; +} #endif diff --git a/report.c b/report.c index 91c547d4f..b169e3ded 100644 --- a/report.c +++ b/report.c @@ -54,9 +54,9 @@ void report_event(message_t msg, char *fmt, ...) { va_list ap; bool fatal = msg == MSG_FATAL; - char *msg_name = msg == MSG_WARN - ? "WARNING" - : msg == MSG_ERROR ? "ERROR" : "FATAL ERROR"; + char *msg_name = msg == MSG_WARN ? "WARNING" + : msg == MSG_ERROR ? "ERROR" + : "FATAL ERROR"; int level = msg == MSG_WARN ? 2 : msg == MSG_ERROR ? 1 : 0; if (verblevel < level) return; diff --git a/scripts/aspell-pws b/scripts/aspell-pws index 394c236e2..fe4bc7ec7 100644 --- a/scripts/aspell-pws +++ b/scripts/aspell-pws @@ -135,3 +135,9 @@ vim lldb utf unicode +checksum +sha +shasum +verifier +CI +foreach diff --git a/scripts/checksums b/scripts/checksums new file mode 100644 index 000000000..b8ae17c10 --- /dev/null +++ b/scripts/checksums @@ -0,0 +1,2 @@ +dba62ae6590328e0f22912593ed6c1e3264ec901 queue.h +0ab0e0224f33940770e9e6297da833073004aebf list.h diff --git a/scripts/driver.py b/scripts/driver.py index f0abc2c06..8e3979f87 100755 --- a/scripts/driver.py +++ b/scripts/driver.py @@ -24,14 +24,14 @@ class Tracer: 3: "trace-03-ops", 4: "trace-04-ops", 5: "trace-05-ops", - 6: "trace-06-string", - 7: "trace-07-robust", + 6: "trace-06-ops", + 7: "trace-07-string", 8: "trace-08-robust", 9: "trace-09-robust", - 10: "trace-10-malloc", + 10: "trace-10-robust", 11: "trace-11-malloc", 12: "trace-12-malloc", - 13: "trace-13-perf", + 13: "trace-13-malloc", 14: "trace-14-perf", 15: "trace-15-perf", 16: "trace-16-perf", @@ -58,7 +58,7 @@ class Tracer: 17: "Trace-17" } - maxScores = [0, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5] + maxScores = [0, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5] RED = '\033[91m' GREEN = '\033[92m' @@ -89,7 +89,7 @@ def runTrace(self, tid): fname = "%s/%s.cmd" % (self.traceDirectory, self.traceDict[tid]) vname = "%d" % self.verbLevel clist = self.command + ["-v", vname, "-f", fname] - + try: retcode = subprocess.call(clist) except Exception as e: diff --git a/scripts/pre-commit.hook b/scripts/pre-commit.hook index 4b2ba1d56..20a08f4af 100755 --- a/scripts/pre-commit.hook +++ b/scripts/pre-commit.hook @@ -1,6 +1,9 @@ #!/usr/bin/env bash -CPPCHECK_suppresses="--inline-suppr harness.c --suppress=unmatchedSuppression:harness.c --suppress=missingIncludeSystem --suppress=unusedFunction:linenoise.c --suppress=variableScope:linenoise.c --suppress=nullPointerRedundantCheck:report.c" +CPPCHECK_suppresses="--inline-suppr harness.c --suppress=unmatchedSuppression:harness.c --suppress=missingIncludeSystem \ +--suppress=unusedFunction:linenoise.c --suppress=variableScope:linenoise.c \ +--suppress=nullPointerRedundantCheck:report.c \ +--suppress=nullPointer:qtest.c" CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1 --force $CPPCHECK_suppresses ." RETURN=0 @@ -40,6 +43,11 @@ if [ $? -ne 0 ]; then DIFF=diff fi +SHA1SUM=$(which sha1sum) +if [ $? -ne 0 ]; then + SHA1SUM=shasum +fi + FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` for FILE in $FILES; do nf=`git checkout-index --temp $FILE | cut -f 1` @@ -72,6 +80,12 @@ if [ ! -z "${FILES[*]}" ]; then echo "${FILES[*]}" fi +$SHA1SUM -c scripts/checksums +if [ $? -ne 0 ]; then + echo "[!] You are not allowed to change the header file queue.h or list.h" >&2 + exit 1 +fi + # Prevent unsafe functions root=$(git rev-parse --show-toplevel) banned="([^f]gets\()|(sprintf\()|(strcpy\()" diff --git a/scripts/pre-push.hook b/scripts/pre-push.hook index dd3f12549..c6d3d4941 100755 --- a/scripts/pre-push.hook +++ b/scripts/pre-push.hook @@ -23,7 +23,7 @@ fi # Show hints echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." -echo -e " https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password" +echo -e " https://docs.github.com/en/get-started/getting-started-with-git/why-is-git-always-asking-for-my-password" echo "" # only run this if you are pushing to master diff --git a/traces/trace-02-ops.cmd b/traces/trace-02-ops.cmd index 41ec44e7b..5ef39385c 100644 --- a/traces/trace-02-ops.cmd +++ b/traces/trace-02-ops.cmd @@ -1,4 +1,4 @@ -# Test of insert_head, insert_tail, and remove_head +# Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid option fail 0 option malloc 0 new @@ -8,9 +8,13 @@ ih dolphin it meerkat it bear it gerbil +it tiger +rt tiger +dm +dm +it meerkat rh dolphin rh bear -rh gerbil -rh meerkat rh bear rh gerbil +rh meerkat diff --git a/traces/trace-04-ops.cmd b/traces/trace-04-ops.cmd index b6911b33b..aec444420 100644 --- a/traces/trace-04-ops.cmd +++ b/traces/trace-04-ops.cmd @@ -1,10 +1,13 @@ -# Test of insert_head, insert_tail, size, and sort +# Test of insert_head, insert_tail, size, swap, and sort option fail 0 option malloc 0 new ih gerbil ih bear ih dolphin +swap +rh bear +ih bear size it meerkat it bear diff --git a/traces/trace-05-ops.cmd b/traces/trace-05-ops.cmd index 19085e920..63f56ef19 100644 --- a/traces/trace-05-ops.cmd +++ b/traces/trace-05-ops.cmd @@ -1,4 +1,4 @@ -# Test of insert_head, insert_tail, remove_head, reverse, size, and sort +# Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort option fail 0 option malloc 0 new @@ -16,13 +16,13 @@ reverse size sort it fish +swap reverse -rh fish rh meerkat -reverse -rh bear -rh bear +rh fish rh gerbil rh gerbil +rh bear +rh bear size free diff --git a/traces/trace-06-ops.cmd b/traces/trace-06-ops.cmd new file mode 100644 index 000000000..b7f9f4f88 --- /dev/null +++ b/traces/trace-06-ops.cmd @@ -0,0 +1,8 @@ +# Test of insert_head, insert_tail, delete duplicate, and sort +new +ih RAND 4 +it gerbil 3 +it lion 2 +it zebra 2 +sort +dedup \ No newline at end of file diff --git a/traces/trace-13-perf.cmd b/traces/trace-13-perf.cmd deleted file mode 100644 index abd926d37..000000000 --- a/traces/trace-13-perf.cmd +++ /dev/null @@ -1,8 +0,0 @@ -# Test performance of insert_tail -option fail 0 -option malloc 0 -new -ih dolphin 1000000 -it gerbil 1000 -reverse -it jaguar 1000 diff --git a/traces/trace-14-perf.cmd b/traces/trace-14-perf.cmd index ce0fe458c..32dcbee56 100644 --- a/traces/trace-14-perf.cmd +++ b/traces/trace-14-perf.cmd @@ -1,7 +1,9 @@ -# Test performance of size +# Test performance of insert_tail, reverse, and sort option fail 0 option malloc 0 new ih dolphin 1000000 -size 1000 +it gerbil 1000000 +reverse +sort diff --git a/traces/trace-15-perf.cmd b/traces/trace-15-perf.cmd index 56985c58d..88492b32f 100644 --- a/traces/trace-15-perf.cmd +++ b/traces/trace-15-perf.cmd @@ -1,11 +1,24 @@ -# Test performance of insert_tail, size, reverse, and sort +# Test performance of sort with random and descending orders +# 10000: all correct sorting algorithms are expected pass +# 50000: sorting algorithms with O(n^2) time complexity are expected failed +# 100000: sorting algorithms with O(nlogn) time complexity are expected pass option fail 0 option malloc 0 new -ih dolphin 1000000 -it gerbil 1000000 -size 1000 +ih RAND 10000 +sort +reverse +sort +free +new +ih RAND 50000 +sort +reverse +sort +free +new +ih RAND 100000 +sort reverse sort -size 1000 - +free diff --git a/traces/trace-16-perf.cmd b/traces/trace-16-perf.cmd index 88492b32f..abd926d37 100644 --- a/traces/trace-16-perf.cmd +++ b/traces/trace-16-perf.cmd @@ -1,24 +1,8 @@ -# Test performance of sort with random and descending orders -# 10000: all correct sorting algorithms are expected pass -# 50000: sorting algorithms with O(n^2) time complexity are expected failed -# 100000: sorting algorithms with O(nlogn) time complexity are expected pass +# Test performance of insert_tail option fail 0 option malloc 0 new -ih RAND 10000 -sort +ih dolphin 1000000 +it gerbil 1000 reverse -sort -free -new -ih RAND 50000 -sort -reverse -sort -free -new -ih RAND 100000 -sort -reverse -sort -free +it jaguar 1000 diff --git a/traces/trace-17-complexity.cmd b/traces/trace-17-complexity.cmd index ee02b5cca..6a68c9241 100644 --- a/traces/trace-17-complexity.cmd +++ b/traces/trace-17-complexity.cmd @@ -1,5 +1,7 @@ -# Test if q_insert_tail and q_size is constant time complexity +# Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant option simulation 1 it -size +ih +rh +rt option simulation 0