From 365d6e69e17e44f972934fd9582a01cc03754fe1 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch 
Date: Thu, 18 May 2017 20:45:46 +0200
Subject: Various mostly safety fixes
---
 README.adoc |  2 +-
 ell.c       | 58 ++++++++++++++++++++++++++++++++++------------------------
 2 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/README.adoc b/README.adoc
index 3ac6f15..376edfc 100644
--- a/README.adoc
+++ b/README.adoc
@@ -47,7 +47,7 @@ Runtime
 -------
 All variables are put in a single global namespace with no further scoping.
 When calling a command (which is a list of lists), all arguments are
-automatically stored in variables named 1, 2, 3, ... n.  They are however
+automatically stored in variables named 0, 1, 2, 3, ... n.  They are however
 effectively inaccessible and you must rename them first using the `arg` special
 form.
 
diff --git a/ell.c b/ell.c
index 9572711..7fd4c53 100755
--- a/ell.c
+++ b/ell.c
@@ -270,8 +270,10 @@ lexer_free (struct lexer *self) {
 	free (self->string.s);
 }
 
-// FIXME: other isspace() stuff is missing
-static bool lexer_is_word_char (int c) { return !strchr ("()[]{}\n@#' ", c); }
+static bool lexer_is_ignored (int c) { return c == ' ' || c == '\t'; }
+static bool lexer_is_word_char (int c) {
+	return !lexer_is_ignored (c) && !strchr ("()[]{}\n@#' ", c);
+}
 
 static int
 lexer_advance (struct lexer *self) {
@@ -286,11 +288,10 @@ lexer_advance (struct lexer *self) {
 	return c;
 }
 
-static void lexer_error (struct lexer *self, char **e, const char *fmt, ...)
+static bool lexer_error (struct lexer *self, char **e, const char *fmt, ...)
 	ATTRIBUTE_PRINTF (3, 4);
 
-// TODO: see "script", we can just use error constants to avoid allocation
-static void
+static bool
 lexer_error (struct lexer *self, char **e, const char *fmt, ...) {
 	va_list ap;
 	va_start (ap, fmt);
@@ -300,11 +301,12 @@ lexer_error (struct lexer *self, char **e, const char *fmt, ...) {
 	*e = format ("near line %u, column %u: %s",
 		self->line + 1, self->column + 1, description);
 
-	// TODO: see above, we should be able to indicate error without allocation
+	// TODO: see "script", we can just use error constants to avoid allocation
 	if (!*e)
 		abort ();
 
 	free (description);
+	return false;
 }
 
 static bool
@@ -333,10 +335,8 @@ lexer_hexa_escape (struct lexer *self, struct buffer *output) {
 
 static bool
 lexer_escape_sequence (struct lexer *self, struct buffer *output, char **e) {
-	if (!self->len) {
-		lexer_error (self, e, "premature end of escape sequence");
-		return false;
-	}
+	if (!self->len)
+		return lexer_error (self, e, "premature end of escape sequence");
 
 	unsigned char c = *self->p;
 	switch (c) {
@@ -356,12 +356,10 @@ lexer_escape_sequence (struct lexer *self, struct buffer *output, char **e) {
 		if (lexer_hexa_escape (self, output))
 			return true;
 
-		lexer_error (self, e, "invalid hexadecimal escape");
-		return false;
+		return lexer_error (self, e, "invalid hexadecimal escape");
 
 	default:
-		lexer_error (self, e, "unknown escape sequence");
-		return false;
+		return lexer_error (self, e, "unknown escape sequence");
 	}
 
 	buffer_append_c (output, c);
@@ -380,14 +378,13 @@ lexer_string (struct lexer *self, struct buffer *output, char **e) {
 		else if (!lexer_escape_sequence (self, output, e))
 			return false;
 	}
-	lexer_error (self, e, "premature end of string");
-	return false;
+	return lexer_error (self, e, "premature end of string");
 }
 
 static enum token
 lexer_next (struct lexer *self, char **e) {
 	// Skip over any whitespace between tokens
-	while (self->len && isspace (*self->p) && *self->p != '\n')
+	while (self->len && lexer_is_ignored (*self->p))
 		lexer_advance (self);
 	if (!self->len)
 		return T_ABORT;
@@ -428,6 +425,8 @@ lexer_next (struct lexer *self, char **e) {
 
 // --- Parsing -----------------------------------------------------------------
 
+// FIXME: the parser generally ignores memory allocation errors
+
 static void
 print_string (const char *s) {
 	putc ('\'', stdout);
@@ -675,6 +674,9 @@ register_native (const char *name, handler_fn handler) {
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
+// TODO: fill in "error_is_fatal"
+// TODO: probably add new_*() methods that set "memory_failure"
+
 struct context {
 	struct item *variables;             ///< List of variables
 
@@ -704,7 +706,6 @@ get (struct context *ctx, const char *name) {
 	return NULL;
 }
 
-// FIXME: cloning and removing
 static bool
 set (struct context *ctx, const char *name, struct item *value) {
 	struct item *iter, *key = NULL, *pair = NULL;
@@ -713,12 +714,19 @@ set (struct context *ctx, const char *name, struct item *value) {
 			break;
 	if (iter) {
 		item_free (iter->head->next);
-		iter->head->next = value;
+		if (!(iter->head->next = new_clone (value))) {
+			ctx->memory_failure = true;
+			return false;
+		}
 		return true;
 	}
 	if ((key = new_string (name, strlen (name)))
 	 && (pair = new_list (NULL))) {
-		(pair->head = key)->next = value;
+		if (!((pair->head = key)->next = new_clone (value))) {
+			item_free (pair);
+			ctx->memory_failure = true;
+			return false;
+		}
 		pair->next = ctx->variables;
 		ctx->variables = pair;
 		return true;
@@ -756,10 +764,12 @@ rename_arguments (struct context *ctx, struct item *names) {
 
 		if (names->type != ITEM_STRING)
 			continue;
-		if (value)
-			set (ctx, names->value, new_clone (value));
-		else
-			set (ctx, names->value, NULL);
+		if (value && !(value = new_clone (value))) {
+			ctx->memory_failure = true;
+			return false;
+		}
+		if (!set (ctx, names->value, value))
+			return false;
 	}
 	return true;
 }
-- 
cgit v1.2.3-70-g09d2