// import.cc -- Go frontend import declarations. // Copyright 2009 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 "filenames.h" #include "simple-object.h" #include "go-c.h" #include "gogo.h" #include "types.h" #include "export.h" #include "import.h" #ifndef O_BINARY #define O_BINARY 0 #endif // The list of paths we search for import files. static std::vector search_path; // Add a directory to the search path. This is called from the option // handling language hook. GO_EXTERN_C void go_add_search_path(const char* path) { search_path.push_back(std::string(path)); } // The name used for parameters, receivers, and results in imported // function types. const char* const Import::import_marker = "*imported*"; // Find import data. This searches the file system for FILENAME and // returns a pointer to a Stream object to read the data that it // exports. If the file is not found, it returns NULL. // When FILENAME is not an absolute path, we use the search path // provided by -I and -L options. // When FILENAME does not exist, we try modifying FILENAME to find the // file. We use the first of these which exists: // * We append ".gox". // * We turn the base of FILENAME into libFILENAME.so. // * We turn the base of FILENAME into libFILENAME.a. // * We append ".o". // When using a search path, we apply each of these transformations at // each entry on the search path before moving on to the next entry. // If the file exists, but does not contain any Go export data, we // stop; we do not keep looking for another file with the same name // later in the search path. Import::Stream* Import::open_package(const std::string& filename, source_location location) { if (!IS_ABSOLUTE_PATH(filename)) { for (std::vector::const_iterator p = search_path.begin(); p != search_path.end(); ++p) { std::string indir = *p; if (!indir.empty() && indir[indir.size() - 1] != '/') indir += '/'; indir += filename; Stream* s = Import::try_package_in_directory(indir, location); if (s != NULL) return s; } } Stream* s = Import::try_package_in_directory(filename, location); if (s != NULL) return s; return NULL; } // Try to find the export data for FILENAME. Import::Stream* Import::try_package_in_directory(const std::string& filename, source_location location) { std::string found_filename = filename; int fd = open(found_filename.c_str(), O_RDONLY | O_BINARY); if (fd >= 0) { struct stat s; if (fstat(fd, &s) >= 0 && S_ISDIR(s.st_mode)) { close(fd); fd = -1; errno = EISDIR; } } if (fd < 0) { if (errno != ENOENT && errno != EISDIR) warning_at(location, 0, "%s: %m", filename.c_str()); fd = Import::try_suffixes(&found_filename); if (fd < 0) return NULL; } // The export data may not be in this file. Stream* s = Import::find_export_data(found_filename, fd, location); if (s != NULL) return s; close(fd); error_at(location, "%s exists but does not contain any Go export data", found_filename.c_str()); return NULL; } // Given import "*PFILENAME", where *PFILENAME does not exist, try // various suffixes. If we find one, set *PFILENAME to the one we // found. Return the open file descriptor. int Import::try_suffixes(std::string* pfilename) { std::string filename = *pfilename + ".gox"; int fd = open(filename.c_str(), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } const char* basename = lbasename(pfilename->c_str()); size_t basename_pos = basename - pfilename->c_str(); filename = pfilename->substr(0, basename_pos) + "lib" + basename + ".so"; fd = open(filename.c_str(), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } filename = pfilename->substr(0, basename_pos) + "lib" + basename + ".a"; fd = open(filename.c_str(), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } filename = *pfilename + ".o"; fd = open(filename.c_str(), O_RDONLY | O_BINARY); if (fd >= 0) { *pfilename = filename; return fd; } return -1; } // Look for export data in the file descriptor FD. Import::Stream* Import::find_export_data(const std::string& filename, int fd, source_location location) { // See if we can read this as an object file. Import::Stream* stream = Import::find_object_export_data(filename, fd, 0, location); if (stream != NULL) return stream; const int len = MAX(Export::v1_magic_len, Import::archive_magic_len); if (lseek(fd, 0, SEEK_SET) < 0) { error_at(location, "lseek %s failed: %m", filename.c_str()); return NULL; } char buf[len]; ssize_t c = read(fd, buf, len); if (c < len) return NULL; // Check for a file containing nothing but Go export data. if (memcmp(buf, Export::v1_magic, Export::v1_magic_len) == 0) return new Stream_from_file(fd); // See if we can read this as an archive. if (Import::is_archive_magic(buf)) return Import::find_archive_export_data(filename, fd, location); return NULL; } // Look for export data in a simple_object. Import::Stream* Import::find_object_export_data(const std::string& filename, int fd, off_t offset, source_location location) { const char* errmsg; int err; simple_object_read* sobj = simple_object_start_read(fd, offset, "__GNU_GO", &errmsg, &err); if (sobj == NULL) return NULL; off_t sec_offset; off_t sec_length; int found = simple_object_find_section(sobj, ".go_export", &sec_offset, &sec_length, &errmsg, &err); simple_object_release_read(sobj); if (!found) return NULL; if (lseek(fd, offset + sec_offset, SEEK_SET) < 0) { error_at(location, "lseek %s failed: %m", filename.c_str()); return NULL; } char* buf = new char[sec_length]; ssize_t c = read(fd, buf, sec_length); if (c < 0) { error_at(location, "read %s failed: %m", filename.c_str()); delete[] buf; return NULL; } if (c < sec_length) { error_at(location, "%s: short read", filename.c_str()); delete[] buf; return NULL; } return new Stream_from_buffer(buf, sec_length); } // Class Import. // Construct an Import object. We make the builtin_types_ vector // large enough to hold all the builtin types. Import::Import(Stream* stream, source_location location) : gogo_(NULL), stream_(stream), location_(location), package_(NULL), add_to_globals_(false), builtin_types_((- SMALLEST_BUILTIN_CODE) + 1), types_() { } // Import the data in the associated stream. Package* Import::import(Gogo* gogo, const std::string& local_name, bool is_local_name_exported) { // Hold on to the Gogo structure. Otherwise we need to pass it // through all the import functions, because we need it when reading // a type. this->gogo_ = gogo; // A stream of export data can include data from more than one input // file. Here we loop over each input file. Stream* stream = this->stream_; while (!stream->at_eof() && !stream->saw_error()) { // The vector of types is package specific. this->types_.clear(); stream->require_bytes(this->location_, Export::v1_magic, Export::v1_magic_len); this->require_c_string("package "); std::string package_name = this->read_identifier(); this->require_c_string(";\n"); this->require_c_string("prefix "); std::string unique_prefix = this->read_identifier(); this->require_c_string(";\n"); this->package_ = gogo->add_imported_package(package_name, local_name, is_local_name_exported, unique_prefix, this->location_, &this->add_to_globals_); if (this->package_ == NULL) { stream->set_saw_error(); return NULL; } this->require_c_string("priority "); std::string priority_string = this->read_identifier(); int prio; if (!this->string_to_int(priority_string, false, &prio)) return NULL; this->package_->set_priority(prio); this->require_c_string(";\n"); if (stream->match_c_string("import ")) this->read_import_init_fns(gogo); // Loop over all the input data for this package. while (!stream->saw_error()) { if (stream->match_c_string("const ")) this->import_const(); else if (stream->match_c_string("type ")) this->import_type(); else if (stream->match_c_string("var ")) this->import_var(); else if (stream->match_c_string("func ")) this->import_func(this->package_); else if (stream->match_c_string("checksum ")) break; else { error_at(this->location_, ("error in import data at %d: " "expected %, %, %, " "%, or %"), stream->pos()); stream->set_saw_error(); return NULL; } } // We currently ignore the checksum. In the future we could // store the checksum somewhere in the generated object and then // verify that the checksum matches at link time or at dynamic // load time. this->require_c_string("checksum "); stream->advance(Export::v1_checksum_len * 2); this->require_c_string(";\n"); } return this->package_; } // Read the list of import control functions. void Import::read_import_init_fns(Gogo* gogo) { this->require_c_string("import"); while (!this->match_c_string(";")) { this->require_c_string(" "); std::string package_name = this->read_identifier(); this->require_c_string(" "); std::string init_name = this->read_identifier(); this->require_c_string(" "); std::string prio_string = this->read_identifier(); int prio; if (!this->string_to_int(prio_string, false, &prio)) return; gogo->add_import_init_fn(package_name, init_name, prio); } this->require_c_string(";\n"); } // Import a constant. void Import::import_const() { std::string name; Type* type; Expression* expr; Named_constant::import_const(this, &name, &type, &expr); Typed_identifier tid(name, type, this->location_); Named_object* no = this->package_->add_constant(tid, expr); if (this->add_to_globals_) this->gogo_->add_named_object(no); } // Import a type. void Import::import_type() { Named_type* type; Named_type::import_named_type(this, &type); // The named type has been added to the package by the type import // process. Here we need to make it visible to the parser, and it // to the global bindings if necessary. type->set_is_visible(); if (this->add_to_globals_) this->gogo_->add_named_type(type); } // Import a variable. void Import::import_var() { std::string name; Type* type; Variable::import_var(this, &name, &type); Variable* var = new Variable(type, NULL, true, false, false, this->location_); Named_object* no; no = this->package_->add_variable(name, var); if (this->add_to_globals_) this->gogo_->add_named_object(no); } // Import a function into PACKAGE. PACKAGE is normally // THIS->PACKAGE_, but it will be different for a method associated // with a type defined in a different package. Named_object* Import::import_func(Package* package) { std::string name; Typed_identifier* receiver; Typed_identifier_list* parameters; Typed_identifier_list* results; bool is_varargs; Function::import_func(this, &name, &receiver, ¶meters, &results, &is_varargs); Function_type *fntype = Type::make_function_type(receiver, parameters, results, this->location_); if (is_varargs) fntype->set_is_varargs(); source_location loc = this->location_; Named_object* no; if (fntype->is_method()) { Type* rtype = receiver->type()->deref(); if (rtype->is_error_type()) return NULL; Named_type* named_rtype = rtype->named_type(); gcc_assert(named_rtype != NULL); no = named_rtype->add_method_declaration(name, package, fntype, loc); } else { no = package->add_function_declaration(name, fntype, loc); if (this->add_to_globals_) this->gogo_->add_named_object(no); } return no; } // Read a type in the import stream. This records the type by the // type index. If the type is named, it registers the name, but marks // it as invisible. Type* Import::read_type() { Stream* stream = this->stream_; this->require_c_string("get_char(); if (c != '-' && (c < '0' || c > '9')) break; number += c; } int index; if (!this->string_to_int(number, true, &index)) return Type::make_error_type(); if (c == '>') { // This type was already defined. if (index < 0 ? (static_cast(- index) >= this->builtin_types_.size() || this->builtin_types_[- index] == NULL) : (static_cast(index) >= this->types_.size() || this->types_[index] == NULL)) { error_at(this->location_, "error in import data at %d: bad type index %d", stream->pos(), index); stream->set_saw_error(); return Type::make_error_type(); } return index < 0 ? this->builtin_types_[- index] : this->types_[index]; } if (c != ' ') { if (!stream->saw_error()) error_at(this->location_, "error in import data at %d: expect %< %> or %<>%>'", stream->pos()); stream->set_saw_error(); stream->advance(1); return Type::make_error_type(); } if (index <= 0 || (static_cast(index) < this->types_.size() && this->types_[index] != NULL)) { error_at(this->location_, "error in import data at %d: type index already defined", stream->pos()); stream->set_saw_error(); return Type::make_error_type(); } if (static_cast(index) >= this->types_.size()) { int newsize = std::max(static_cast(index) + 1, this->types_.size() * 2); this->types_.resize(newsize, NULL); } if (stream->peek_char() != '"') { Type* type = Type::import_type(this); this->require_c_string(">"); this->types_[index] = type; return type; } // This type has a name. stream->advance(1); std::string type_name; while ((c = stream->get_char()) != '"') type_name += c; // If this type is in the current package, the name will be // .PREFIX.PACKAGE.NAME or simply NAME with no dots. Otherwise, a // non-hidden symbol will be PREFIX.PACKAGE.NAME and a hidden symbol // will be .PREFIX.PACKAGE.NAME. std::string package_name; std::string unique_prefix; if (type_name.find('.') != std::string::npos) { bool is_hidden = false; size_t start = 0; if (type_name[0] == '.') { ++start; is_hidden = true; } size_t dot1 = type_name.find('.', start); size_t dot2; if (dot1 == std::string::npos) dot2 = std::string::npos; else dot2 = type_name.find('.', dot1 + 1); if (dot1 == std::string::npos || dot2 == std::string::npos) { error_at(this->location_, ("error at import data at %d: missing dot in type name"), stream->pos()); stream->set_saw_error(); } else { unique_prefix = type_name.substr(start, dot1 - start); package_name = type_name.substr(dot1 + 1, dot2 - (dot1 + 1)); } if (!is_hidden) type_name.erase(0, dot2 + 1); } this->require_c_string(" "); // Declare the type in the appropriate package. If we haven't seen // it before, mark it as invisible. We declare it before we read // the actual definition of the type, since the definition may refer // to the type itself. Package* package; if (package_name.empty()) package = this->package_; else package = this->gogo_->register_package(package_name, unique_prefix, UNKNOWN_LOCATION); Named_object* no = package->bindings()->lookup(type_name); if (no == NULL) no = package->add_type_declaration(type_name, this->location_); else if (!no->is_type_declaration() && !no->is_type()) { error_at(this->location_, "imported %<%s.%s%> both type and non-type", Gogo::message_name(package->name()).c_str(), Gogo::message_name(type_name).c_str()); stream->set_saw_error(); return Type::make_error_type(); } else gcc_assert(no->package() == package); if (this->types_[index] == NULL) { if (no->is_type_declaration()) { // FIXME: It's silly to make a forward declaration every time. this->types_[index] = Type::make_forward_declaration(no); } else { gcc_assert(no->is_type()); this->types_[index] = no->type_value(); } } // If there is no type definition, then this is just a forward // declaration of a type defined in some other file. Type* type; if (this->match_c_string(">")) type = this->types_[index]; else { type = this->read_type(); if (no->is_type_declaration()) { // We can define the type now. no = package->add_type(type_name, type, this->location_); Named_type* ntype = no->type_value(); // This type has not yet been imported. ntype->clear_is_visible(); type = ntype; } else if (no->is_type()) { // We have seen this type before. FIXME: it would be a good // idea to check that the two imported types are identical, // but we have not finalized the methds yet, which means // that we can nt reliably compare interface types. type = no->type_value(); // Don't change the visibility of the existing type. } this->types_[index] = type; // Read the type methods. if (this->match_c_string("\n")) { this->advance(1); while (this->match_c_string(" func")) { this->advance(1); this->import_func(package); } } } this->require_c_string(">"); return type; } // Register the builtin types. void Import::register_builtin_types(Gogo* gogo) { this->register_builtin_type(gogo, "int8", BUILTIN_INT8); this->register_builtin_type(gogo, "int16", BUILTIN_INT16); this->register_builtin_type(gogo, "int32", BUILTIN_INT32); this->register_builtin_type(gogo, "int64", BUILTIN_INT64); this->register_builtin_type(gogo, "uint8", BUILTIN_UINT8); this->register_builtin_type(gogo, "uint16", BUILTIN_UINT16); this->register_builtin_type(gogo, "uint32", BUILTIN_UINT32); this->register_builtin_type(gogo, "uint64", BUILTIN_UINT64); this->register_builtin_type(gogo, "float32", BUILTIN_FLOAT32); this->register_builtin_type(gogo, "float64", BUILTIN_FLOAT64); this->register_builtin_type(gogo, "complex64", BUILTIN_COMPLEX64); this->register_builtin_type(gogo, "complex128", BUILTIN_COMPLEX128); this->register_builtin_type(gogo, "int", BUILTIN_INT); this->register_builtin_type(gogo, "uint", BUILTIN_UINT); this->register_builtin_type(gogo, "uintptr", BUILTIN_UINTPTR); this->register_builtin_type(gogo, "bool", BUILTIN_BOOL); this->register_builtin_type(gogo, "string", BUILTIN_STRING); } // Register a single builtin type. void Import::register_builtin_type(Gogo* gogo, const char* name, Builtin_code code) { Named_object* named_object = gogo->lookup_global(name); gcc_assert(named_object != NULL && named_object->is_type()); int index = - static_cast(code); gcc_assert(index > 0 && static_cast(index) < this->builtin_types_.size()); this->builtin_types_[index] = named_object->type_value(); } // Read an identifier from the stream. std::string Import::read_identifier() { std::string ret; Stream* stream = this->stream_; int c; while (true) { c = stream->peek_char(); if (c == -1 || c == ' ' || c == ';') break; ret += c; stream->advance(1); } return ret; } // Turn a string into a integer with appropriate error handling. bool Import::string_to_int(const std::string &s, bool is_neg_ok, int* ret) { char* end; long prio = strtol(s.c_str(), &end, 10); if (*end != '\0' || prio > 0x7fffffff || (prio < 0 && !is_neg_ok)) { error_at(this->location_, "invalid integer in import data at %d", this->stream_->pos()); this->stream_->set_saw_error(); return false; } *ret = prio; return true; } // Class Import::Stream. Import::Stream::Stream() : pos_(0), saw_error_(false) { } Import::Stream::~Stream() { } // Return the next character to come from the stream. int Import::Stream::peek_char() { const char* read; if (!this->do_peek(1, &read)) return -1; // Make sure we return an unsigned char, so that we don't get // confused by \xff. unsigned char ret = *read; return ret; } // Return true if the next LENGTH characters from the stream match // BYTES bool Import::Stream::match_bytes(const char* bytes, size_t length) { const char* read; if (!this->do_peek(length, &read)) return false; return memcmp(bytes, read, length) == 0; } // Require that the next LENGTH bytes from the stream match BYTES. void Import::Stream::require_bytes(source_location location, const char* bytes, size_t length) { const char* read; if (!this->do_peek(length, &read) || memcmp(bytes, read, length) != 0) { if (!this->saw_error_) error_at(location, "import error at %d: expected %<%.*s%>", this->pos(), static_cast(length), bytes); this->saw_error_ = true; return; } this->advance(length); } // Class Stream_from_file. Stream_from_file::Stream_from_file(int fd) : fd_(fd), data_() { if (lseek(fd, 0, SEEK_SET) != 0) { error("lseek failed: %m"); this->set_saw_error(); } } Stream_from_file::~Stream_from_file() { close(this->fd_); } // Read next bytes. bool Stream_from_file::do_peek(size_t length, const char** bytes) { if (this->data_.length() <= length) { *bytes = this->data_.data(); return true; } // Don't bother to handle the general case, since we don't need it. gcc_assert(length < 64); char buf[64]; ssize_t got = read(this->fd_, buf, length); if (got < 0) { if (!this->saw_error()) error("read failed: %m"); this->set_saw_error(); return false; } if (lseek(this->fd_, - got, SEEK_CUR) != 0) { if (!this->saw_error()) error("lseek failed: %m"); this->set_saw_error(); return false; } if (static_cast(got) < length) return false; this->data_.assign(buf, got); *bytes = this->data_.data(); return true; } // Advance. void Stream_from_file::do_advance(size_t skip) { if (lseek(this->fd_, skip, SEEK_CUR) != 0) { if (!this->saw_error()) error("lseek failed: %m"); this->set_saw_error(); } if (!this->data_.empty()) { if (this->data_.length() < skip) this->data_.erase(0, skip); else this->data_.clear(); } }