diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..9705d27f7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: Chromium +Language: Cpp +MaxEmptyLinesToKeep: 3 +IndentCaseLabels: false +AllowShortIfStatementsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +DerivePointerAlignment: false +PointerAlignment: Right +SpaceAfterCStyleCast: true +TabWidth: 4 +UseTab: Never +IndentWidth: 4 +BreakBeforeBraces: Linux +AccessModifierOffset: -4 diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..a8aef756a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -O0 -g -Wall -Werror + +all: qtest + -tar -cf handin.tar queue.c queue.h + +queue.o: queue.c queue.h harness.h + $(CC) $(CFLAGS) -c queue.c + +qtest: qtest.c report.c console.c harness.c queue.o + $(CC) $(CFLAGS) -o qtest qtest.c report.c console.c harness.c queue.o + tar cf handin.tar queue.c queue.h + +test: qtest driver.py + chmod +x driver.py + ./driver.py + +clean: + rm -f *.o *~ qtest + rm -rf *.dSYM + (cd traces; rm -f *~) + diff --git a/README b/README new file mode 100644 index 000000000..21112ab30 --- /dev/null +++ b/README @@ -0,0 +1,51 @@ +This is the handout directory for the 15-213 C Lab. + +************************ +Running the autograders: +************************ + +Before running the autograders, compile your code to create the testing program qtest + linux> make + +Check the correctness of your code: + linux> make test + +****** +Using qtest: +****** + +qtest provides a command interpreter that can create and manipulate queues. + +Run ./qtest -h to see the list of command-line options + +When you execute ./qtest, it will give a command prompt "cmd>". Type +"help" to see a list of available commands + + +****** +Files: +****** + +# You will handing in these two files +queue.h Modified version of declarations including new fields you want to introduce +queue.c Modified version of queue code to fix deficiencies of original code + +# Tools for evaluating your queue code +Makefile Builds the evaluation program qtest +README This file +driver.py* The C lab driver program, runs qtest on a standard set of traces + +# Helper files + +console.{c,h}: Implements command-line interpreter for qtest +report.{c,h}: Implements printing of information at different levels of verbosity +harness.{c,h}: Customized version of malloc and free to provide rigorous testing framework +qtest.c Code for qtest + +# Trace files + +traces/trace-XX-CAT.cmd Trace files used by the driver. These are input files for qtest. + They are short and simple. We encourage to study them to see what tests are being performed. + XX is the trace number (1-15). CAT describes the general nature of the test. + +traces/trace-eg.cmd: A simple, documented trace file to demonstrate the operation of qtest diff --git a/console.c b/console.c new file mode 100644 index 000000000..9682e4b4d --- /dev/null +++ b/console.c @@ -0,0 +1,645 @@ +/* Implementation of simple command-line interface */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "report.h" +#include "console.h" + + +/* Some global values */ +static cmd_ptr cmd_list = NULL; +static param_ptr param_list = NULL; +static bool block_flag = false; +static bool prompt_flag = true; + +/* Am I timing a command that has the console blocked? */ +static bool block_timing = false; + +/* Time of day */ +static double first_time; +static double last_time; + +/* + Implement buffered I/O using variant of RIO package from CS:APP + Must create stack of buffers to handle I/O with nested source commands. +*/ + +#define RIO_BUFSIZE 8192 +typedef struct RIO_ELE rio_t, *rio_ptr; + +struct RIO_ELE { + int fd; /* File descriptor */ + int cnt; /* Unread bytes in internal buffer */ + char *bufptr; /* Next unread byte in internal buffer */ + char buf[RIO_BUFSIZE]; /* Internal buffer */ + rio_ptr prev; /* Next element in stack */ +}; + +rio_ptr buf_stack; +char linebuf[RIO_BUFSIZE]; + +/* Maximum file descriptor */ +int fd_max = 0; + +/* Parameters */ +static int err_limit = 5; +static int err_cnt = 0; +static int echo = 0; + +static bool quit_flag = false; +static char *prompt = "cmd>"; + + +/* Optional function to call as part of exit process */ +/* Maximum number of quit functions */ + +#define MAXQUIT 10 +static cmd_function quit_helpers[MAXQUIT]; +static int quit_helper_cnt = 0; + +bool do_quit_cmd(int argc, char *argv[]); +bool do_help_cmd(int argc, char *argv[]); +bool do_option_cmd(int argc, char *argv[]); +bool do_source_cmd(int argc, char *argv[]); +bool do_log_cmd(int argc, char *argv[]); +bool do_time_cmd(int argc, char *argv[]); +bool do_comment_cmd(int argc, char *argv[]); + +static void init_in(); + +static bool push_file(char *fname); +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("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); +#if 0 + add_param("megabytes", &mblimit, "Maximum megabytes allowed", NULL); + add_param("seconds", &timelimit, "Maximum seconds allowed", + change_timeout); +#endif + init_in(); + init_time(&last_time); + first_time = last_time; +} + +/* Add a new command */ +void add_cmd(char *name, cmd_function operation, char *documentation) +{ + cmd_ptr next_cmd = cmd_list; + cmd_ptr *last_loc = &cmd_list; + while (next_cmd && strcmp(name, next_cmd->name) > 0) { + last_loc = &next_cmd->next; + next_cmd = next_cmd->next; + } + cmd_ptr ele = (cmd_ptr) malloc_or_fail(sizeof(cmd_ele), "add_cmd"); + ele->name = name; + ele->operation = operation; + ele->documentation = documentation; + ele->next = next_cmd; + *last_loc = ele; +} + +/* Add a new parameter */ +void add_param(char *name, + int *valp, + char *documentation, + setter_function setter) +{ + param_ptr next_param = param_list; + param_ptr *last_loc = ¶m_list; + while (next_param && strcmp(name, next_param->name) > 0) { + last_loc = &next_param->next; + next_param = next_param->next; + } + param_ptr ele = (param_ptr) malloc_or_fail(sizeof(param_ele), "add_param"); + ele->name = name; + ele->valp = valp; + ele->documentation = documentation; + ele->setter = setter; + ele->next = next_param; + *last_loc = ele; +} + + +/* Parse a string into a command line */ +char **parse_args(char *line, int *argcp) +{ + /* + Must first determine how many arguments there are. + Replace all white space with null characters + */ + size_t len = strlen(line); + /* First copy into buffer with each substring null-terminated */ + char *buf = malloc_or_fail(len + 1, "parse_args"); + char *src = line; + char *dst = buf; + bool skipping = true; + int c; + int argc = 0; + while ((c = *src++) != '\0') { + if (isspace(c)) { + if (!skipping) { + /* Hit end of word */ + *dst++ = '\0'; + skipping = true; + } + } else { + if (skipping) { + /* Hit start of new word */ + argc++; + skipping = false; + } + *dst++ = c; + } + } + /* Now assemble into array of strings */ + char **argv = calloc_or_fail(argc, sizeof(char *), "parse_args"); + size_t i; + src = buf; + for (i = 0; i < argc; i++) { + argv[i] = strsave_or_fail(src, "parse_args"); + src += strlen(argv[i]) + 1; + } + free_block(buf, len + 1); + *argcp = argc; + return argv; +} + +void record_error() +{ + err_cnt++; + if (err_cnt >= err_limit) { + report(1, "Error limit exceeded. Stopping command execution"); + quit_flag = true; + } +} + +/* Execute a command that has already been split into arguments */ +static bool interpret_cmda(int argc, char *argv[]) +{ + if (argc == 0) + return true; + /* Try to find matching command */ + cmd_ptr next_cmd = cmd_list; + bool ok = true; + while (next_cmd && strcmp(argv[0], next_cmd->name) != 0) + next_cmd = next_cmd->next; + if (next_cmd) { + ok = next_cmd->operation(argc, argv); + if (!ok) + record_error(); + } else { + report(1, "Unknown command '%s'", argv[0]); + record_error(); + ok = false; + } + return ok; +} + +/* Execute a command from a command line */ +bool interpret_cmd(char *cmdline) +{ + int argc; + if (quit_flag) + return false; +#if RPT >= 6 + report(6, "Interpreting command '%s'\n", cmdline); +#endif + char **argv = parse_args(cmdline, &argc); + bool ok = interpret_cmda(argc, argv); + int i; + for (i = 0; i < argc; i++) + free_string(argv[i]); + free_array(argv, argc, sizeof(char *)); + return ok; +} + +/* Set function to be executed as part of program exit */ +void add_quit_helper(cmd_function qf) +{ + if (quit_helper_cnt < MAXQUIT) { + quit_helpers[quit_helper_cnt++] = qf; + } else + report_event(MSG_FATAL, "Exceeded limit on quit helpers"); +} + +/* Set prompt string */ +void set_prompt(char *p) +{ + prompt = p; +} + +/* Turn echoing on/off */ +void set_echo(bool on) +{ + echo = on ? 1 : 0; +} + + +/* Built-in commands */ +bool do_quit_cmd(int argc, char *argv[]) +{ + cmd_ptr c = cmd_list; + bool ok = true; + while (c) { + cmd_ptr ele = c; + c = c->next; + free_block(ele, sizeof(cmd_ele)); + } + param_ptr p = param_list; + while (p) { + param_ptr ele = p; + p = p->next; + free_block(ele, sizeof(param_ele)); + } + while (buf_stack) + pop_file(); + int i; + for (i = 0; i < quit_helper_cnt; i++) { + ok = ok && quit_helpers[i](argc, argv); + } + quit_flag = true; + return ok; +} + +bool do_help_cmd(int argc, char *argv[]) +{ + cmd_ptr clist = cmd_list; + report(1, "Commands:", argv[0]); + while (clist) { + report(1, "\t%s\t%s", clist->name, clist->documentation); + clist = clist->next; + } + param_ptr plist = param_list; + report(1, "Options:"); + while (plist) { + report(1, "\t%s\t%d\t%s", plist->name, *plist->valp, + plist->documentation); + plist = plist->next; + } + return true; +} + +bool do_comment_cmd(int argc, char *argv[]) +{ + int i; + if (echo) + return true; + for (i = 0; i < argc - 1; i++) { + report_noreturn(1, "%s ", argv[i]); + } + if (i < argc) { + report(1, "%s", argv[i]); + } + return true; +} + +/* Extract integer from text and store at loc */ +bool get_int(char *vname, int *loc) +{ + char *end = NULL; + long int v = strtol(vname, &end, 0); + if (v == LONG_MIN || *end != '\0') + return false; + *loc = (int) v; + return true; +} + +bool do_option_cmd(int argc, char *argv[]) +{ + size_t i; + if (argc == 1) { + param_ptr plist = param_list; + report(1, "Options:"); + while (plist) { + report(1, "\t%s\t%d\t%s", plist->name, *plist->valp, + plist->documentation); + plist = plist->next; + } + return true; + } + for (i = 1; i < argc; i++) { + char *name = argv[i]; + int value = 0; + bool found = false; + /* Get value from next argument */ + if (i + 1 >= argc) { + report(1, "No value given for parameter %s", name); + return false; + } else if (!get_int(argv[++i], &value)) { + report(1, "Cannot parse '%s' as integer", argv[i]); + return false; + } + /* Find parameter in list */ + param_ptr plist = param_list; + while (!found && plist) { + if (strcmp(plist->name, name) == 0) { + int oldval = *plist->valp; + *plist->valp = value; + if (plist->setter) + plist->setter(oldval); + found = true; + } else + plist = plist->next; + } + /* Didn't find parameter */ + if (!found) { + report(1, "Unknown parameter '%s'", name); + return false; + } + } + return true; +} + +bool do_source_cmd(int argc, char *argv[]) +{ + if (argc < 2) { + report(1, "No source file given"); + return false; + } + if (!push_file(argv[1])) { + report(1, "Could not open source file '%s'", argv[1]); + return false; + } + return true; +} + +bool do_log_cmd(int argc, char *argv[]) +{ + if (argc < 2) { + report(1, "No log file given"); + return false; + } + bool result = set_logfile(argv[1]); + if (!result) { + report(1, "Couldn't open log file '%s'", argv[1]); + } + return result; +} + +bool do_time_cmd(int argc, char *argv[]) +{ + double delta = delta_time(&last_time); + bool ok = true; + if (argc <= 1) { + double elapsed = last_time - first_time; + report(1, "Elapsed time = %.3f, Delta time = %.3f", elapsed, delta); + } else { + ok = interpret_cmda(argc - 1, argv + 1); + if (block_flag) { + block_timing = true; + } else { + delta = delta_time(&last_time); + report(1, "Delta time = %.3f", delta); + } + } + return ok; +} + +/* Create new buffer for named file. + Name == NULL for stdin. + Return true if successful. +*/ +static bool push_file(char *fname) +{ + int fd = fname ? open(fname, O_RDONLY) : STDIN_FILENO; + if (fd < 0) + return false; + if (fd > fd_max) + fd_max = fd; + rio_ptr rnew = malloc_or_fail(sizeof(rio_t), "push_file"); + rnew->fd = fd; + rnew->cnt = 0; + rnew->bufptr = rnew->buf; + rnew->prev = buf_stack; + buf_stack = rnew; + return true; +} + +/* Pop a file buffer from stack. + Return true if stack is now empty +*/ +static void pop_file() +{ + if (buf_stack) { + rio_ptr rsave = buf_stack; + buf_stack = rsave->prev; + close(rsave->fd); + free_block(rsave, sizeof(rio_t)); + } +} + + +/* Handling of input */ +static void init_in() +{ + buf_stack = NULL; +} + +/* Read command from input file. + When hit EOF, close that file and return NULL +*/ +static char *readline() +{ + int cnt; + char c; + char *lptr = linebuf; + + if (buf_stack == NULL) + return NULL; + + for (cnt = 0; cnt < RIO_BUFSIZE - 2; cnt++) { + if (buf_stack->cnt <= 0) { + /* Need to read from input file */ + buf_stack->cnt = read(buf_stack->fd, buf_stack->buf, RIO_BUFSIZE); + buf_stack->bufptr = buf_stack->buf; + if (buf_stack->cnt <= 0) { + /* Encountered EOF */ + pop_file(); + if (cnt > 0) { + /* Last line of file did not terminate with newline. */ + /* Terminate line & return it */ + *lptr++ = '\n'; + *lptr++ = '\0'; + if (echo) { + report_noreturn(1, prompt); + report_noreturn(1, linebuf); + } + return linebuf; + } else + return NULL; + } + } + /* Have text in buffer */ + c = *buf_stack->bufptr++; + *lptr++ = c; + buf_stack->cnt--; + if (c == '\n') + break; + } + if (c != '\n') { + /* Hit buffer limit. Artificially terminate line */ + *lptr++ = '\n'; + } + *lptr++ = '\0'; + if (echo) { + report_noreturn(1, prompt); + report_noreturn(1, linebuf); + } + return linebuf; +} + + +void block_console() +{ + block_flag = true; +} + +void unblock_console() +{ + block_flag = false; + if (block_timing) { + double delta = delta_time(&last_time); + report(1, "Delta time = %.3f", delta); + } + block_timing = false; +} + + +/* Determine if there is a complete command line in input buffer */ +static bool read_ready() +{ + int i; + for (i = 0; buf_stack && i < buf_stack->cnt; i++) { + if (buf_stack->bufptr[i] == '\n') + return true; + } + return false; +} + +/* + Handle command processing in program that uses select as main control loop. + Like select, but checks whether command input either present in internal + buffer + or readable from command input. If so, that command is executed. + Same return as select. Command input file removed from readfds + + nfds should be set to the maximum file descriptor for network sockets. + If nfds == 0, this indicates that there is no pending network activity +*/ + +int cmd_select(int nfds, + fd_set *readfds, + fd_set *writefds, + fd_set *exceptfds, + struct timeval *timeout) +{ + char *cmdline; + int infd; + fd_set local_readset; + while (!block_flag && read_ready()) { + cmdline = readline(); + interpret_cmd(cmdline); + prompt_flag = true; + } + if (cmd_done()) + return 0; + if (!block_flag) { + /* Process any commands in input buffer */ + if (readfds == NULL) + readfds = &local_readset; + /* Add input fd to readset for select */ + infd = buf_stack->fd; + FD_SET(infd, readfds); + if (infd == STDIN_FILENO && prompt_flag) { + printf("%s", prompt); + fflush(stdout); + prompt_flag = true; + } + if (infd >= nfds) { + nfds = infd + 1; + } + } + if (nfds == 0) + return 0; + int result = select(nfds, readfds, writefds, exceptfds, timeout); + if (result <= 0) + return result; + infd = buf_stack->fd; + if (readfds && FD_ISSET(infd, readfds)) { + /* Commandline input available */ + FD_CLR(infd, readfds); + result--; + cmdline = readline(); + if (cmdline) + interpret_cmd(cmdline); + } + return result; +} + + +bool start_cmd(char *infile_name) +{ + bool ok = push_file(infile_name); + if (!ok) + report(1, "Could not open source file '%s'", + infile_name ? infile_name : "standard input"); + return ok; +} + +bool cmd_done() +{ + return buf_stack == NULL || quit_flag; +} + + +bool finish_cmd() +{ + bool ok = true; + if (!quit_flag) { + ok = ok && do_quit_cmd(0, NULL); + } + return ok && err_cnt == 0; +} + +bool run_console(char *infile_name) +{ + if (!push_file(infile_name)) { + report(1, "ERROR: Could not open source file '%s'", infile_name); + return false; + } + while (!cmd_done()) { + cmd_select(0, NULL, NULL, NULL, NULL); + } + return err_cnt == 0; +} diff --git a/console.h b/console.h new file mode 100644 index 000000000..8ed2f5981 --- /dev/null +++ b/console.h @@ -0,0 +1,104 @@ +/* Implementation of simple command-line interface */ + +/* Each command defined in terms of a function */ +typedef bool (*cmd_function)(int argc, char *argv[]); + +/* Information about each command */ +/* Organized as linked list in alphabetical order */ +typedef struct CELE cmd_ele, *cmd_ptr; +struct CELE { + char *name; + cmd_function operation; + char *documentation; + cmd_ptr next; +}; + +/* Optionally supply function that gets invoked when parameter changes */ +typedef void (*setter_function)(int oldval); + +/* Integer-valued parameters */ +typedef struct PELE param_ele, *param_ptr; +struct PELE { + char *name; + int *valp; + char *documentation; + /* Function that gets called whenever parameter changes */ + setter_function setter; + param_ptr next; +}; + +/* Initialize interpreter */ +void init_cmd(); + +/* Add a new command */ +void add_cmd(char *name, cmd_function operation, char *documentation); + +/* Add a new parameter */ +void add_param(char *name, + int *valp, + char *doccumentation, + setter_function setter); + +/* Execute a command from a command line */ +bool interpret_cmd(char *cmdline); + +/* Execute a sequence of commands read from a file */ +bool interpret_file(FILE *fp); + +/* Extract integer from text and store at loc */ +bool get_int(char *vname, int *loc); + +/* Add function to be executed as part of program exit */ +void add_quit_helper(cmd_function qf); + +/* Set prompt */ +void set_prompt(char *prompt); + +/* Turn echoing on/off */ +void set_echo(bool on); + +/* + Some console commands require network activity to complete. + Program maintains a flag indicating whether console is ready to execute a new + command (unblocked) or it must wait for pending network activity (blocked). + + The following calls set/clear that flag +*/ + +void block_console(); +void unblock_console(); + +/* Start command intrepretation. + If infile_name is NULL, then read commands from stdin + Otherwise, use infile as command source +*/ +bool start_cmd(char *infile_name); + + +/* Is it time to quit the command loop? */ +bool cmd_done(); + +/* Complete command interpretation */ +/* Return true if no errors occurred */ +bool finish_cmd(); + +/* + Handle command processing in program that uses select as main + control loop. Like select, but (if console unblocked) it checks + whether command input either present in internal buffer or readable + from command input. If so, that command is executed. Same return + as select. Command input file removed from readfds + + nfds should be set to the maximum file descriptor for network sockets. + If nfds == 0, this indicates that there is no pending network activity +*/ + +int cmd_select(int nfds, + fd_set *readfds, + fd_set *writefds, + fd_set *exceptfds, + struct timeval *timeout); + +/* Run command loop. Non-null infile_name implies read commands from that file + */ +bool run_console(char *infile_name); diff --git a/driver.py b/driver.py new file mode 100755 index 000000000..de8dff6ca --- /dev/null +++ b/driver.py @@ -0,0 +1,151 @@ +#!/usr/bin/python + +import subprocess +import sys +import getopt + +# Driver program for C programming exercise +class Tracer: + + traceDirectory = "./traces" + qtest = "./qtest" + verbLevel = 0 + autograde = False + + traceDict = { + 1 : "trace-01-ops", + 2 : "trace-02-ops", + 3 : "trace-03-ops", + 4 : "trace-04-ops", + 5 : "trace-05-ops", + 6 : "trace-06-string", + 7 : "trace-07-robust", + 8 : "trace-08-robust", + 9 : "trace-09-robust", + 10 : "trace-10-malloc", + 11 : "trace-11-malloc", + 12 : "trace-12-malloc", + 13 : "trace-13-perf", + 14 : "trace-14-perf", + 15 : "trace-15-perf" + } + + traceProbs = { + 1 : "Trace-01", + 2 : "Trace-02", + 3 : "Trace-03", + 4 : "Trace-04", + 5 : "Trace-05", + 6 : "Trace-06", + 7 : "Trace-07", + 8 : "Trace-08", + 9 : "Trace-09", + 10 : "Trace-10", + 11 : "Trace-11", + 12 : "Trace-12", + 13 : "Trace-13", + 14 : "Trace-14", + 15 : "Trace-15" + } + + + maxScores = [0, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7] + + def __init__(self, qtest = "", verbLevel = 0, autograde = False): + if qtest != "": + self.qtest = qtest + self.verbLevel = verbLevel + self.autograde = autograde + + def runTrace(self, tid): + if not tid in self.traceDict: + print "ERROR: No trace with id %d" % tid + return False + fname = "%s/%s.cmd" % (self.traceDirectory, self.traceDict[tid]) + vname = "%d" % self.verbLevel + clist = [self.qtest, "-v", vname, "-f", fname] + try: + retcode = subprocess.call(clist) + except Exception as e: + print "Call of '%s' failed: %s" % (" ".join(clist), e) + return False + return retcode == 0 + + def run(self, tid = 0): + scoreDict = { k : 0 for k in self.traceDict.keys() } + print "---\tTrace\t\tPoints" + if tid == 0: + tidList = self.traceDict.keys() + else: + if not tid in self.traceDict: + print "ERROR: Invalid trace ID %d" % tid + return + tidList = [tid] + score = 0 + maxscore = 0 + for t in tidList: + tname = self.traceDict[t] + if self.verbLevel > 0: + print "+++ TESTING trace %s:" % tname + ok = self.runTrace(t) + maxval = self.maxScores[t] + tval = maxval if ok else 0 + print "---\t%s\t%d/%d" % (tname, tval, maxval) + score += tval + maxscore += maxval + scoreDict[t] = tval + print "---\tTOTAL\t\t%d/%d" % (score, maxscore) + if self.autograde: + # Generate JSON string + jstring = '{"scores": {' + first = True + for k in scoreDict.keys(): + if not first: + jstring += ', ' + first = False + jstring += '"%s" : %d' % (self.traceProbs[k], scoreDict[k]) + jstring += '}}' + print jstring + +def usage(name): + print "Usage: %s [-h] [-p PROG] [-t TID] [-v VLEVEL]" % name + print " -h Print this message" + print " -p PROG Program to test" + print " -t TID Trace ID to test" + print " -v VLEVEL Set verbosity level (0-3)" + sys.exit(0) + +def run(name, args): + prog = "" + tid = 0 + vlevel = 1 + levelFixed = False + autograde = False + + + optlist, args = getopt.getopt(args, 'hp:t:v:A') + for (opt, val) in optlist: + if opt == '-h': + usage(name) + elif opt == '-p': + prog = val + elif opt == '-t': + tid = int(val) + elif opt == '-v': + vlevel = int(val) + levelFixed = True + elif opt == '-A': + autograde = True + else: + print "Unrecognized option '%s'" % opt + usage(name) + if not levelFixed and autograde: + vlevel = 0 + t = Tracer(qtest = prog, verbLevel = vlevel, autograde = autograde) + t.run(tid) + +if __name__ == "__main__": + run(sys.argv[0], sys.argv[1:]) + + + diff --git a/harness.c b/harness.c new file mode 100644 index 000000000..7247bc586 --- /dev/null +++ b/harness.c @@ -0,0 +1,281 @@ +/* Test support code */ + +#include +#include +#include +#include +#include +#include + +#include "report.h" + +/* Our program needs to use regular malloc/free */ +#define INTERNAL 1 +#include "harness.h" + +/** Special values **/ + +/* Value at start of every allocated block */ +#define MAGICHEADER 0xdeadbeef +/* Value when deallocate block */ +#define MAGICFREE 0xffffffff +/* Value at end of every block */ +#define MAGICFOOTER 0xbeefdead +/* Byte to fill newly malloced space with */ +#define FILLCHAR 0x55 + +/** Data structures used by our code **/ + +/* + Represent allocated blocks as doubly-linked list, with + next and prev pointers at beginning +*/ + + +typedef struct BELE { + struct BELE *next; + struct BELE *prev; + size_t payload_size; + size_t magic_header; /* Marker to see if block seems legitimate */ + unsigned char payload[0]; + /* Also place magic number at tail of every block */ +} block_ele_t; + +static block_ele_t *allocated = NULL; +static size_t allocated_count = 0; +/* Percent probability of malloc failure */ +int fail_probability = 0; +static bool cautious_mode = true; +static bool noallocate_mode = false; +static bool error_occurred = false; +static char *error_message = ""; + +static int time_limit = 1; + +/* + * Data for managing exceptions + */ +static jmp_buf env; +static volatile sig_atomic_t jmp_ready = false; +static bool time_limited = false; + + +/* + Internal functions + */ +/* Should this allocation fail? */ +static bool fail_allocation() +{ + double weight = (double) random() / RAND_MAX; + return (weight < 0.01 * fail_probability); +} + +/* + Find header of block, given its payload. + Signal error if doesn't seem like legitimate block + */ +static block_ele_t *find_header(void *p) +{ + if (p == NULL) { + report_event(MSG_ERROR, "Attempting to free null block"); + error_occurred = true; + } + block_ele_t *b = (block_ele_t *) ((size_t) p - sizeof(block_ele_t)); + if (cautious_mode) { + /* Make sure this is really an allocated block */ + block_ele_t *ab = allocated; + bool found = false; + while (ab && !found) { + found = ab == b; + ab = ab->next; + } + if (!found) { + report_event(MSG_ERROR, + "Attempted to free unallocated block. Address = %p", + p); + error_occurred = true; + } + } + if (b->magic_header != MAGICHEADER) { + report_event( + MSG_ERROR, + "Attempted to free unallocated or corrupted block. Address = %p", + p); + error_occurred = true; + } + return b; +} + +/* Given pointer to block, find its footer */ +static size_t *find_footer(block_ele_t *b) +{ + size_t *p = (size_t *) ((size_t) b + b->payload_size + sizeof(block_ele_t)); + return p; +} + + +/* + Implementation of application functions + */ +void *test_malloc(size_t size) +{ + if (noallocate_mode) { + report_event(MSG_FATAL, "Calls to malloc disallowed"); + return NULL; + } + if (fail_allocation()) { + report_event(MSG_WARN, "Malloc returning NULL"); + return NULL; + } + block_ele_t *new_block = + malloc(size + sizeof(block_ele_t) + sizeof(size_t)); + if (new_block == NULL) { + report_event(MSG_FATAL, "Couldn't allocate any more memory"); + error_occurred = true; + } + new_block->magic_header = MAGICHEADER; + new_block->payload_size = size; + *find_footer(new_block) = MAGICFOOTER; + void *p = (void *) &new_block->payload; + memset(p, FILLCHAR, size); + new_block->next = allocated; + new_block->prev = NULL; + if (allocated) + allocated->prev = new_block; + allocated = new_block; + allocated_count++; + return p; +} + +void test_free(void *p) +{ + if (noallocate_mode) { + report_event(MSG_FATAL, "Calls to free disallowed"); + return; + } + if (p == NULL) { + report(MSG_ERROR, "Attempt to free NULL"); + error_occurred = true; + return; + } + block_ele_t *b = find_header(p); + size_t footer = *find_footer(b); + if (footer != MAGICFOOTER) { + report_event(MSG_ERROR, + "Corruption detected in block with address %p when " + "attempting to free it", + p); + error_occurred = true; + } + b->magic_header = MAGICFREE; + *find_footer(b) = MAGICFREE; + memset(p, FILLCHAR, b->payload_size); + + /* Unlink from list */ + block_ele_t *bn = b->next; + block_ele_t *bp = b->prev; + if (bp) + bp->next = bn; + else + allocated = bn; + if (bn) + bn->prev = bp; + + free(b); + allocated_count--; +} + +size_t allocation_check() +{ + return allocated_count; +} + +/* + Implementation of functions for testing + */ + + +/* + Set/unset cautious mode. + In this mode, makes extra sure any block to be freed is currently allocated. +*/ +void set_cautious_mode(bool cautious) +{ + cautious_mode = cautious; +} + +/* + Set/unset restricted allocation mode. + In this mode, calls to malloc and free are disallowed. + */ +void set_noallocate_mode(bool noallocate) +{ + noallocate_mode = noallocate; +} + + +/* + Return whether any errors have occurred since last time set error limit + */ +bool error_check() +{ + bool e = error_occurred; + error_occurred = false; + return e; +} + +/* + * Prepare for a risky operation using setjmp. + * Function returns true for initial return, false for error return + */ +bool exception_setup(bool limit_time) +{ + if (sigsetjmp(env, 1)) { + /* Got here from longjmp */ + jmp_ready = false; + if (time_limited) { + alarm(0); + time_limited = false; + } + if (error_message) { + report_event(MSG_ERROR, error_message); + } + error_message = ""; + return false; + } else { + /* Got here from initial call */ + jmp_ready = true; + if (limit_time) { + alarm(time_limit); + time_limited = true; + } + return true; + } +} + +/* + * Call once past risky code + */ +void exception_cancel() +{ + if (time_limited) { + alarm(0); + time_limited = false; + } + jmp_ready = false; + error_message = ""; +} + + +/* + * Use longjmp to return to most recent exception setup + */ +void trigger_exception(char *msg) +{ + error_occurred = true; + error_message = msg; + if (jmp_ready) + siglongjmp(env, 1); + else + exit(1); +} diff --git a/harness.h b/harness.h new file mode 100644 index 000000000..c80c71121 --- /dev/null +++ b/harness.h @@ -0,0 +1,60 @@ +#include +#include +#include + +/* + This test harness enables us to do stringent testing of code. + It overloads the library versions of malloc and free with ones that + allow checking for common allocation errors. +*/ + +void *test_malloc(size_t size); +void test_free(void *p); + +#ifdef INTERNAL +/* Report number of allocated blocks */ +size_t allocation_check(); + +/* Probability of malloc failing, expressed as percent */ +int fail_probability; + +/* + Set/unset cautious mode. + In this mode, makes extra sure any block to be freed is currently allocated. +*/ +void set_cautious_mode(bool cautious); + +/* + Set/unset restricted allocation mode. + In this mode, calls to malloc and free are disallowed. + */ +void set_noallocate_mode(bool noallocate); + + +/* + Return whether any errors have occurred since last time checked + */ +bool error_check(); + +/* + * Prepare for a risky operation using setjmp. + * Function returns true for initial return, false for error return + */ +bool exception_setup(bool limit_time); + +/* + * Call once past risky code + */ +void exception_cancel(); + +/* + * Use longjmp to return to most recent exception setup. Include error message + */ +void trigger_exception(char *msg); + + +#else +/* Tested program use our versions of malloc and free */ +#define malloc test_malloc +#define free test_free +#endif diff --git a/qtest.c b/qtest.c new file mode 100644 index 000000000..2d68f4912 --- /dev/null +++ b/qtest.c @@ -0,0 +1,576 @@ +/* Implementation of testing code for queue code */ + +#include +#include +#include +#include +#include + +/* Our program needs to use regular malloc/free */ +#define INTERNAL 1 +#include "harness.h" + +/* What character limit will be used for displaying strings? */ +#define MAXSTRING 1024 +/* How much padding should be added to check for string overrun? */ +#define STRINGPAD MAXSTRING + +/* + It is a bit sketchy to use this #include file on the solution version of the + code. + OK as long as head field of queue_t structure is in first position in solution + code +*/ +#include "queue.h" + +#include "report.h" +#include "console.h" + +/***** Settable parameters *****/ + +/* + 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 +*/ +#define BIG_QUEUE 30 + +int big_queue_size = BIG_QUEUE; + +/******* Global variables ******/ + +/* Queue being tested */ +queue_t *q = NULL; +/* Number of elements in queue */ +size_t qcnt = 0; + +/* How many times can queue operations fail */ +int fail_limit = BIG_QUEUE; +int fail_count = 0; + +int string_length = MAXSTRING; + +/****** Forward declarations ******/ +static bool show_queue(int vlevel); +bool do_new(int argc, char *argv[]); +bool do_free(int argc, char *argv[]); +bool do_insert_head(int argc, char *argv[]); +bool do_insert_tail(int argc, char *argv[]); +bool do_remove_head(int argc, char *argv[]); +bool do_remove_head_quiet(int argc, char *argv[]); +bool do_reverse(int argc, char *argv[]); +bool do_size(int argc, char *argv[]); +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 " + "(default: n == 1)"); + add_cmd("it", do_insert_tail, + " str [n] | Insert string str at tail of queue n times " + "(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("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); +} + +bool do_new(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + bool ok = true; + if (q != NULL) { + report(3, "Freeing old queue"); + ok = do_free(argc, argv); + } + error_check(); + if (exception_setup(true)) + q = q_new(); + exception_cancel(); + qcnt = 0; + show_queue(3); + return ok && !error_check(); +} + +bool do_free(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + bool ok = true; + if (q == NULL) + report(3, "Warning: Calling free on null queue"); + error_check(); + if (qcnt > big_queue_size) + set_cautious_mode(false); + if (exception_setup(true)) + q_free(q); + exception_cancel(); + set_cautious_mode(true); + q = NULL; + qcnt = 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(); +} + +bool do_insert_head(int argc, char *argv[]) +{ + char *inserts; + char *lasts = NULL; + int reps = 1; + int r; + bool ok = true; + if (argc != 2 && argc != 3) { + report(1, "%s needs 1-2 arguments", argv[0]); + return false; + } + inserts = argv[1]; + if (argc == 3) { + if (!get_int(argv[2], &reps)) { + report(1, "Invalid number of insertions '%s'", argv[2]); + return false; + } + } + if (q == NULL) + report(3, "Warning: Calling insert head on null queue"); + error_check(); + if (exception_setup(true)) { + for (r = 0; ok && r < reps; r++) { + bool rval = q_insert_head(q, inserts); + if (rval) { + qcnt++; + if (!q->head->value) { + report(1, "ERROR: Failed to save copy of string in list"); + ok = false; + } else if (r == 0 && inserts == q->head->value) { + report(1, + "ERROR: Need to allocate and copy string for new " + "list element"); + ok = false; + break; + } else if (r == 1 && lasts == q->head->value) { + report(1, + "ERROR: Need to allocate separate string for each " + "list element"); + ok = false; + break; + } + lasts = q->head->value; + } else { + fail_count++; + if (fail_count < fail_limit) + report(2, "Insertion of %s failed", inserts); + else { + report(1, + "ERROR: Insertion of %s failed (%d failures total)", + inserts, fail_count); + ok = false; + } + } + ok = ok && !error_check(); + } + } + exception_cancel(); + show_queue(3); + return ok; +} + +bool do_insert_tail(int argc, char *argv[]) +{ + char *inserts; + int reps = 1; + int r; + bool ok = true; + if (argc != 2 && argc != 3) { + report(1, "%s needs 1-2 arguments", argv[0]); + return false; + } + inserts = argv[1]; + if (argc == 3) { + if (!get_int(argv[2], &reps)) { + report(1, "Invalid number of insertions '%s'", argv[2]); + return false; + } + } + if (q == NULL) + report(3, "Warning: Calling insert tail on null queue"); + error_check(); + if (exception_setup(true)) { + for (r = 0; ok && r < reps; r++) { + bool rval = q_insert_tail(q, inserts); + if (rval) { + qcnt++; + if (!q->head->value) { + report(1, "ERROR: Failed to save copy of string in list"); + ok = false; + } + } else { + fail_count++; + if (fail_count < fail_limit) + report(2, "Insertion of %s failed", inserts); + else { + report(1, + "ERROR: Insertion of %s failed (%d failures total)", + inserts, fail_count); + ok = false; + } + } + ok = ok && !error_check(); + } + } + exception_cancel(); + show_queue(3); + return ok; +} + +bool do_remove_head(int argc, char *argv[]) +{ + if (argc != 1 && argc != 2) { + report(1, "%s needs 0-1 arguments", argv[0]); + return false; + } + char *removes = malloc(string_length + STRINGPAD + 1); + if (removes == NULL) { + report(1, + "INTERNAL ERROR. Could not allocate space for removed strings"); + return false; + } + char *checks = malloc(string_length + 1); + if (checks == NULL) { + report(1, + "INTERNAL ERROR. Could not allocate space for removed strings"); + free(removes); + return false; + } + bool check = argc > 1; + bool ok = true; + if (check) { + strncpy(checks, argv[1], string_length + 1); + checks[string_length] = '\0'; + } + + removes[0] = '\0'; + memset(removes + 1, 'X', string_length + STRINGPAD - 1); + removes[string_length + STRINGPAD] = '\0'; + + if (q == NULL) + report(3, "Warning: Calling remove head on null queue"); + else if (q->head == NULL) + report(3, "Warning: Calling remove head on empty queue"); + error_check(); + bool rval = false; + if (exception_setup(true)) + rval = q_remove_head(q, removes, string_length + 1); + exception_cancel(); + if (rval) { + removes[string_length + STRINGPAD] = '\0'; + if (removes[0] == '\0') { + report(1, "ERROR: Failed to store removed value"); + ok = false; + } else if (removes[string_length + 1] != 'X') { + report(1, + "ERROR: copying of string in remove_head overflowed " + "destination buffer."); + ok = false; + } else { + report(2, "Removed %s from queue", removes); + } + qcnt--; + } else { + fail_count++; + if (!check && fail_count < fail_limit) { + report(2, "Removal from queue failed"); + } else { + report(1, "ERROR: Removal from queue failed (%d failures total)", + fail_count); + ok = false; + } + } + if (ok && check && strcmp(removes, checks) != 0) { + report(1, "ERROR: Removed value %s != expected value %s", removes, + checks); + ok = false; + } + show_queue(3); + free(removes); + free(checks); + return ok && !error_check(); +} + +bool do_remove_head_quiet(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + bool ok = true; + if (q == NULL) + report(3, "Warning: Calling remove head on null queue"); + else if (q->head == NULL) + report(3, "Warning: Calling remove head on empty queue"); + error_check(); + bool rval = false; + if (exception_setup(true)) + rval = q_remove_head(q, NULL, 0); + exception_cancel(); + if (rval) { + report(2, "Removed element from queue"); + qcnt--; + } else { + fail_count++; + if (fail_count < fail_limit) + report(2, "Removal failed"); + else { + report(1, "ERROR: Removal failed (%d failures total)", fail_count); + ok = false; + } + } + show_queue(3); + return ok && !error_check(); +} + +bool do_reverse(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + if (q == NULL) + report(3, "Warning: Calling reverse on null queue"); + error_check(); + set_noallocate_mode(true); + if (exception_setup(true)) + q_reverse(q); + exception_cancel(); + set_noallocate_mode(false); + show_queue(3); + return !error_check(); +} + +bool do_size(int argc, char *argv[]) +{ + if (argc != 1 && argc != 2) { + report(1, "%s takes 0-1 arguments", argv[0]); + return false; + } + int reps = 1; + int r; + bool ok = true; + if (argc != 1 && argc != 2) { + report(1, "%s needs 0-1 arguments", argv[0]); + return false; + } + if (argc == 2) { + if (!get_int(argv[1], &reps)) { + report(1, "Invalid number of calls to size '%s'", argv[2]); + } + } + int cnt = 0; + if (q == NULL) + report(3, "Warning: Calling size on null queue"); + error_check(); + if (exception_setup(true)) { + for (r = 0; ok && r < reps; r++) { + cnt = q_size(q); + ok = ok && !error_check(); + } + } + exception_cancel(); + if (ok) { + if (qcnt == cnt) { + report(2, "Queue size = %d", cnt); + } else { + report(1, + "ERROR: Computed queue size as %d, but correct value is %d", + cnt, (int) qcnt); + ok = false; + } + } + show_queue(3); + + return ok && !error_check(); +} + +static bool show_queue(int vlevel) +{ + bool ok = true; + if (verblevel < vlevel) + return true; + int cnt = 0; + if (q == NULL) { + report(vlevel, "q = NULL"); + return true; + } + report_noreturn(vlevel, "q = ["); + list_ele_t *e = q->head; + if (exception_setup(true)) { + while (ok && e && cnt < qcnt) { + if (cnt < big_queue_size) + report_noreturn(vlevel, cnt == 0 ? "%s" : " %s", e->value); + e = e->next; + cnt++; + ok = ok && !error_check(); + } + } + exception_cancel(); + if (!ok) { + report(vlevel, " ... ]"); + return false; + } + if (e == NULL) { + if (cnt <= big_queue_size) + report(vlevel, "]"); + else + report(vlevel, " ... ]"); + } else { + report(vlevel, " ... ]"); + report( + vlevel, + "ERROR: Either list has cycle, or queue has more than %d elements", + qcnt); + ok = false; + } + return ok; +} + +bool do_show(int argc, char *argv[]) +{ + if (argc != 1) { + report(1, "%s takes no arguments", argv[0]); + return false; + } + return show_queue(0); +} + +/* Signal handlers */ +void sigsegvhandler(int sig) +{ + trigger_exception( + "Segmentation fault occurred. You dereferenced a NULL or invalid " + "pointer"); +} + +void sigalrmhandler(int sig) +{ + trigger_exception( + "Time limit exceeded. Either you are in an infinite loop, or your " + "code is too inefficient"); +} + + +static void queue_init() +{ + fail_count = 0; + q = NULL; + signal(SIGSEGV, sigsegvhandler); + signal(SIGALRM, sigalrmhandler); +} + + +static bool queue_quit(int argc, char *argv[]) +{ + report(3, "Freeing queue"); + if (qcnt > big_queue_size) + set_cautious_mode(false); + if (exception_setup(true)) + q_free(q); + exception_cancel(); + set_cautious_mode(true); + size_t bcnt = allocation_check(); + if (bcnt > 0) { + report(1, "ERROR: Freed queue, but %lu blocks are still allocated", + bcnt); + return false; + } + return true; +} + + +static void usage(char *cmd) +{ + printf("Usage: %s [-h] [-f IFILE][-v VLEVEL][-l LFILE]\n", cmd); + printf("\t-h Print this information\n"); + printf("\t-f IFILE Read commands from IFILE\n"); + printf("\t-v VLEVEL Set verbosity level\n"); + printf("\t-l LFILE Echo results to LFILE\n"); + exit(0); +} + +#define BUFSIZE 256 + +int main(int argc, char *argv[]) +{ + /* To hold input file name */ + char buf[BUFSIZE]; + char *infile_name = NULL; + char lbuf[BUFSIZE]; + char *logfile_name = NULL; + int level = 4; + int c; + + while ((c = getopt(argc, argv, "hv:f:l:")) != -1) { + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'f': + strncpy(buf, optarg, BUFSIZE); + buf[BUFSIZE - 1] = '\0'; + infile_name = buf; + break; + case 'v': + level = atoi(optarg); + break; + case 'l': + strncpy(lbuf, optarg, BUFSIZE); + buf[BUFSIZE - 1] = '\0'; + logfile_name = lbuf; + break; + default: + printf("Unknown option '%c'\n", c); + usage(argv[0]); + break; + } + } + queue_init(); + init_cmd(); + console_init(); + set_verblevel(level); + if (level > 1) { + set_echo(true); + } + if (logfile_name) + set_logfile(logfile_name); + add_quit_helper(queue_quit); + bool ok = true; + ok = ok && run_console(infile_name); + ok = ok && finish_cmd(); + return ok ? 0 : 1; +} diff --git a/queue.c b/queue.c new file mode 100644 index 000000000..e494dcf22 --- /dev/null +++ b/queue.c @@ -0,0 +1,111 @@ +/* + * Code for basic C skills diagnostic. + * Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017 + * Modified to store strings, 2018 + */ + +/* + * This program implements a queue supporting both FIFO and LIFO + * operations. + * + * It uses a singly-linked list to represent the set of queue elements + */ + +#include +#include +#include + +#include "harness.h" +#include "queue.h" + +/* + Create empty queue. + Return NULL if could not allocate space. +*/ +queue_t *q_new() +{ + queue_t *q = malloc(sizeof(queue_t)); + /* What if malloc returned NULL? */ + q->head = NULL; + return q; +} + +/* Free all storage used by queue */ +void q_free(queue_t *q) +{ + /* How about freeing the list elements and the strings? */ + /* Free queue structure */ + free(q); +} + +/* + Attempt to insert element at head of queue. + Return true if successful. + Return false if q is NULL or could not allocate space. + 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) +{ + list_ele_t *newh; + /* 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; +} + + +/* + Attempt to insert element at tail of queue. + Return true if successful. + Return false if q is NULL or could not allocate space. + 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) +{ + /* You need to write the complete code for this function */ + /* Remember: It should operate in O(1) time */ + return false; +} + +/* + Attempt to remove element from head of queue. + Return true if successful. + Return false 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. +*/ +bool q_remove_head(queue_t *q, char *sp, size_t bufsize) +{ + /* You need to fix up this code. */ + q->head = q->head->next; + return true; +} + +/* + Return number of elements in queue. + Return 0 if q is NULL or empty + */ +int q_size(queue_t *q) +{ + /* You need to write the code for this function */ + /* Remember: It should operate in O(1) time */ + return 0; +} + +/* + Reverse elements in queue + No effect if q is NULL or empty + This function should not allocate or free any list elements + (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) +{ + /* You need to write the code for this function */ +} diff --git a/queue.h b/queue.h new file mode 100644 index 000000000..88b082baf --- /dev/null +++ b/queue.h @@ -0,0 +1,90 @@ +/* + * Code for basic C skills diagnostic. + * Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017 + * Extended to store strings, 2018 + */ + +/* + * This program implements a queue supporting both FIFO and LIFO + * operations. + * + * It uses a singly-linked list to represent the set of queue elements + */ + +#include + +/************** Data structure declarations ****************/ + +/* Linked list element (You shouldn't need to change this) */ +typedef struct ELE { + /* 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 */ + /* + You will need to add more fields to this structure + to efficiently implement q_size and q_insert_tail + */ +} queue_t; + +/************** Operations on queue ************************/ + +/* + Create empty queue. + Return NULL if could not allocate space. +*/ +queue_t *q_new(); + +/* + Free ALL storage used by queue. + No effect if q is NULL +*/ +void q_free(queue_t *q); + +/* + Attempt to insert element at head of queue. + Return true if successful. + Return false if q is NULL or could not allocate space. + 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); + +/* + Attempt to insert element at tail of queue. + Return true if successful. + Return false if q is NULL or could not allocate space. + 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); + +/* + Attempt to remove element from head of queue. + Return true if successful. + Return false 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. +*/ +bool q_remove_head(queue_t *q, char *sp, size_t bufsize); + +/* + Return number of elements in queue. + Return 0 if q is NULL or empty + */ +int q_size(queue_t *q); + +/* + Reverse elements in queue + No effect if q is NULL or empty + This function should not allocate or free any list elements + (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); diff --git a/report.c b/report.c new file mode 100644 index 000000000..fcb353124 --- /dev/null +++ b/report.c @@ -0,0 +1,380 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "report.h" + +#define MAX(a, b) ((a) < (b) ? (b) : (a)) + +FILE *errfile = NULL; +FILE *verbfile = NULL; +FILE *logfile = NULL; + +int verblevel = 0; +void init_files(FILE *efile, FILE *vfile) +{ + errfile = efile; + verbfile = vfile; +} + +static char fail_buf[1024] = "FATAL Error. Exiting\n"; + +volatile int rval = 0; + +/* Default fatal function */ +void default_fatal_fun() +{ + /* + sprintf(fail_buf, "FATAL. Memory: allocated = %.3f GB, resident = %.3f + GB\n", + gigabytes(current_bytes), gigabytes(resident_bytes())); + */ + rval = write(STDOUT_FILENO, fail_buf, strlen(fail_buf) + 1); + if (logfile) + fputs(fail_buf, logfile); +} + +/* Optional function to call when fatal error encountered */ +void (*fatal_fun)() = default_fatal_fun; + +void set_verblevel(int level) +{ + verblevel = level; +} + +bool set_logfile(char *file_name) +{ + logfile = fopen(file_name, "w"); + return logfile != NULL; +} + +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"; + int level = msg == MSG_WARN ? 2 : msg == MSG_ERROR ? 1 : 0; + if (verblevel < level) + return; + if (!errfile) + init_files(stdout, stdout); + va_start(ap, fmt); + fprintf(errfile, "%s: ", msg_name); + vfprintf(errfile, fmt, ap); + fprintf(errfile, "\n"); + fflush(errfile); + va_end(ap); + if (logfile) { + va_start(ap, fmt); + fprintf(logfile, "Error: "); + vfprintf(logfile, fmt, ap); + fprintf(logfile, "\n"); + fflush(logfile); + va_end(ap); + fclose(logfile); + } + if (fatal) { + if (fatal_fun) + fatal_fun(); + exit(1); + } +} + + +void report(int level, char *fmt, ...) +{ + va_list ap; + if (!verbfile) + init_files(stdout, stdout); + if (level <= verblevel) { + va_start(ap, fmt); + vfprintf(verbfile, fmt, ap); + fprintf(verbfile, "\n"); + fflush(verbfile); + va_end(ap); + if (logfile) { + va_start(ap, fmt); + vfprintf(logfile, fmt, ap); + fprintf(logfile, "\n"); + fflush(logfile); + va_end(ap); + } + } +} + +void report_noreturn(int level, char *fmt, ...) +{ + va_list ap; + if (!verbfile) + init_files(stdout, stdout); + if (level <= verblevel) { + va_start(ap, fmt); + vfprintf(verbfile, fmt, ap); + fflush(verbfile); + va_end(ap); + if (logfile) { + va_start(ap, fmt); + vfprintf(logfile, fmt, ap); + fflush(logfile); + va_end(ap); + } + } +} + +void safe_report(int level, char *msg) +{ + if (level > verblevel) + return; + if (!errfile) + init_files(stdout, stdout); + fputs(msg, errfile); + if (logfile) { + fputs(msg, logfile); + } +} + + + +/* Functions denoting failures */ + +/* General failure */ + +/* Need to be able to print without using malloc */ +void fail_fun(char *format, char *msg) +{ + sprintf(fail_buf, format, msg); + /* Tack on return */ + fail_buf[strlen(fail_buf)] = '\n'; + /* Use write to avoid any buffering issues */ + rval = write(STDOUT_FILENO, fail_buf, strlen(fail_buf) + 1); + if (logfile) { + /* Don't know file descriptor for logfile */ + fputs(fail_buf, logfile); + } + if (fatal_fun) + fatal_fun(); + if (logfile) + fclose(logfile); + exit(1); +} + +/* Maximum number of megabytes that application can use (0 = unlimited) */ +int mblimit = 0; +/* Maximum number of seconds that application can use. (0 = unlimited) */ +int timelimit = 0; + +/* Keeping track of memory allocation */ +static size_t allocate_cnt = 0; +static size_t allocate_bytes = 0; +static size_t free_cnt = 0; +static size_t free_bytes = 0; +/* These are externally visible */ +size_t peak_bytes = 0; +size_t last_peak_bytes = 0; +size_t current_bytes = 0; + +static void check_exceed(size_t new_bytes) +{ + size_t limit_bytes = (size_t) mblimit << 20; + size_t request_bytes = new_bytes + current_bytes; + if (mblimit > 0 && request_bytes > limit_bytes) { + report_event(MSG_FATAL, + "Exceeded memory limit of %u megabytes with %lu bytes", + mblimit, request_bytes); + } +} + +/* Call malloc & exit if fails */ +void *malloc_or_fail(size_t bytes, char *fun_name) +{ + check_exceed(bytes); + void *p = malloc(bytes); + if (!p) { + fail_fun("Malloc returned NULL in %s", fun_name); + return NULL; + } + allocate_cnt++; + allocate_bytes += bytes; + current_bytes += bytes; + peak_bytes = MAX(peak_bytes, current_bytes); + last_peak_bytes = MAX(last_peak_bytes, current_bytes); + return p; +} + +/* Call calloc returns NULL & exit if fails */ +void *calloc_or_fail(size_t cnt, size_t bytes, char *fun_name) +{ + check_exceed(cnt * bytes); + void *p = calloc(cnt, bytes); + if (!p) { + fail_fun("Calloc returned NULL in %s", fun_name); + return NULL; + } + allocate_cnt++; + allocate_bytes += cnt * bytes; + current_bytes += cnt * bytes; + peak_bytes = MAX(peak_bytes, current_bytes); + last_peak_bytes = MAX(last_peak_bytes, current_bytes); + + return p; +} + +/* Call realloc returns NULL & exit if fails. + Require explicit indication of current allocation */ +void *realloc_or_fail(void *old, + size_t old_bytes, + size_t new_bytes, + char *fun_name) +{ + if (new_bytes > old_bytes) + check_exceed(new_bytes - old_bytes); + void *p = realloc(old, new_bytes); + if (!p) { + fail_fun("Realloc returned NULL in %s", fun_name); + return NULL; + } + allocate_cnt++; + allocate_bytes += new_bytes; + current_bytes += (new_bytes - old_bytes); + peak_bytes = MAX(peak_bytes, current_bytes); + last_peak_bytes = MAX(last_peak_bytes, current_bytes); + free_cnt++; + free_bytes += old_bytes; + return p; +} + +char *strsave_or_fail(char *s, char *fun_name) +{ + if (!s) + return NULL; + size_t len = strlen(s); + check_exceed(len + 1); + char *ss = malloc(len + 1); + if (!ss) { + fail_fun("strsave failed in %s", fun_name); + } + allocate_cnt++; + allocate_bytes += len + 1; + current_bytes += len + 1; + peak_bytes = MAX(peak_bytes, current_bytes); + last_peak_bytes = MAX(last_peak_bytes, current_bytes); + + return strcpy(ss, s); +} + +/* Free block, as from malloc, realloc, or strsave */ +void free_block(void *b, size_t bytes) +{ + if (b == NULL) { + report_event(MSG_ERROR, "Attempting to free null block"); + } + free(b); + free_cnt++; + free_bytes += bytes; + current_bytes -= bytes; +} + +/* Free array, as from calloc */ +void free_array(void *b, size_t cnt, size_t bytes) +{ + if (b == NULL) { + report_event(MSG_ERROR, "Attempting to free null block"); + } + free(b); + free_cnt++; + free_bytes += cnt * bytes; + current_bytes -= cnt * bytes; +} + +/* Free string saved by strsave_or_fail */ +void free_string(char *s) +{ + if (s == NULL) { + report_event(MSG_ERROR, "Attempting to free null block"); + } + free_block((void *) s, strlen(s) + 1); +} + + +/* Report current allocation status */ +void mem_status(FILE *fp) +{ + fprintf(fp, + "Allocated cnt/bytes: %lu/%lu. Freed cnt/bytes: %lu/%lu.\n" + " Peak bytes %lu, Last peak bytes %ld, Current bytes %ld\n", + (long unsigned) allocate_cnt, (long unsigned) allocate_bytes, + (long unsigned) free_cnt, (long unsigned) free_bytes, + (long unsigned) peak_bytes, (long unsigned) last_peak_bytes, + (long unsigned) current_bytes); +} + +/* Initialization of timers */ +void init_time(double *timep) +{ + (void) delta_time(timep); +} + +double delta_time(double *timep) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + double current_time = tv.tv_sec + 1.0E-6 * tv.tv_usec; + double delta = current_time - *timep; + *timep = current_time; + return delta; +} + +/* Number of bytes resident in physical memory */ +size_t resident_bytes() +{ + struct rusage r; + size_t mem = 0; + int code = getrusage(RUSAGE_SELF, &r); + if (code < 0) { + report_event(MSG_ERROR, "Call to getrusage failed"); + } else { + mem = r.ru_maxrss * 1024; + } + return mem; +} + +double gigabytes(size_t n) +{ + return (double) n / (1UL << 30); +} + +void reset_peak_bytes() +{ + last_peak_bytes = current_bytes; +} + +#if 0 +static char timeout_buf[256]; + +void sigalrmhandler(int sig) { + safe_report(true, timeout_buf); +} + + +void change_timeout(int oldval) { + sprintf(timeout_buf, "Program timed out after %d seconds\n", timelimit); + /* alarm function will correctly cancel existing alarms */ + signal(SIGALRM, sigalrmhandler); + alarm(timelimit); +} + +/* Handler for SIGTERM signals */ +void sigterm_handler(int sig) { + safe_report(true, "SIGTERM signal received"); +} +#endif diff --git a/report.h b/report.h new file mode 100644 index 000000000..3f26fbe64 --- /dev/null +++ b/report.h @@ -0,0 +1,112 @@ +#include +#include + +/* Default reporting level. Must recompile when change */ +#ifndef RPT +#define RPT 2 +#endif + +/* Ways to report interesting behavior and errors */ + +/* Things to report */ +typedef enum { MSG_WARN, MSG_ERROR, MSG_FATAL } message_t; + +/* Buffer sizes */ +#define MAX_CHAR 512 + +void init_files(FILE *errfile, FILE *verbfile); + +bool set_logfile(char *file_name); + +extern int verblevel; +void set_verblevel(int level); + +/* Maximum number of megabytes that application can use (0 = unlimited) */ +extern int mblimit; + +/* Maximum number of seconds that application can use. (0 = unlimited) */ +extern int timelimit; + +/* Optional function to call when fatal error encountered */ +extern void (*fatal_fun)(); + +/* Error messages */ +void report_event(message_t msg, char *fmt, ...); + +/* Report useful information */ +void report(int verblevel, char *fmt, ...); + +/* Like report, but without return character */ +void report_noreturn(int verblevel, char *fmt, ...); + +/* Simple failure report. Works even when malloc returns NULL */ +void fail_fun(char *format, char *msg); + +/* Signal safe reporting function */ +void safe_report(int verblevel, char *msg); + +/* Attempt to call malloc. Fail when returns NULL */ +void *malloc_or_fail(size_t bytes, char *fun_name); + +/* Attempt to call calloc. Fail when returns NULL */ +void *calloc_or_fail(size_t cnt, size_t bytes, char *fun_name); + +/* Attempt to call realloc. Fail when returns NULL */ +void *realloc_or_fail(void *old, + size_t old_bytes, + size_t new_bytes, + char *fun_name); + +/* Attempt to save string. Fail when malloc returns NULL */ +char *strsave_or_fail(char *s, char *fun_name); + +/* Free block, as from malloc, realloc, or strsave */ +void free_block(void *b, size_t len); + +/* Free array, as from calloc */ +void free_array(void *b, size_t cnt, size_t bytes); + +/* Free string saved by strsave_or_fail */ +void free_string(char *s); + +/* Report current allocation status */ +void mem_status(FILE *fp); + +/** Time measurement. **/ + +/* Time counted as fp number in seconds */ +void init_time(double *timep); + +/* Compute time since last call with this timer + and reset timer */ +double delta_time(double *timep); + +/** Memory usage **/ + +/* Number of bytes resident in physical memory */ +size_t resident_bytes(); + +/* Convert bytes to gigabytes */ +double gigabytes(size_t bytes); + +/** Counters giving peak memory usage **/ + +/* Never resets */ +size_t peak_bytes; + +/* Resettable */ +size_t last_peak_bytes; + +/* Instantaneous */ +size_t current_bytes; + + +/* Reset last_peak_bytes */ +void reset_peak_bytes(); + + +/* Change value of timeout */ +void change_timeout(int oldval); + +/* Handler for SIGTERM signals */ +void sigterm_handler(int sig); diff --git a/traces/trace-01-ops.cmd b/traces/trace-01-ops.cmd new file mode 100644 index 000000000..4c38ea79d --- /dev/null +++ b/traces/trace-01-ops.cmd @@ -0,0 +1,11 @@ +# Test of insert_head and remove_head +option fail 0 +option malloc 0 +new +ih gerbil +ih bear +ih dolphin +rh dolphin +rh bear +rh gerbil + diff --git a/traces/trace-02-ops.cmd b/traces/trace-02-ops.cmd new file mode 100644 index 000000000..41ec44e7b --- /dev/null +++ b/traces/trace-02-ops.cmd @@ -0,0 +1,16 @@ +# Test of insert_head, insert_tail, and remove_head +option fail 0 +option malloc 0 +new +ih gerbil +ih bear +ih dolphin +it meerkat +it bear +it gerbil +rh dolphin +rh bear +rh gerbil +rh meerkat +rh bear +rh gerbil diff --git a/traces/trace-03-ops.cmd b/traces/trace-03-ops.cmd new file mode 100644 index 000000000..0a89aa4ab --- /dev/null +++ b/traces/trace-03-ops.cmd @@ -0,0 +1,24 @@ +# Test of insert_head, insert_tail, reverse, and remove_head +option fail 0 +option malloc 0 +new +ih dolphin +ih bear +ih gerbil +reverse +it meerkat +it bear +it gerbil +reverse +it squirrel +reverse +rh squirrel +ih vulture +reverse +rh gerbil +rh bear +rh meerkat +rh gerbil +rh bear +rh dolphin +rh vulture diff --git a/traces/trace-04-ops.cmd b/traces/trace-04-ops.cmd new file mode 100644 index 000000000..af8a166f3 --- /dev/null +++ b/traces/trace-04-ops.cmd @@ -0,0 +1,18 @@ +# Test of insert_head, insert_tail, and size +option fail 0 +option malloc 0 +new +ih gerbil +ih bear +ih dolphin +size +it meerkat +it bear +it gerbil +size +rh dolphin +rh +rh +rh +size + diff --git a/traces/trace-05-ops.cmd b/traces/trace-05-ops.cmd new file mode 100644 index 000000000..3084aa3d1 --- /dev/null +++ b/traces/trace-05-ops.cmd @@ -0,0 +1,23 @@ +# Test of insert_head, insert_tail, remove_head reverse, and size +option fail 0 +option malloc 0 +new +ih dolphin +ih bear +ih gerbil +reverse +size +it meerkat +it bear +it gerbil +size +rh dolphin +reverse +size +rh gerbil +rh bear +rh meerkat +rh gerbil +rh bear +size +free diff --git a/traces/trace-06-string.cmd b/traces/trace-06-string.cmd new file mode 100644 index 000000000..07c4e5cbe --- /dev/null +++ b/traces/trace-06-string.cmd @@ -0,0 +1,30 @@ +# Test of truncated strings +option fail 0 +option malloc 0 +new +ih aardvark_bear_dolphin_gerbil_jaguar 5 +it meerkat_panda_squirrel_vulture_wolf 5 +rh aardvark_bear_dolphin_gerbil_jaguar +reverse +rh meerkat_panda_squirrel_vulture_wolf +option length 30 +rh meerkat_panda_squirrel_vulture +reverse +option length 28 +rh aardvark_bear_dolphin_gerbil +option length 21 +rh aardvark_bear_dolphin +reverse +option length 22 +rh meerkat_panda_squirrel +option length 7 +rh meerkat +reverse +option length 8 +rh aardvark +option length 100 +rh aardvark_bear_dolphin_gerbil_jaguar +reverse +rh meerkat_panda_squirrel_vulture_wolf +free +quit \ No newline at end of file diff --git a/traces/trace-07-robust.cmd b/traces/trace-07-robust.cmd new file mode 100644 index 000000000..190383c4b --- /dev/null +++ b/traces/trace-07-robust.cmd @@ -0,0 +1,9 @@ +# Test operations on NULL queue +option fail 10 +option malloc 0 +free +ih bear +it dolphin +rh +reverse +size diff --git a/traces/trace-08-robust.cmd b/traces/trace-08-robust.cmd new file mode 100644 index 000000000..e506980fa --- /dev/null +++ b/traces/trace-08-robust.cmd @@ -0,0 +1,7 @@ +# Test operations on empty queue +option fail 10 +option malloc 0 +new +rh +reverse +size diff --git a/traces/trace-09-robust.cmd b/traces/trace-09-robust.cmd new file mode 100644 index 000000000..61f14559b --- /dev/null +++ b/traces/trace-09-robust.cmd @@ -0,0 +1,7 @@ +# Test remove_head with NULL argument +option fail 10 +option malloc 0 +new +ih bear +rhq + diff --git a/traces/trace-10-malloc.cmd b/traces/trace-10-malloc.cmd new file mode 100644 index 000000000..b24814fbb --- /dev/null +++ b/traces/trace-10-malloc.cmd @@ -0,0 +1,11 @@ +# Test of malloc failure on new +option fail 10 +option malloc 50 +new +new +new +new +new +new + + diff --git a/traces/trace-11-malloc.cmd b/traces/trace-11-malloc.cmd new file mode 100644 index 000000000..a95d91abf --- /dev/null +++ b/traces/trace-11-malloc.cmd @@ -0,0 +1,9 @@ +# Test of malloc failure on insert_head +option fail 30 +option malloc 0 +new +option malloc 25 +ih gerbil 20 + + + diff --git a/traces/trace-12-malloc.cmd b/traces/trace-12-malloc.cmd new file mode 100644 index 000000000..88c5bb00d --- /dev/null +++ b/traces/trace-12-malloc.cmd @@ -0,0 +1,10 @@ +# Test of malloc failure on insert_tail +option fail 50 +option malloc 0 +new +ih jaguar 20 +option malloc 25 +it gerbil 20 + + + diff --git a/traces/trace-13-perf.cmd b/traces/trace-13-perf.cmd new file mode 100644 index 000000000..abd926d37 --- /dev/null +++ b/traces/trace-13-perf.cmd @@ -0,0 +1,8 @@ +# 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 new file mode 100644 index 000000000..ce0fe458c --- /dev/null +++ b/traces/trace-14-perf.cmd @@ -0,0 +1,7 @@ +# Test performance of size +option fail 0 +option malloc 0 +new +ih dolphin 1000000 +size 1000 + diff --git a/traces/trace-15-perf.cmd b/traces/trace-15-perf.cmd new file mode 100644 index 000000000..2c04c4296 --- /dev/null +++ b/traces/trace-15-perf.cmd @@ -0,0 +1,11 @@ +# Test performance of insert_tail, size, and reverse +option fail 0 +option malloc 0 +new +ih dolphin 1000000 +it gerbil 1000000 +size 1000 +reverse +reverse +size 1000 + diff --git a/traces/trace-eg.cmd b/traces/trace-eg.cmd new file mode 100644 index 000000000..d72e16ced --- /dev/null +++ b/traces/trace-eg.cmd @@ -0,0 +1,21 @@ +# Demonstration of queue testing framework +# Use help command to see list of commands and options +# Initial queue is NULL. +show +# Create empty queue +new +# Fill it with some values. First at the head +ih dolphin +ih bear +ih gerbil +# Now at the tail +it meerkat +it bear +# Reverse it +reverse +# See how long it is +size +# Delete queue. Goes back to a NULL queue. +free +# Exit program +quit