diff options
Diffstat (limited to 'src/google/protobuf/compiler/objectivec/objectivec_helpers.cc')
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_helpers.cc | 882 |
1 files changed, 666 insertions, 216 deletions
diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc index 8527b74b..df71c8bb 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc @@ -28,9 +28,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifdef _MSC_VER -#include <io.h> -#else +#ifndef _MSC_VER #include <unistd.h> #endif #include <climits> @@ -44,12 +42,15 @@ #include <google/protobuf/stubs/hash.h> #include <google/protobuf/compiler/objectivec/objectivec_helpers.h> +#include <google/protobuf/descriptor.pb.h> #include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/io/printer.h> #include <google/protobuf/io/zero_copy_stream_impl.h> -#include <google/protobuf/descriptor.pb.h> #include <google/protobuf/stubs/common.h> +#include <google/protobuf/stubs/io_win32.h> #include <google/protobuf/stubs/strutil.h> + // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some // error cases, so it seems to be ok to use as a back door for errors. @@ -58,6 +59,24 @@ namespace protobuf { namespace compiler { namespace objectivec { +// <io.h> is transitively included in this file. Import the functions explicitly +// in this port namespace to avoid ambiguous definition. +namespace posix { +#ifdef _WIN32 +using ::google::protobuf::internal::win32::open; +#else +using ::open; +#endif +} // namespace port + +Options::Options() { + // Default is the value of the env for the package prefixes. + const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES"); + if (file_path) { + expected_prefixes_path = file_path; + } +} + namespace { hash_set<string> MakeWordsMap(const char* const words[], size_t num_words) { @@ -73,11 +92,15 @@ const char* const kUpperSegmentsList[] = {"url", "http", "https"}; hash_set<string> kUpperSegments = MakeWordsMap(kUpperSegmentsList, GOOGLE_ARRAYSIZE(kUpperSegmentsList)); +bool ascii_isnewline(char c) { + return c == '\n' || c == '\r'; +} + // Internal helper for name handing. // Do not expose this outside of helpers, stick to having functions for specific // cases (ClassName(), FieldName()), so there is always consistent suffix rules. string UnderscoresToCamelCase(const string& input, bool first_capitalized) { - vector<string> values; + std::vector<string> values; string current; bool last_char_was_number = false; @@ -116,9 +139,14 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) { } values.push_back(current); - for (vector<string>::iterator i = values.begin(); i != values.end(); ++i) { + string result; + bool first_segment_forces_upper = false; + for (std::vector<string>::iterator i = values.begin(); i != values.end(); ++i) { string value = *i; bool all_upper = (kUpperSegments.count(value) > 0); + if (all_upper && (result.length() == 0)) { + first_segment_forces_upper = true; + } for (int j = 0; j < value.length(); j++) { if (j == 0 || all_upper) { value[j] = ascii_toupper(value[j]); @@ -126,13 +154,11 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) { // Nothing, already in lower. } } - *i = value; - } - string result; - for (vector<string>::iterator i = values.begin(); i != values.end(); ++i) { - result += *i; + result += value; } - if ((result.length() != 0) && !first_capitalized) { + if ((result.length() != 0) && + !first_capitalized && + !first_segment_forces_upper) { result[0] = ascii_tolower(result[0]); } return result; @@ -180,7 +206,7 @@ const char* const kReservedWordList[] = { // method declared in protos. The main cases are methods // that take no arguments, or setFoo:/hasFoo: type methods. "clear", "data", "delimitedData", "descriptor", "extensionRegistry", - "extensionsCurrentlySet", "isInitialized", "serializedSize", + "extensionsCurrentlySet", "initialized", "isInitialized", "serializedSize", "sortedExtensionsInUse", "unknownFields", // MacTypes.h names @@ -194,10 +220,14 @@ const char* const kReservedWordList[] = { hash_set<string> kReservedWords = MakeWordsMap(kReservedWordList, GOOGLE_ARRAYSIZE(kReservedWordList)); -string SanitizeNameForObjC(const string& input, const string& extension) { +string SanitizeNameForObjC(const string& input, + const string& extension, + string* out_suffix_added) { if (kReservedWords.count(input) > 0) { + if (out_suffix_added) *out_suffix_added = extension; return input + extension; } + if (out_suffix_added) out_suffix_added->clear(); return input; } @@ -209,11 +239,6 @@ string NameFromFieldDescriptor(const FieldDescriptor* field) { } } -// Escape C++ trigraphs by escaping question marks to \? -string EscapeTrigraphs(const string& to_escape) { - return StringReplace(to_escape, "?", "\\?", true); -} - void PathSplit(const string& path, string* directory, string* basename) { string::size_type last_slash = path.rfind('/'); if (last_slash == string::npos) { @@ -251,8 +276,41 @@ bool IsSpecialName(const string& name, const string* special_names, return false; } +string GetZeroEnumNameForFlagType(const FlagType flag_type) { + switch(flag_type) { + case FLAGTYPE_DESCRIPTOR_INITIALIZATION: + return "GPBDescriptorInitializationFlag_None"; + case FLAGTYPE_EXTENSION: + return "GPBExtensionNone"; + case FLAGTYPE_FIELD: + return "GPBFieldNone"; + default: + GOOGLE_LOG(FATAL) << "Can't get here."; + return "0"; + } +} + +string GetEnumNameForFlagType(const FlagType flag_type) { + switch(flag_type) { + case FLAGTYPE_DESCRIPTOR_INITIALIZATION: + return "GPBDescriptorInitializationFlags"; + case FLAGTYPE_EXTENSION: + return "GPBExtensionOptions"; + case FLAGTYPE_FIELD: + return "GPBFieldFlags"; + default: + GOOGLE_LOG(FATAL) << "Can't get here."; + return string(); + } +} + } // namespace +// Escape C++ trigraphs by escaping question marks to \? +string EscapeTrigraphs(const string& to_escape) { + return StringReplace(to_escape, "?", "\\?", true); +} + string StripProto(const string& filename) { if (HasSuffixString(filename, ".protodevel")) { return StripSuffixString(filename, ".protodevel"); @@ -261,6 +319,16 @@ string StripProto(const string& filename) { } } +void StringPieceTrimWhitespace(StringPiece* input) { + while (!input->empty() && ascii_isspace(*input->data())) { + input->remove_prefix(1); + } + while (!input->empty() && ascii_isspace((*input)[input->length() - 1])) { + input->remove_suffix(1); + } +} + + bool IsRetainedName(const string& name) { // List of prefixes from // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html @@ -282,11 +350,10 @@ string BaseFileName(const FileDescriptor* file) { return basename; } -string FileName(const FileDescriptor* file) { - string path = FilePath(file); - string basename; - PathSplit(path, NULL, &basename); - return basename; +string FileClassPrefix(const FileDescriptor* file) { + // Default is empty string, no need to check has_objc_class_prefix. + string result = file->options().objc_class_prefix(); + return result; } string FilePath(const FileDescriptor* file) { @@ -306,10 +373,17 @@ string FilePath(const FileDescriptor* file) { return output; } -string FileClassPrefix(const FileDescriptor* file) { - // Default is empty string, no need to check has_objc_class_prefix. - string result = file->options().objc_class_prefix(); - return result; +string FilePathBasename(const FileDescriptor* file) { + string output; + string basename; + string directory; + PathSplit(file->name(), &directory, &basename); + basename = StripProto(basename); + + // CamelCase to be more ObjC friendly. + output = UnderscoresToCamelCase(basename, true); + + return output; } string FileClassName(const FileDescriptor* file) { @@ -318,7 +392,7 @@ string FileClassName(const FileDescriptor* file) { name += "Root"; // There aren't really any reserved words that end in "Root", but playing // it safe and checking. - return SanitizeNameForObjC(name, "_RootClass"); + return SanitizeNameForObjC(name, "_RootClass", NULL); } string ClassNameWorker(const Descriptor* descriptor) { @@ -340,11 +414,15 @@ string ClassNameWorker(const EnumDescriptor* descriptor) { } string ClassName(const Descriptor* descriptor) { + return ClassName(descriptor, NULL); +} + +string ClassName(const Descriptor* descriptor, string* out_suffix_added) { // 1. Message names are used as is (style calls for CamelCase, trust it). // 2. Check for reserved word at the very end and then suffix things. string prefix = FileClassPrefix(descriptor->file()); string name = ClassNameWorker(descriptor); - return SanitizeNameForObjC(prefix + name, "_Class"); + return SanitizeNameForObjC(prefix + name, "_Class", out_suffix_added); } string EnumName(const EnumDescriptor* descriptor) { @@ -358,7 +436,7 @@ string EnumName(const EnumDescriptor* descriptor) { // yields Fixed_Class, Fixed_Size. string name = FileClassPrefix(descriptor->file()); name += ClassNameWorker(descriptor); - return SanitizeNameForObjC(name, "_Enum"); + return SanitizeNameForObjC(name, "_Enum", NULL); } string EnumValueName(const EnumValueDescriptor* descriptor) { @@ -373,7 +451,7 @@ string EnumValueName(const EnumValueDescriptor* descriptor) { const string& name = class_name + "_" + value_str; // There aren't really any reserved words with an underscore and a leading // capital letter, but playing it safe and checking. - return SanitizeNameForObjC(name, "_Value"); + return SanitizeNameForObjC(name, "_Value", NULL); } string EnumValueShortName(const EnumValueDescriptor* descriptor) { @@ -410,7 +488,7 @@ string UnCamelCaseEnumShortName(const string& name) { string ExtensionMethodName(const FieldDescriptor* descriptor) { const string& name = NameFromFieldDescriptor(descriptor); const string& result = UnderscoresToCamelCase(name, false); - return SanitizeNameForObjC(result, "_Extension"); + return SanitizeNameForObjC(result, "_Extension", NULL); } string FieldName(const FieldDescriptor* field) { @@ -425,7 +503,7 @@ string FieldName(const FieldDescriptor* field) { result += "_p"; } } - return SanitizeNameForObjC(result, "_p"); + return SanitizeNameForObjC(result, "_p", NULL); } string FieldNameCapitalized(const FieldDescriptor* field) { @@ -723,7 +801,7 @@ string DefaultValue(const FieldDescriptor* field) { uint32 length = ghtonl(default_string.length()); string bytes((const char*)&length, sizeof(length)); bytes.append(default_string); - return "(NSData*)\"" + CEscape(bytes) + "\""; + return "(NSData*)\"" + EscapeTrigraphs(CEscape(bytes)) + "\""; } else { return "@\"" + EscapeTrigraphs(CEscape(default_string)) + "\""; } @@ -740,102 +818,155 @@ string DefaultValue(const FieldDescriptor* field) { return NULL; } -string BuildFlagsString(const vector<string>& strings) { +bool HasNonZeroDefaultValue(const FieldDescriptor* field) { + // Repeated fields don't have defaults. + if (field->is_repeated()) { + return false; + } + + // As much as checking field->has_default_value() seems useful, it isn't + // because of enums. proto2 syntax allows the first item in an enum (the + // default) to be non zero. So checking field->has_default_value() would + // result in missing this non zero default. See MessageWithOneBasedEnum in + // objectivec/Tests/unittest_objc.proto for a test Message to confirm this. + + // Some proto file set the default to the zero value, so make sure the value + // isn't the zero case. + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + return field->default_value_int32() != 0; + case FieldDescriptor::CPPTYPE_UINT32: + return field->default_value_uint32() != 0U; + case FieldDescriptor::CPPTYPE_INT64: + return field->default_value_int64() != 0LL; + case FieldDescriptor::CPPTYPE_UINT64: + return field->default_value_uint64() != 0ULL; + case FieldDescriptor::CPPTYPE_DOUBLE: + return field->default_value_double() != 0.0; + case FieldDescriptor::CPPTYPE_FLOAT: + return field->default_value_float() != 0.0f; + case FieldDescriptor::CPPTYPE_BOOL: + return field->default_value_bool(); + case FieldDescriptor::CPPTYPE_STRING: { + const string& default_string = field->default_value_string(); + return default_string.length() != 0; + } + case FieldDescriptor::CPPTYPE_ENUM: + return field->default_value_enum()->number() != 0; + case FieldDescriptor::CPPTYPE_MESSAGE: + return false; + } + + // Some compilers report reaching end of function even though all cases of + // the enum are handed in the switch. + GOOGLE_LOG(FATAL) << "Can't get here."; + return false; +} + +string BuildFlagsString(const FlagType flag_type, + const std::vector<string>& strings) { if (strings.size() == 0) { - return "0"; + return GetZeroEnumNameForFlagType(flag_type); + } else if (strings.size() == 1) { + return strings[0]; } - string string; + string string("(" + GetEnumNameForFlagType(flag_type) + ")("); for (size_t i = 0; i != strings.size(); ++i) { if (i > 0) { string.append(" | "); } string.append(strings[i]); } + string.append(")"); return string; } -string BuildCommentsString(const SourceLocation& location) { +string BuildCommentsString(const SourceLocation& location, + bool prefer_single_line) { const string& comments = location.leading_comments.empty() ? location.trailing_comments : location.leading_comments; - vector<string> lines; + std::vector<string> lines; SplitStringAllowEmpty(comments, "\n", &lines); while (!lines.empty() && lines.back().empty()) { lines.pop_back(); } - string prefix("//"); - string suffix("\n"); - string final_comments; - for (int i = 0; i < lines.size(); i++) { - // We use $ for delimiters, so replace comments with dollars with - // html escaped version. - // None of the other compilers handle this (as of this writing) but we - // ran into it once, so just to be safe. - final_comments += - prefix + StringReplace(lines[i], "$", "$", true) + suffix; + // If there are no comments, just return an empty string. + if (lines.size() == 0) { + return ""; } - return final_comments; -} -namespace { - -// Internal helper class that parses the expected package to prefix mappings -// file. -class Parser { - public: - Parser(map<string, string>* inout_package_to_prefix_map) - : prefix_map_(inout_package_to_prefix_map), line_(0) {} - - // Parses a check of input, returning success/failure. - bool ParseChunk(StringPiece chunk); - - // Should be called to finish parsing (after all input has been provided via - // ParseChunk()). Returns success/failure. - bool Finish(); - - int last_line() const { return line_; } - string error_str() const { return error_str_; } - - private: - bool ParseLoop(); + string prefix; + string suffix; + string final_comments; + string epilogue; - map<string, string>* prefix_map_; - int line_; - string error_str_; - StringPiece p_; - string leftover_; -}; + bool add_leading_space = false; -bool Parser::ParseChunk(StringPiece chunk) { - if (!leftover_.empty()) { - chunk.AppendToString(&leftover_); - p_ = StringPiece(leftover_); - } else { - p_ = chunk; - } - bool result = ParseLoop(); - if (p_.empty()) { - leftover_.clear(); + if (prefer_single_line && lines.size() == 1) { + prefix = "/** "; + suffix = " */\n"; } else { - leftover_ = p_.ToString(); + prefix = "* "; + suffix = "\n"; + final_comments += "/**\n"; + epilogue = " **/\n"; + add_leading_space = true; } + + for (int i = 0; i < lines.size(); i++) { + string line = StripPrefixString(lines[i], " "); + // HeaderDoc and appledoc use '\' and '@' for markers; escape them. + line = StringReplace(line, "\\", "\\\\", true); + line = StringReplace(line, "@", "\\@", true); + // Decouple / from * to not have inline comments inside comments. + line = StringReplace(line, "/*", "/\\*", true); + line = StringReplace(line, "*/", "*\\/", true); + line = prefix + line; + StripWhitespace(&line); + // If not a one line, need to add the first space before *, as + // StripWhitespace would have removed it. + line = (add_leading_space ? " " : "") + line; + final_comments += line + suffix; + } + final_comments += epilogue; + return final_comments; +} + +// Making these a generator option for folks that don't use CocoaPods, but do +// want to put the library in a framework is an interesting question. The +// problem is it means changing sources shipped with the library to actually +// use a different value; so it isn't as simple as a option. +const char* const ProtobufLibraryFrameworkName = "Protobuf"; + +string ProtobufFrameworkImportSymbol(const string& framework_name) { + // GPB_USE_[framework_name]_FRAMEWORK_IMPORTS + string result = string("GPB_USE_"); + result += ToUpper(framework_name); + result += "_FRAMEWORK_IMPORTS"; return result; } -bool Parser::Finish() { - if (leftover_.empty()) { +bool IsProtobufLibraryBundledProtoFile(const FileDescriptor* file) { + // We don't check the name prefix or proto package because some files + // (descriptor.proto), aren't shipped generated by the library, so this + // seems to be the safest way to only catch the ones shipped. + const string name = file->name(); + if (name == "google/protobuf/any.proto" || + name == "google/protobuf/api.proto" || + name == "google/protobuf/duration.proto" || + name == "google/protobuf/empty.proto" || + name == "google/protobuf/field_mask.proto" || + name == "google/protobuf/source_context.proto" || + name == "google/protobuf/struct.proto" || + name == "google/protobuf/timestamp.proto" || + name == "google/protobuf/type.proto" || + name == "google/protobuf/wrappers.proto") { return true; } - // Force a newline onto the end to finish parsing. - p_ = StringPiece(leftover_ + "\n"); - if (!ParseLoop()) { - return false; - } - return p_.empty(); // Everything used? + return false; } -static bool ascii_isnewline(char c) { return c == '\n' || c == '\r'; } - bool ReadLine(StringPiece* input, StringPiece* line) { for (int len = 0; len < input->size(); ++len) { if (ascii_isnewline((*input)[len])) { @@ -848,15 +979,6 @@ bool ReadLine(StringPiece* input, StringPiece* line) { return false; // Ran out of input with no newline. } -void TrimWhitespace(StringPiece* input) { - while (!input->empty() && ascii_isspace(*input->data())) { - input->remove_prefix(1); - } - while (!input->empty() && ascii_isspace((*input)[input->length() - 1])) { - input->remove_suffix(1); - } -} - void RemoveComment(StringPiece* input) { int offset = input->find('#'); if (offset != StringPiece::npos) { @@ -864,91 +986,64 @@ void RemoveComment(StringPiece* input) { } } -bool Parser::ParseLoop() { - StringPiece line; - while (ReadLine(&p_, &line)) { - ++line_; - RemoveComment(&line); - TrimWhitespace(&line); - if (line.size() == 0) { - continue; // Blank line. - } - int offset = line.find('='); - if (offset == StringPiece::npos) { - error_str_ = - string("Line without equal sign: '") + line.ToString() + "'."; - return false; - } - StringPiece package(line, 0, offset); - StringPiece prefix(line, offset + 1, line.length() - offset - 1); - TrimWhitespace(&package); - TrimWhitespace(&prefix); - // Don't really worry about error checking the package/prefix for - // being valid. Assume the file is validated when it is created/edited. - (*prefix_map_)[package.ToString()] = prefix.ToString(); +namespace { + +class ExpectedPrefixesCollector : public LineConsumer { + public: + ExpectedPrefixesCollector(std::map<string, string>* inout_package_to_prefix_map) + : prefix_map_(inout_package_to_prefix_map) {} + + virtual bool ConsumeLine(const StringPiece& line, string* out_error); + + private: + std::map<string, string>* prefix_map_; +}; + +bool ExpectedPrefixesCollector::ConsumeLine( + const StringPiece& line, string* out_error) { + int offset = line.find('='); + if (offset == StringPiece::npos) { + *out_error = + string("Expected prefixes file line without equal sign: '") + + line.ToString() + "'."; + return false; } + StringPiece package(line, 0, offset); + StringPiece prefix(line, offset + 1, line.length() - offset - 1); + StringPieceTrimWhitespace(&package); + StringPieceTrimWhitespace(&prefix); + // Don't really worry about error checking the package/prefix for + // being valid. Assume the file is validated when it is created/edited. + (*prefix_map_)[package.ToString()] = prefix.ToString(); return true; } -bool LoadExpectedPackagePrefixes(map<string, string>* prefix_map, - string* out_expect_file_path, +bool LoadExpectedPackagePrefixes(const Options &generation_options, + std::map<string, string>* prefix_map, string* out_error) { - const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES"); - if (file_path == NULL) { + if (generation_options.expected_prefixes_path.empty()) { return true; } - int fd; - do { - fd = open(file_path, O_RDONLY); - } while (fd < 0 && errno == EINTR); - if (fd < 0) { - *out_error = - string(file_path) + ":0:0: error: Unable to open." + strerror(errno); - return false; - } - io::FileInputStream file_stream(fd); - file_stream.SetCloseOnDelete(true); - *out_expect_file_path = file_path; - - Parser parser(prefix_map); - const void* buf; - int buf_len; - while (file_stream.Next(&buf, &buf_len)) { - if (buf_len == 0) { - continue; - } - - if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len))) { - *out_error = string(file_path) + ":" + SimpleItoa(parser.last_line()) + - ":0: error: " + parser.error_str(); - return false; - } - } - return parser.Finish(); + ExpectedPrefixesCollector collector(prefix_map); + return ParseSimpleFile( + generation_options.expected_prefixes_path, &collector, out_error); } -} // namespace - -bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) { +bool ValidateObjCClassPrefix( + const FileDescriptor* file, + const string& expected_prefixes_path, + const std::map<string, string>& expected_package_prefixes, + string* out_error) { const string prefix = file->options().objc_class_prefix(); const string package = file->package(); // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some // error cases, so it seems to be ok to use as a back door for warnings. - // Load any expected package prefixes to validate against those. - map<string, string> expected_package_prefixes; - string expect_file_path; - if (!LoadExpectedPackagePrefixes(&expected_package_prefixes, - &expect_file_path, out_error)) { - // Any error, clear the entries that were read. - expected_package_prefixes.clear(); - } - // Check: Error - See if there was an expected prefix for the package and // report if it doesn't match (wrong or missing). - map<string, string>::iterator package_match = + std::map<string, string>::const_iterator package_match = expected_package_prefixes.find(package); if (package_match != expected_package_prefixes.end()) { // There was an entry, and... @@ -957,8 +1052,9 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) { return true; } else { // ...it didn't match! - *out_error = "protoc:0: error: Expected 'option objc_class_prefix = \"" + - package_match->second + "\";' in '" + file->name() + "'"; + *out_error = "error: Expected 'option objc_class_prefix = \"" + + package_match->second + "\";' for package '" + package + + "' in '" + file->name() + "'"; if (prefix.length()) { *out_error += "; but found '" + prefix + "' instead"; } @@ -968,72 +1064,136 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) { } // If there was no prefix option, we're done at this point. - if (prefix.length() == 0) { + if (prefix.empty()) { // No prefix, nothing left to check. return true; } - // Check: Error - Make sure the prefix wasn't expected for a different - // package (overlap is allowed, but it has to be listed as an expected - // overlap). - for (map<string, string>::iterator i = expected_package_prefixes.begin(); - i != expected_package_prefixes.end(); ++i) { - if (i->second == prefix) { - *out_error = - "protoc:0: error: Found 'option objc_class_prefix = \"" + prefix + - "\";' in '" + file->name() + - "'; that prefix is already used for 'package " + i->first + - ";'. It can only be reused by listing it in the expected file (" + - expect_file_path + ")."; - return false; // Only report first usage of the prefix. - } - } - // Check: Warning - Make sure the prefix is is a reasonable value according // to Apple's rules (the checks above implicitly whitelist anything that // doesn't meet these rules). if (!ascii_isupper(prefix[0])) { - cerr << endl + std::cerr << std::endl << "protoc:0: warning: Invalid 'option objc_class_prefix = \"" << prefix << "\";' in '" << file->name() << "';" - << " it should start with a capital letter." << endl; - cerr.flush(); + << " it should start with a capital letter." << std::endl; + std::cerr.flush(); } if (prefix.length() < 3) { // Apple reserves 2 character prefixes for themselves. They do use some // 3 character prefixes, but they haven't updated the rules/docs. - cerr << endl + std::cerr << std::endl << "protoc:0: warning: Invalid 'option objc_class_prefix = \"" << prefix << "\";' in '" << file->name() << "';" << " Apple recommends they should be at least 3 characters long." - << endl; - cerr.flush(); + << std::endl; + std::cerr.flush(); + } + + // Look for any other package that uses the same prefix. + string other_package_for_prefix; + for (std::map<string, string>::const_iterator i = expected_package_prefixes.begin(); + i != expected_package_prefixes.end(); ++i) { + if (i->second == prefix) { + other_package_for_prefix = i->first; + break; + } + } + + // Check: Warning - If the file does not have a package, check whether + // the prefix declared is being used by another package or not. + if (package.empty()) { + // The file does not have a package and ... + if (other_package_for_prefix.empty()) { + // ... no other package has declared that prefix. + std::cerr << std::endl + << "protoc:0: warning: File '" << file->name() << "' has no " + << "package. Consider adding a new package to the proto and adding '" + << "new.package = " << prefix << "' to the expected prefixes file (" + << expected_prefixes_path << ")." << std::endl; + std::cerr.flush(); + } else { + // ... another package has declared the same prefix. + std::cerr << std::endl + << "protoc:0: warning: File '" << file->name() << "' has no package " + << "and package '" << other_package_for_prefix << "' already uses '" + << prefix << "' as its prefix. Consider either adding a new package " + << "to the proto, or reusing one of the packages already using this " + << "prefix in the expected prefixes file (" + << expected_prefixes_path << ")." << std::endl; + std::cerr.flush(); + } + return true; + } + + // Check: Error - Make sure the prefix wasn't expected for a different + // package (overlap is allowed, but it has to be listed as an expected + // overlap). + if (!other_package_for_prefix.empty()) { + *out_error = + "error: Found 'option objc_class_prefix = \"" + prefix + + "\";' in '" + file->name() + + "'; that prefix is already used for 'package " + + other_package_for_prefix + ";'. It can only be reused by listing " + + "it in the expected file (" + + expected_prefixes_path + ")."; + return false; // Only report first usage of the prefix. } // Check: Warning - If the given package/prefix pair wasn't expected, issue a // warning issue a warning suggesting it gets added to the file. if (!expected_package_prefixes.empty()) { - cerr << endl + std::cerr << std::endl << "protoc:0: warning: Found unexpected 'option objc_class_prefix = \"" << prefix << "\";' in '" << file->name() << "';" << " consider adding it to the expected prefixes file (" - << expect_file_path << ")." << endl; - cerr.flush(); + << expected_prefixes_path << ")." << std::endl; + std::cerr.flush(); + } + + return true; +} + +} // namespace + +bool ValidateObjCClassPrefixes(const std::vector<const FileDescriptor*>& files, + const Options& generation_options, + string* out_error) { + // Load the expected package prefixes, if available, to validate against. + std::map<string, string> expected_package_prefixes; + if (!LoadExpectedPackagePrefixes(generation_options, + &expected_package_prefixes, + out_error)) { + return false; } + for (int i = 0; i < files.size(); i++) { + bool is_valid = + ValidateObjCClassPrefix(files[i], + generation_options.expected_prefixes_path, + expected_package_prefixes, + out_error); + if (!is_valid) { + return false; + } + } return true; } +TextFormatDecodeData::TextFormatDecodeData() { } + +TextFormatDecodeData::~TextFormatDecodeData() { } + void TextFormatDecodeData::AddString(int32 key, const string& input_for_decode, const string& desired_output) { - for (vector<DataEntry>::const_iterator i = entries_.begin(); + for (std::vector<DataEntry>::const_iterator i = entries_.begin(); i != entries_.end(); ++i) { if (i->first == key) { - cerr << "error: duplicate key (" << key + std::cerr << "error: duplicate key (" << key << ") making TextFormat data, input: \"" << input_for_decode - << "\", desired: \"" << desired_output << "\"." << endl; - cerr.flush(); + << "\", desired: \"" << desired_output << "\"." << std::endl; + std::cerr.flush(); abort(); } } @@ -1044,14 +1204,14 @@ void TextFormatDecodeData::AddString(int32 key, } string TextFormatDecodeData::Data() const { - ostringstream data_stringstream; + std::ostringstream data_stringstream; if (num_entries() > 0) { io::OstreamOutputStream data_outputstream(&data_stringstream); io::CodedOutputStream output_stream(&data_outputstream); output_stream.WriteVarint32(num_entries()); - for (vector<DataEntry>::const_iterator i = entries_.begin(); + for (std::vector<DataEntry>::const_iterator i = entries_.begin(); i != entries_.end(); ++i) { output_stream.WriteVarint32(i->first); output_stream.WriteString(i->second); @@ -1185,18 +1345,18 @@ string DirectDecodeString(const string& str) { string TextFormatDecodeData::DecodeDataForString(const string& input_for_decode, const string& desired_output) { if ((input_for_decode.size() == 0) || (desired_output.size() == 0)) { - cerr << "error: got empty string for making TextFormat data, input: \"" + std::cerr << "error: got empty string for making TextFormat data, input: \"" << input_for_decode << "\", desired: \"" << desired_output << "\"." - << endl; - cerr.flush(); + << std::endl; + std::cerr.flush(); abort(); } if ((input_for_decode.find('\0') != string::npos) || (desired_output.find('\0') != string::npos)) { - cerr << "error: got a null char in a string for making TextFormat data," + std::cerr << "error: got a null char in a string for making TextFormat data," << " input: \"" << CEscape(input_for_decode) << "\", desired: \"" - << CEscape(desired_output) << "\"." << endl; - cerr.flush(); + << CEscape(desired_output) << "\"." << std::endl; + std::cerr.flush(); abort(); } @@ -1232,6 +1392,296 @@ string TextFormatDecodeData::DecodeDataForString(const string& input_for_decode, return builder.Finish() + (char)'\0'; } +namespace { + +class Parser { + public: + Parser(LineConsumer* line_consumer) + : line_consumer_(line_consumer), line_(0) {} + + // Parses a check of input, returning success/failure. + bool ParseChunk(StringPiece chunk); + + // Should be called to finish parsing (after all input has been provided via + // ParseChunk()). Returns success/failure. + bool Finish(); + + int last_line() const { return line_; } + string error_str() const { return error_str_; } + + private: + bool ParseLoop(); + + LineConsumer* line_consumer_; + int line_; + string error_str_; + StringPiece p_; + string leftover_; +}; + +bool Parser::ParseChunk(StringPiece chunk) { + if (!leftover_.empty()) { + chunk.AppendToString(&leftover_); + p_ = StringPiece(leftover_); + } else { + p_ = chunk; + } + bool result = ParseLoop(); + if (p_.empty()) { + leftover_.clear(); + } else { + leftover_ = p_.ToString(); + } + return result; +} + +bool Parser::Finish() { + if (leftover_.empty()) { + return true; + } + // Force a newline onto the end to finish parsing. + leftover_ += "\n"; + p_ = StringPiece(leftover_); + if (!ParseLoop()) { + return false; + } + return p_.empty(); // Everything used? +} + +bool Parser::ParseLoop() { + StringPiece line; + while (ReadLine(&p_, &line)) { + ++line_; + RemoveComment(&line); + StringPieceTrimWhitespace(&line); + if (line.size() == 0) { + continue; // Blank line. + } + if (!line_consumer_->ConsumeLine(line, &error_str_)) { + return false; + } + } + return true; +} + +} // namespace + +LineConsumer::LineConsumer() {} + +LineConsumer::~LineConsumer() {} + +bool ParseSimpleFile( + const string& path, LineConsumer* line_consumer, string* out_error) { + int fd; + do { + fd = posix::open(path.c_str(), O_RDONLY); + } while (fd < 0 && errno == EINTR); + if (fd < 0) { + *out_error = + string("error: Unable to open \"") + path + "\", " + strerror(errno); + return false; + } + io::FileInputStream file_stream(fd); + file_stream.SetCloseOnDelete(true); + + Parser parser(line_consumer); + const void* buf; + int buf_len; + while (file_stream.Next(&buf, &buf_len)) { + if (buf_len == 0) { + continue; + } + + if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len))) { + *out_error = + string("error: ") + path + + " Line " + SimpleItoa(parser.last_line()) + ", " + parser.error_str(); + return false; + } + } + return parser.Finish(); +} + +ImportWriter::ImportWriter( + const string& generate_for_named_framework, + const string& named_framework_to_proto_path_mappings_path, + bool include_wkt_imports) + : generate_for_named_framework_(generate_for_named_framework), + named_framework_to_proto_path_mappings_path_( + named_framework_to_proto_path_mappings_path), + include_wkt_imports_(include_wkt_imports), + need_to_parse_mapping_file_(true) { +} + +ImportWriter::~ImportWriter() {} + +void ImportWriter::AddFile(const FileDescriptor* file, + const string& header_extension) { + const string file_path(FilePath(file)); + + if (IsProtobufLibraryBundledProtoFile(file)) { + // The imports of the WKTs are only needed within the library itself, + // in other cases, they get skipped because the generated code already + // import GPBProtocolBuffers.h and hence proves them. + if (include_wkt_imports_) { + protobuf_framework_imports_.push_back( + FilePathBasename(file) + header_extension); + protobuf_non_framework_imports_.push_back(file_path + header_extension); + } + return; + } + + // Lazy parse any mappings. + if (need_to_parse_mapping_file_) { + ParseFrameworkMappings(); + } + + std::map<string, string>::iterator proto_lookup = + proto_file_to_framework_name_.find(file->name()); + if (proto_lookup != proto_file_to_framework_name_.end()) { + other_framework_imports_.push_back( + proto_lookup->second + "/" + + FilePathBasename(file) + header_extension); + return; + } + + if (!generate_for_named_framework_.empty()) { + other_framework_imports_.push_back( + generate_for_named_framework_ + "/" + + FilePathBasename(file) + header_extension); + return; + } + + other_imports_.push_back(file_path + header_extension); +} + +void ImportWriter::Print(io::Printer* printer) const { + assert(protobuf_non_framework_imports_.size() == + protobuf_framework_imports_.size()); + + bool add_blank_line = false; + + if (protobuf_framework_imports_.size() > 0) { + const string framework_name(ProtobufLibraryFrameworkName); + const string cpp_symbol(ProtobufFrameworkImportSymbol(framework_name)); + + printer->Print( + "#if $cpp_symbol$\n", + "cpp_symbol", cpp_symbol); + for (std::vector<string>::const_iterator iter = protobuf_framework_imports_.begin(); + iter != protobuf_framework_imports_.end(); ++iter) { + printer->Print( + " #import <$framework_name$/$header$>\n", + "framework_name", framework_name, + "header", *iter); + } + printer->Print( + "#else\n"); + for (std::vector<string>::const_iterator iter = protobuf_non_framework_imports_.begin(); + iter != protobuf_non_framework_imports_.end(); ++iter) { + printer->Print( + " #import \"$header$\"\n", + "header", *iter); + } + printer->Print( + "#endif\n"); + + add_blank_line = true; + } + + if (other_framework_imports_.size() > 0) { + if (add_blank_line) { + printer->Print("\n"); + } + + for (std::vector<string>::const_iterator iter = other_framework_imports_.begin(); + iter != other_framework_imports_.end(); ++iter) { + printer->Print( + "#import <$header$>\n", + "header", *iter); + } + + add_blank_line = true; + } + + if (other_imports_.size() > 0) { + if (add_blank_line) { + printer->Print("\n"); + } + + for (std::vector<string>::const_iterator iter = other_imports_.begin(); + iter != other_imports_.end(); ++iter) { + printer->Print( + "#import \"$header$\"\n", + "header", *iter); + } + } +} + +void ImportWriter::ParseFrameworkMappings() { + need_to_parse_mapping_file_ = false; + if (named_framework_to_proto_path_mappings_path_.empty()) { + return; // Nothing to do. + } + + ProtoFrameworkCollector collector(&proto_file_to_framework_name_); + string parse_error; + if (!ParseSimpleFile(named_framework_to_proto_path_mappings_path_, + &collector, &parse_error)) { + std::cerr << "error parsing " << named_framework_to_proto_path_mappings_path_ + << " : " << parse_error << std::endl; + std::cerr.flush(); + } +} + +bool ImportWriter::ProtoFrameworkCollector::ConsumeLine( + const StringPiece& line, string* out_error) { + int offset = line.find(':'); + if (offset == StringPiece::npos) { + *out_error = + string("Framework/proto file mapping line without colon sign: '") + + line.ToString() + "'."; + return false; + } + StringPiece framework_name(line, 0, offset); + StringPiece proto_file_list(line, offset + 1, line.length() - offset - 1); + StringPieceTrimWhitespace(&framework_name); + + int start = 0; + while (start < proto_file_list.length()) { + offset = proto_file_list.find(',', start); + if (offset == StringPiece::npos) { + offset = proto_file_list.length(); + } + + StringPiece proto_file(proto_file_list, start, offset - start); + StringPieceTrimWhitespace(&proto_file); + if (proto_file.size() != 0) { + std::map<string, string>::iterator existing_entry = + map_->find(proto_file.ToString()); + if (existing_entry != map_->end()) { + std::cerr << "warning: duplicate proto file reference, replacing framework entry for '" + << proto_file.ToString() << "' with '" << framework_name.ToString() + << "' (was '" << existing_entry->second << "')." << std::endl; + std::cerr.flush(); + } + + if (proto_file.find(' ') != StringPiece::npos) { + std::cerr << "note: framework mapping file had a proto file with a space in, hopefully that isn't a missing comma: '" + << proto_file.ToString() << "'" << std::endl; + std::cerr.flush(); + } + + (*map_)[proto_file.ToString()] = framework_name.ToString(); + } + + start = offset + 1; + } + + return true; +} + + } // namespace objectivec } // namespace compiler } // namespace protobuf |