From b0ccd3922f5ee31b682e7844d97e5180c33fa8ea Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 4 Jan 2021 18:37:08 -0800 Subject: [PATCH] compiler: add support for reading embedcfg files This is the code that parses an embedcfg file, which is a JSON file created by the go command when it sees go:embed directives. This code is not yet called, and does not yet do anything. It's being sent as a separate CL to isolate just the JSON parsing code. * Make-lang.in (GO_OBJS): Add go/embed.o. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281532 --- gcc/go/Make-lang.in | 1 + gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/embed.cc | 628 +++++++++++++++++++++++++++++++++++++ gcc/go/gofrontend/gogo.h | 4 + 4 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 gcc/go/gofrontend/embed.cc diff --git a/gcc/go/Make-lang.in b/gcc/go/Make-lang.in index 84dd6cc0f18..4bdc8f6ea5a 100644 --- a/gcc/go/Make-lang.in +++ b/gcc/go/Make-lang.in @@ -51,6 +51,7 @@ go-warn = $(STRICT_WARN) GO_OBJS = \ go/ast-dump.o \ + go/embed.o \ go/escape.o \ go/export.o \ go/expressions.o \ diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index 8cfc63248a7..82f43f5f21f 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -fd5396b1af389a55d1e3612702cfdad6755084e9 +22ce16e28220d446c4557f47129024e3561f3d77 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/embed.cc b/gcc/go/gofrontend/embed.cc new file mode 100644 index 00000000000..19c6930d0c3 --- /dev/null +++ b/gcc/go/gofrontend/embed.cc @@ -0,0 +1,628 @@ +// 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* 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 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(this->p_ - this->data_.data()), + msg); + else + go_error_at(Linemap::unknown_location(), + "%<-fgo-embedcfg%>: %s: %s", + this->filename_, msg); +} diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index bdb3166006d..0d80bdeda4f 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -393,6 +393,10 @@ class Gogo set_c_header(const std::string& s) { this->c_header_ = s; } + // Read an embedcfg file. + void + read_embedcfg(const char* filename); + // Return whether to check for division by zero in binary operations. bool check_divide_by_zero() const -- 2.30.2