| // 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); |
| } |