aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
diff options
context:
space:
mode:
authorGravatar Thomas Van Lenten <thomasvl@google.com>2015-07-06 12:29:08 -0400
committerGravatar Thomas Van Lenten <thomasvl@google.com>2015-08-14 13:34:51 -0400
commit4e43931eaf897d719bc718d5baf1ab84444fdfee (patch)
tree275ae0622f1a867ecefc34db938c0f27102af039 /src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
parentedaefac5d2221ab26b9988fb76c79760c6495fb8 (diff)
Add support for a file listing expected package to objc prefixes for validation.
- Add a env var to pass a set of expected prefixes for validation. - Report warnings/errors based on the expected prefixes vs. the data in the files compiled. - Use some helpers from common directory.
Diffstat (limited to 'src/google/protobuf/compiler/objectivec/objectivec_helpers.cc')
-rw-r--r--src/google/protobuf/compiler/objectivec/objectivec_helpers.cc358
1 files changed, 275 insertions, 83 deletions
diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
index 45d122d1..b724d35c 100644
--- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
+++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
@@ -28,10 +28,18 @@
// (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
+#include <unistd.h>
+#endif
#include <climits>
+#include <errno.h>
+#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <sstream>
+#include <stdlib.h>
#include <vector>
#include <google/protobuf/stubs/hash.h>
@@ -39,6 +47,7 @@
#include <google/protobuf/io/coded_stream.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/strutil.h>
// NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
@@ -51,45 +60,6 @@ namespace objectivec {
namespace {
-// islower()/isupper()/tolower()/toupper() change based on locale.
-//
-// src/google/protobuf/stubs/strutil.h:150 has the same pattern. For the
-// Objective C plugin, test failures were seen on TravisCI because isupper('A')
-// was coming back false for some server's locale. This approach avoids any
-// such issues.
-
-bool IsLower(const char c) {
- return ('a' <= c && c <= 'z');
-}
-
-bool IsUpper(const char c) {
- return ('A' <= c && c <= 'Z');
-}
-
-char ToLower(char c) {
- if ('A' <= c && c <= 'Z') {
- c += 'a' - 'A';
- }
- return c;
-}
-
-// toupper() changes based on locale. We don't want this!
-char ToUpper(char c) {
- if ('a' <= c && c <= 'z') {
- c += 'A' - 'a';
- }
- return c;
-}
-
-string TrimString(const string& s) {
- string::size_type start = s.find_first_not_of(" \n\r\t");
- if (start == string::npos) {
- return "";
- }
- string::size_type end = s.find_last_not_of(" \n\r\t") + 1;
- return s.substr(start, end - start);
-}
-
hash_set<string> MakeWordsMap(const char* const words[], size_t num_words) {
hash_set<string> result;
for (int i = 0; i < num_words; i++) {
@@ -115,7 +85,7 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
bool last_char_was_upper = false;
for (int i = 0; i < input.size(); i++) {
char c = input[i];
- if (c >= '0' && c <= '9') {
+ if (ascii_isdigit(c)) {
if (!last_char_was_number) {
values.push_back(current);
current = "";
@@ -123,7 +93,7 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
current += c;
last_char_was_number = last_char_was_lower = last_char_was_upper = false;
last_char_was_number = true;
- } else if (IsLower(c)) {
+ } else if (ascii_islower(c)) {
// lowercase letter can follow a lowercase or uppercase letter
if (!last_char_was_lower && !last_char_was_upper) {
values.push_back(current);
@@ -132,12 +102,12 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
current += c; // already lower
last_char_was_number = last_char_was_lower = last_char_was_upper = false;
last_char_was_lower = true;
- } else if (IsUpper(c)) {
+ } else if (ascii_isupper(c)) {
if (!last_char_was_upper) {
values.push_back(current);
current = "";
}
- current += ToLower(c);
+ current += ascii_tolower(c);
last_char_was_number = last_char_was_lower = last_char_was_upper = false;
last_char_was_upper = true;
} else {
@@ -151,7 +121,7 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
bool all_upper = (kUpperSegments.count(value) > 0);
for (int j = 0; j < value.length(); j++) {
if (j == 0 || all_upper) {
- value[j] = ToUpper(value[j]);
+ value[j] = ascii_toupper(value[j]);
} else {
// Nothing, already in lower.
}
@@ -163,7 +133,7 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
result += *i;
}
if ((result.length() != 0) && !first_capitalized) {
- result[0] = ToLower(result[0]);
+ result[0] = ascii_tolower(result[0]);
}
return result;
}
@@ -272,7 +242,7 @@ bool IsSpecialName(const string& name, const string* special_names,
// If name is longer than the retained_name[i] that it matches
// the next character must be not lower case (newton vs newTon vs
// new_ton).
- return !IsLower(name[length]);
+ return !ascii_islower(name[length]);
} else {
return true;
}
@@ -342,30 +312,6 @@ string FileClassPrefix(const FileDescriptor* file) {
return result;
}
-void ValidateObjCClassPrefix(const FileDescriptor* file) {
- string prefix = file->options().objc_class_prefix();
- if (prefix.length() > 0) {
- // 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.
- if (!IsUpper(prefix[0])) {
- cerr << endl
- << "protoc:0: warning: Invalid 'option objc_class_prefix = \""
- << prefix << "\";' in '" << file->name() << "';"
- << " it should start with a capital letter."
- << endl;
- cerr.flush();
- }
- if (prefix.length() < 3) {
- cerr << 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();
- }
- }
-}
-
string FileClassName(const FileDescriptor* file) {
string name = FileClassPrefix(file);
name += UnderscoresToCamelCase(StripProto(BaseFileName(file)), true);
@@ -453,10 +399,10 @@ string UnCamelCaseEnumShortName(const string& name) {
string result;
for (int i = 0; i < name.size(); i++) {
char c = name[i];
- if (i > 0 && c >= 'A' && c <= 'Z') {
+ if (i > 0 && ascii_isupper(c)) {
result += '_';
}
- result += ToUpper(c);
+ result += ascii_toupper(c);
}
return result;
}
@@ -487,7 +433,7 @@ string FieldNameCapitalized(const FieldDescriptor* field) {
// name.
string result = FieldName(field);
if (result.length() > 0) {
- result[0] = ToUpper(result[0]);
+ result[0] = ascii_toupper(result[0]);
}
return result;
}
@@ -511,7 +457,7 @@ string OneofNameCapitalized(const OneofDescriptor* descriptor) {
// Use the common handling and then up-case the first letter.
string result = OneofName(descriptor);
if (result.length() > 0) {
- result[0] = ToUpper(result[0]);
+ result[0] = ascii_toupper(result[0]);
}
return result;
}
@@ -526,8 +472,8 @@ string UnCamelCaseFieldName(const string& name, const FieldDescriptor* field) {
}
if (field->type() == FieldDescriptor::TYPE_GROUP) {
if (worker.length() > 0) {
- if (worker[0] >= 'a' && worker[0] <= 'z') {
- worker[0] = ToUpper(worker[0]);
+ if (ascii_islower(worker[0])) {
+ worker[0] = ascii_toupper(worker[0]);
}
}
return worker;
@@ -535,11 +481,11 @@ string UnCamelCaseFieldName(const string& name, const FieldDescriptor* field) {
string result;
for (int i = 0; i < worker.size(); i++) {
char c = worker[i];
- if (c >= 'A' && c <= 'Z') {
+ if (ascii_isupper(c)) {
if (i > 0) {
result += '_';
}
- result += ToLower(c);
+ result += ascii_tolower(c);
} else {
result += c;
}
@@ -831,6 +777,252 @@ string BuildCommentsString(const SourceLocation& location) {
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();
+
+ map<string, string>* prefix_map_;
+ 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.
+ p_ = StringPiece(leftover_ + "\n");
+ if (!ParseLoop()) {
+ return false;
+ }
+ return p_.empty(); // Everything used?
+}
+
+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])) {
+ *line = StringPiece(input->data(), len);
+ ++len; // advance over the newline
+ *input = StringPiece(input->data() + len, input->size() - len);
+ return true;
+ }
+ }
+ 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) {
+ input->remove_suffix(input->length() - offset);
+ }
+}
+
+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 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,
+ string* out_error) {
+ const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES");
+ if (file_path == NULL) {
+ 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();
+}
+
+} // namespace
+
+bool ValidateObjCClassPrefix(const FileDescriptor* file, 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.
+
+ // First Check: Warning - if there is a prefix, ensure it is is a reasonable
+ // value according to Apple's rules.
+ if (prefix.length()) {
+ if (!ascii_isupper(prefix[0])) {
+ cerr << endl
+ << "protoc:0: warning: Invalid 'option objc_class_prefix = \""
+ << prefix << "\";' in '" << file->name() << "';"
+ << " it should start with a capital letter." << endl;
+ cerr.flush();
+ }
+ if (prefix.length() < 3) {
+ cerr << 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();
+ }
+ }
+
+ // 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)) {
+ return false;
+ }
+
+ // If there are no expected prefixes, out of here.
+ if (expected_package_prefixes.size() == 0) {
+ return true;
+ }
+
+ // Second Check: Error - See if there was an expected prefix for the package
+ // and report if it doesn't match.
+ map<string, string>::iterator package_match =
+ expected_package_prefixes.find(package);
+ if (package_match != expected_package_prefixes.end()) {
+ // There was an entry, and...
+ if (package_match->second == prefix) {
+ // ...it matches. All good, out of here!
+ return true;
+ } else {
+ // ...it didn't match!
+ *out_error = "protoc:0: error: Expected 'option objc_class_prefix = \"" +
+ package_match->second + "\";' in '" + file->name() + "'";
+ if (prefix.length()) {
+ *out_error += "; but found '" + prefix + "' instead";
+ }
+ *out_error += ".";
+ return false;
+ }
+ }
+
+ // Third Check: Error - If there was a prefix make sure it wasn't expected
+ // for a different package instead (overlap is allowed, but it has to be
+ // listed as an expected overlap).
+ if (prefix.length()) {
+ 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.
+ }
+ }
+ }
+
+ // Fourth Check: Warning - If there was a prefix, and it wasn't expected,
+ // issue a warning suggesting it gets added to the file.
+ if (prefix.length()) {
+ cerr << endl
+ << "protoc:0: warning: Found 'option objc_class_prefix = \"" << prefix
+ << "\";' in '" << file->name() << "';"
+ << " should you add it to the expected prefixes file ("
+ << expect_file_path << ")?" << endl;
+ cerr.flush();
+ }
+
+ return true;
+}
+
void TextFormatDecodeData::AddString(int32 key,
const string& input_for_decode,
const string& desired_output) {
@@ -898,7 +1090,7 @@ class DecodeDataBuilder {
void AddChar(const char desired) {
++segment_len_;
- is_all_upper_ &= IsUpper(desired);
+ is_all_upper_ &= ascii_isupper(desired);
}
void Push() {
@@ -913,9 +1105,9 @@ class DecodeDataBuilder {
bool AddFirst(const char desired, const char input) {
if (desired == input) {
op_ = kOpAsIs;
- } else if (desired == ToUpper(input)) {
+ } else if (desired == ascii_toupper(input)) {
op_ = kOpFirstUpper;
- } else if (desired == ToLower(input)) {
+ } else if (desired == ascii_tolower(input)) {
op_ = kOpFirstLower;
} else {
// Can't be transformed to match.
@@ -953,7 +1145,7 @@ bool DecodeDataBuilder::AddCharacter(const char desired, const char input) {
if (desired == input) {
// If we aren't transforming it, or we're upper casing it and it is
// supposed to be uppercase; just add it to the segment.
- if ((op_ != kOpAllUpper) || IsUpper(desired)) {
+ if ((op_ != kOpAllUpper) || ascii_isupper(desired)) {
AddChar(desired);
return true;
}
@@ -965,7 +1157,7 @@ bool DecodeDataBuilder::AddCharacter(const char desired, const char input) {
// If we need to uppercase, and everything so far has been uppercase,
// promote op to AllUpper.
- if ((desired == ToUpper(input)) && is_all_upper_) {
+ if ((desired == ascii_toupper(input)) && is_all_upper_) {
op_ = kOpAllUpper;
AddChar(desired);
return true;