blob: 19c6930d0c3e5d9e35a939fbd2bb6ca109c2a095 [file] [log] [blame]
// embed.cc -- Go frontend go:embed handling.
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "go-system.h"
#include "operator.h"
#include "go-diagnostics.h"
#include "lex.h"
#include "gogo.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
// Read a file into *DATA. Returns false on error.
static bool
read_file(const char* filename, Location loc, std::string* data)
{
int fd = open(filename, O_RDONLY | O_BINARY);
if (fd < 0)
{
go_error_at(loc, "%s: %m", filename);
return false;
}
struct stat st;
if (fstat(fd, &st) < 0)
{
go_error_at(loc, "%s: %m", filename);
return false;
}
off_t want = st.st_size;
// Most files read here are going to be incorporated into the object file
// and then the executable. Set a limit on the size we will accept.
if (want > 2000000000)
{
go_error_at(loc, "%s: file too large", filename);
return false;
}
data->resize(want);
off_t got = 0;
while (want > 0)
{
// C++11 requires that std::string use contiguous bytes, so this
// is safe.
ssize_t n = read(fd, &(*data)[got], want);
if (n < 0)
{
close(fd);
go_error_at(loc, "%s: %m", filename);
return false;
}
if (n == 0)
{
data->resize(got);
break;
}
got += n;
want -= n;
}
close(fd);
return true;
}
// A JSON value as read from an embedcfg file. For our purposes a
// JSON value is a string, or a list of strings, or a mapping from
// strings to values. We don't expect any numbers. We also don't
// expect an array of anything other than strings; that is, we don't
// accept an array of general JSON values.
class Json_value
{
public:
// The types of values.
enum Json_value_classification
{
JSON_VALUE_UNKNOWN,
JSON_VALUE_STRING,
JSON_VALUE_ARRAY,
JSON_VALUE_MAP
};
Json_value()
: classification_(JSON_VALUE_UNKNOWN), string_(), array_(), map_()
{ }
~Json_value();
Json_value_classification
classification() const
{ return this->classification_; }
// Set to a string value.
void
set_string(const std::string& str)
{
go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
this->classification_ = JSON_VALUE_STRING;
this->string_ = str;
}
// Start an array value.
void
start_array()
{
go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
this->classification_ = JSON_VALUE_ARRAY;
}
// Add an array entry.
void
add_array_entry(const std::string& s)
{
go_assert(this->classification_ == JSON_VALUE_ARRAY);
this->array_.push_back(s);
}
// Start a map value.
void
start_map()
{
go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
this->classification_ = JSON_VALUE_MAP;
}
// Add a map entry.
void
add_map_entry(const std::string& key, Json_value* val)
{
go_assert(this->classification_ == JSON_VALUE_MAP);
this->map_[key] = val;
}
// Return the strings from a string value.
const std::string&
to_string() const
{
go_assert(this->classification_ == JSON_VALUE_STRING);
return this->string_;
}
// Fetch a vector of strings, and drop them from the JSON value.
void
get_and_clear_array(std::vector<std::string>* v)
{
go_assert(this->classification_ == JSON_VALUE_ARRAY);
std::swap(*v, this->array_);
}
// Look up a map entry. Returns NULL if not found.
Json_value*
lookup_map_entry(const std::string& key);
// Iterate over a map.
typedef Unordered_map(std::string, Json_value*)::iterator map_iterator;
map_iterator
map_begin()
{
go_assert(this->classification_ == JSON_VALUE_MAP);
return this->map_.begin();
}
map_iterator
map_end()
{ return this->map_.end(); }
private:
// Classification.
Json_value_classification classification_;
// A string, for JSON_VALUE_STRING.
std::string string_;
// Array, for JSON_VALUE_ARRAY.
std::vector<std::string> array_;
// Mapping, for JSON_VALUE_MAP.
Unordered_map(std::string, Json_value*) map_;
};
// Delete a JSON value.
Json_value::~Json_value()
{
if (this->classification_ == JSON_VALUE_MAP)
{
for (map_iterator p = this->map_begin();
p != this->map_end();
++p)
delete p->second;
}
}
// Look up a map entry in a JSON value.
Json_value*
Json_value::lookup_map_entry(const std::string& key)
{
go_assert(this->classification_ == JSON_VALUE_MAP);
Unordered_map(std::string, Json_value*)::iterator p = this->map_.find(key);
if (p == this->map_.end())
return NULL;
return p->second;
}
// Manage reading the embedcfg file.
class Embedcfg_reader
{
public:
Embedcfg_reader(const char* filename)
: filename_(filename), data_(), p_(NULL), pend_(NULL)
{}
// Read the contents of FILENAME. Return whether it succeeded.
bool
initialize_from_file();
// Read a JSON object.
bool
read_object(Json_value*);
// Report an error if not at EOF.
void
check_eof();
// Report an error for the embedcfg file.
void
error(const char* msg);
private:
bool
read_value(Json_value*);
bool
read_array(Json_value*);
bool
read_string(std::string*);
bool
skip_whitespace(bool eof_ok);
// File name.
const char* filename_;
// File contents.
std::string data_;
// Next character to process.
const char *p_;
// End of data.
const char *pend_;
};
// Read the embedcfg file.
void
Gogo::read_embedcfg(const char *filename)
{
class Embedcfg_reader r(filename);
if (!r.initialize_from_file())
return;
Json_value val;
if (!r.read_object(&val))
return;
r.check_eof();
if (val.classification() != Json_value::JSON_VALUE_MAP)
{
r.error("invalid embedcfg: not a JSON object");
return;
}
Json_value* patterns = val.lookup_map_entry("Patterns");
if (patterns == NULL)
{
r.error("invalid embedcfg: missing Patterns");
return;
}
if (patterns->classification() != Json_value::JSON_VALUE_MAP)
{
r.error("invalid embedcfg: Patterns is not a JSON object");
return;
}
Json_value* files = val.lookup_map_entry("Files");
if (files == NULL)
{
r.error("invalid embedcfg: missing Files");
return;
}
if (files->classification() != Json_value::JSON_VALUE_MAP)
{
r.error("invalid embedcfg: Files is not a JSON object");
return;
}
// TODO: Actually do something with patterns and files.
}
// Read the contents of FILENAME into this->data_. Returns whether it
// succeeded.
bool
Embedcfg_reader::initialize_from_file()
{
if (!read_file(this->filename_, Linemap::unknown_location(), &this->data_))
return false;
if (this->data_.empty())
{
this->error("empty file");
return false;
}
this->p_ = this->data_.data();
this->pend_ = this->p_ + this->data_.size();
return true;
}
// Read a JSON object into VAL. Return whether it succeeded.
bool
Embedcfg_reader::read_object(Json_value* val)
{
if (!this->skip_whitespace(false))
return false;
if (*this->p_ != '{')
{
this->error("expected %<{%>");
return false;
}
++this->p_;
val->start_map();
if (!this->skip_whitespace(false))
return false;
if (*this->p_ == '}')
{
++this->p_;
return true;
}
while (true)
{
if (!this->skip_whitespace(false))
return false;
if (*this->p_ != '"')
{
this->error("expected %<\"%>");
return false;
}
std::string key;
if (!this->read_string(&key))
return false;
if (!this->skip_whitespace(false))
return false;
if (*this->p_ != ':')
{
this->error("expected %<:%>");
return false;
}
++this->p_;
Json_value* subval = new Json_value();
if (!this->read_value(subval))
return false;
val->add_map_entry(key, subval);
if (!this->skip_whitespace(false))
return false;
if (*this->p_ == '}')
{
++this->p_;
return true;
}
if (*this->p_ != ',')
{
this->error("expected %<,%> or %<}%>");
return false;
}
++this->p_;
}
}
// Read a JSON array into VAL. Return whether it succeeded.
bool
Embedcfg_reader::read_array(Json_value* val)
{
if (!this->skip_whitespace(false))
return false;
if (*this->p_ != '[')
{
this->error("expected %<[%>");
return false;
}
++this->p_;
val->start_array();
if (!this->skip_whitespace(false))
return false;
if (*this->p_ == ']')
{
++this->p_;
return true;
}
while (true)
{
// If we were parsing full JSON we would call read_value here,
// not read_string.
std::string s;
if (!this->read_string(&s))
return false;
val->add_array_entry(s);
if (!this->skip_whitespace(false))
return false;
if (*this->p_ == ']')
{
++this->p_;
return true;
}
if (*this->p_ != ',')
{
this->error("expected %<,%> or %<]%>");
return false;
}
++this->p_;
}
}
// Read a JSON value into VAL. Return whether it succeeded.
bool
Embedcfg_reader::read_value(Json_value* val)
{
if (!this->skip_whitespace(false))
return false;
switch (*this->p_)
{
case '"':
{
std::string s;
if (!this->read_string(&s))
return false;
val->set_string(s);
return true;
}
case '{':
return this->read_object(val);
case '[':
return this->read_array(val);
default:
this->error("invalid JSON syntax");
return false;
}
}
// Read a JSON string. Return whether it succeeded.
bool
Embedcfg_reader::read_string(std::string* str)
{
if (!this->skip_whitespace(false))
return false;
if (*this->p_ != '"')
{
this->error("expected %<\"%>");
return false;
}
++this->p_;
str->clear();
while (this->p_ < this->pend_ && *this->p_ != '"')
{
if (*this->p_ != '\\')
{
str->push_back(*this->p_);
++this->p_;
continue;
}
++this->p_;
if (this->p_ >= this->pend_)
{
this->error("unterminated string");
return false;
}
switch (*this->p_)
{
case '"': case '\\': case '/':
str->push_back(*this->p_);
++this->p_;
break;
case 'b':
str->push_back('\b');
++this->p_;
break;
case 'f':
str->push_back('\f');
++this->p_;
break;
case 'n':
str->push_back('\n');
++this->p_;
break;
case 'r':
str->push_back('\r');
++this->p_;
break;
case 't':
str->push_back('\t');
++this->p_;
break;
case 'u':
{
++this->p_;
unsigned int rune = 0;
for (int i = 0; i < 4; i++)
{
if (this->p_ >= this->pend_)
{
this->error("unterminated string");
return false;
}
unsigned char c = *this->p_;
++this->p_;
rune <<= 4;
if (c >= '0' && c <= '9')
rune += c - '0';
else if (c >= 'A' && c <= 'F')
rune += c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
rune += c - 'a' + 10;
else
{
this->error("invalid hex digit");
return false;
}
}
Lex::append_char(rune, false, str, Linemap::unknown_location());
}
break;
default:
this->error("unrecognized string escape");
return false;
}
}
if (*this->p_ == '"')
{
++this->p_;
return true;
}
this->error("unterminated string");
return false;
}
// Report an error if not at EOF.
void
Embedcfg_reader::check_eof()
{
if (this->skip_whitespace(true))
this->error("extraneous data at end of file");
}
// Skip whitespace. Return whether there is more to read.
bool
Embedcfg_reader::skip_whitespace(bool eof_ok)
{
while (this->p_ < this->pend_)
{
switch (*this->p_)
{
case ' ': case '\t': case '\n': case '\r':
++this->p_;
break;
default:
return true;
}
}
if (!eof_ok)
this->error("unexpected EOF");
return false;
}
// Report an error.
void
Embedcfg_reader::error(const char* msg)
{
if (!this->data_.empty() && this->p_ != NULL)
go_error_at(Linemap::unknown_location(),
"%<-fgo-embedcfg%>: %s: %lu: %s",
this->filename_,
static_cast<unsigned long>(this->p_ - this->data_.data()),
msg);
else
go_error_at(Linemap::unknown_location(),
"%<-fgo-embedcfg%>: %s: %s",
this->filename_, msg);
}