Always check if next_word() returned an empty string (if it hit eof).

Thanks Rebecca Allard for running into a crash when a .mu file ends with
'{' (without a following newline).

Open question: how to express the constraint that next_word() should
always check if its result is empty? Can *any* type system do that?!
Even the usual constraint that we must use a result isn't iron-clad: you
could save the result in a variable but then ignore it. Unless you go to
Go's extraordinary lengths of considering any dead code an error.
This commit is contained in:
Kartik K. Agaram 2016-10-21 01:08:09 -07:00
parent 22d93b7671
commit 66abe7c1bd
8 changed files with 130 additions and 9 deletions

View File

@ -26,6 +26,10 @@ vector<recipe_ordinal> load(istream& in) {
skip_whitespace_and_comments(in);
if (!has_data(in)) break;
string command = next_word(in);
if (command.empty()) {
assert(!has_data(in));
break;
}
// Command Handlers
if (command == "recipe" || command == "def") {
recipe_ordinal r = slurp_recipe(in);
@ -50,6 +54,11 @@ vector<recipe_ordinal> load(istream& in) {
int slurp_recipe(istream& in) {
recipe result;
result.name = next_word(in);
if (result.name.empty()) {
assert(!has_data(in));
raise << "file ended with 'recipe'\n" << end();
return -1;
}
// End Load Recipe Name
skip_whitespace_but_not_newline(in);
// End Recipe Refinements
@ -89,7 +98,7 @@ bool next_instruction(istream& in, instruction* curr) {
curr->clear();
skip_whitespace_and_comments(in);
if (!has_data(in)) {
raise << "0: unbalanced '[' for recipe\n" << end();
raise << "incomplete recipe at end of file (0)\n" << end();
return false;
}
@ -97,10 +106,15 @@ bool next_instruction(istream& in, instruction* curr) {
while (has_data(in) && in.peek() != '\n') {
skip_whitespace_but_not_newline(in);
if (!has_data(in)) {
raise << "1: unbalanced '[' for recipe\n" << end();
raise << "incomplete recipe at end of file (1)\n" << end();
return false;
}
string word = next_word(in);
if (word.empty()) {
assert(!has_data(in));
raise << "incomplete recipe at end of file (2)\n" << end();
return false;
}
words.push_back(word);
skip_whitespace_but_not_newline(in);
}
@ -113,7 +127,7 @@ bool next_instruction(istream& in, instruction* curr) {
curr->label = words.at(0);
trace(9993, "parse") << "label: " << curr->label << end();
if (!has_data(in)) {
raise << "7: unbalanced '[' for recipe\n" << end();
raise << "incomplete recipe at end of file (3)\n" << end();
return false;
}
return true;
@ -149,6 +163,7 @@ bool next_instruction(istream& in, instruction* curr) {
return true;
}
// can return empty string -- only if `in` has no more data
string next_word(istream& in) {
skip_whitespace_but_not_newline(in);
// End next_word Special-cases

View File

@ -107,7 +107,13 @@ if (starts_with(s, "{")) {
return;
}
{
string_tree* type_names = new string_tree(next_word(in));
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete dilated reagent at end of file (0)\n" << end();
return;
}
string_tree* type_names = new string_tree(s);
// End Parsing Dilated Reagent Type Property(type_names)
type = new_type_tree(type_names);
delete type_names;
@ -116,7 +122,13 @@ if (starts_with(s, "{")) {
string key = slurp_key(in);
if (key.empty()) continue;
if (key == "}") continue;
string_tree* value = new string_tree(next_word(in));
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete dilated reagent at end of file (1)\n" << end();
return;
}
string_tree* value = new string_tree(s);
// End Parsing Dilated Reagent Property(value)
properties.push_back(pair<string, string_tree*>(key, value));
}
@ -126,6 +138,11 @@ if (starts_with(s, "{")) {
:(code)
string slurp_key(istream& in) {
string result = next_word(in);
if (result.empty()) {
assert(!has_data(in));
raise << "incomplete dilated reagent at end of file (2)\n" << end();
return result;
}
while (!result.empty() && *result.rbegin() == ':')
strip_last(result);
while (isspace(in.peek()) || in.peek() == ':')

View File

@ -46,7 +46,13 @@ string_tree* parse_string_tree(istream& in) {
return NULL;
}
if (in.peek() != '(') {
string_tree* result = new string_tree(next_word(in));
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete string tree at end of file (0)\n" << end();
return NULL;
}
string_tree* result = new string_tree(s);
return result;
}
in.get(); // skip '('
@ -57,10 +63,18 @@ string_tree* parse_string_tree(istream& in) {
assert(has_data(in));
if (in.peek() == ')') break;
*curr = new string_tree(NULL, NULL);
if (in.peek() == '(')
if (in.peek() == '(') {
(*curr)->left = parse_string_tree(in);
else
(*curr)->left = new string_tree(next_word(in));
}
else {
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete string tree at end of file (1)\n" << end();
return NULL;
}
(*curr)->left = new string_tree(s);
}
curr = &(*curr)->right;
}
in.get(); // skip ')'

View File

@ -721,6 +721,11 @@ Num_calls_to_transform_all_at_first_definition = -1;
void insert_container(const string& command, kind_of_type kind, istream& in) {
skip_whitespace_but_not_newline(in);
string name = next_word(in);
if (name.empty()) {
assert(!has_data(in));
raise << "incomplete container definition at end of file (0)\n" << end();
return;
}
// End container Name Refinements
trace(9991, "parse") << "--- defining " << command << ' ' << name << end();
if (!contains_key(Type_ordinal, name)
@ -744,6 +749,11 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
while (has_data(in)) {
skip_whitespace_and_comments(in);
string element = next_word(in);
if (element.empty()) {
assert(!has_data(in));
raise << "incomplete container definition at end of file (1)\n" << end();
return;
}
if (element == "]") break;
if (in.peek() != '\n') {
raise << command << " '" << name << "' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code.\n" << end();

View File

@ -78,6 +78,11 @@ else if (command == "pending-scenario") {
scenario parse_scenario(istream& in) {
scenario result;
result.name = next_word(in);
if (result.name.empty()) {
assert(!has_data(in));
raise << "incomplete scenario at end of file\n" << end();
return result;
}
skip_whitespace_and_comments(in);
if (in.peek() != '[') {
raise << "Expected '[' after scenario '" << result.name << "'\n" << end();
@ -329,6 +334,11 @@ void check_memory(const string& s) {
skip_whitespace_and_comments(in);
if (!has_data(in)) break;
string lhs = next_word(in);
if (lhs.empty()) {
assert(!has_data(in));
raise << "incomplete 'memory-should-contain' block at end of file (0)\n" << end();
return;
}
if (!is_integer(lhs)) {
check_type(lhs, in);
continue;
@ -338,6 +348,11 @@ void check_memory(const string& s) {
string _assign; in >> _assign; assert(_assign == "<-");
skip_whitespace_and_comments(in);
string rhs = next_word(in);
if (rhs.empty()) {
assert(!has_data(in));
raise << "incomplete 'memory-should-contain' block at end of file (1)\n" << end();
return;
}
if (!is_integer(rhs) && !is_noninteger(rhs)) {
if (Current_scenario && !Scenario_testing_scenario)
// genuine test in a mu file
@ -374,9 +389,19 @@ void check_type(const string& lhs, istream& in) {
x.set_value(to_integer(x.name));
skip_whitespace_and_comments(in);
string _assign = next_word(in);
if (_assign.empty()) {
assert(!has_data(in));
raise << "incomplete 'memory-should-contain' block at end of file (2)\n" << end();
return;
}
assert(_assign == "<-");
skip_whitespace_and_comments(in);
string literal = next_word(in);
if (literal.empty()) {
assert(!has_data(in));
raise << "incomplete 'memory-should-contain' block at end of file (3)\n" << end();
return;
}
int address = x.value;
// exclude quoting brackets
assert(*literal.begin() == '['); literal.erase(literal.begin());
@ -750,6 +775,11 @@ void run_mu_scenario(const string& form) {
in >> std::noskipws;
skip_whitespace_and_comments(in);
string _scenario = next_word(in);
if (_scenario.empty()) {
assert(!has_data(in));
raise << "no scenario in string passed into run_mu_scenario()\n" << end();
return;
}
assert(_scenario == "scenario");
scenario s = parse_scenario(in);
run_mu_scenario(s);

View File

@ -34,6 +34,11 @@ Fragments_used.clear();
:(before "End Command Handlers")
else if (command == "before") {
string label = next_word(in);
if (label.empty()) {
assert(!has_data(in));
raise << "incomplete 'before' block at end of file\n" << end();
return result;
}
recipe tmp;
slurp_body(in, tmp);
if (is_waypoint(label))
@ -44,6 +49,11 @@ else if (command == "before") {
}
else if (command == "after") {
string label = next_word(in);
if (label.empty()) {
assert(!has_data(in));
raise << "incomplete 'after' block at end of file\n" << end();
return result;
}
recipe tmp;
slurp_body(in, tmp);
if (is_waypoint(label))

View File

@ -33,6 +33,11 @@ void load_recipe_header(istream& in, recipe& result) {
result.has_header = true;
while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete recipe header at end of file (0)\n" << end();
return;
}
if (s == "<-")
raise << "recipe " << result.name << " should say '->' and not '<-'\n" << end();
if (s == "->") break;
@ -42,6 +47,11 @@ void load_recipe_header(istream& in, recipe& result) {
}
while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
string s = next_word(in);
if (s.empty()) {
assert(!has_data(in));
raise << "incomplete recipe header at end of file (1)\n" << end();
return;
}
result.products.push_back(reagent(s));
trace(9999, "parse") << "header product: " << result.products.back().original_string << end();
skip_whitespace_but_not_newline(in);

View File

@ -115,6 +115,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
skip_whitespace_and_comments(in);
if (!has_data(in)) break;
string filename = next_word(in);
if (filename.empty()) {
assert(!has_data(in));
raise << "incomplete 'resources' block at end of file (0)\n" << end();
return;
}
if (*filename.begin() != '[') {
raise << caller << ": assume-resources: filename '" << filename << "' must begin with a '['\n" << end();
break;
@ -130,6 +135,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
break;
}
string arrow = next_word(in);
if (arrow.empty()) {
assert(!has_data(in));
raise << "incomplete 'resources' block at end of file (1)\n" << end();
return;
}
if (arrow != "<-") {
raise << caller << ": assume-resources: expected '<-' after filename '" << filename << "' but got '" << arrow << "'\n" << end();
break;
@ -139,6 +149,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
break;
}
string contents = next_word(in);
if (contents.empty()) {
assert(!has_data(in));
raise << "incomplete 'resources' block at end of file (2)\n" << end();
return;
}
if (*contents.begin() != '[') {
raise << caller << ": assume-resources: file contents '" << contents << "' for filename '" << filename << "' must begin with a '['\n" << end();
break;