From fcb5c896996a31d324c864277cff5fc011bea2ff Mon Sep 17 00:00:00 2001 From: Ben Bader Date: Tue, 13 Nov 2018 16:06:24 -0800 Subject: [PATCH] Add redaction and toJson to obj-c thrifts --- compiler/cpp/CMakeLists.txt | 9 + compiler/cpp/Makefile.am | 12 +- compiler/cpp/src/Makefile.am | 9 + .../src/thrift/generate/t_cocoa_generator.cc | 728 +++++++++++++++++- .../cpp/src/thrift/generate/t_generator.h | 9 +- compiler/cpp/src/thrift/parse/t_base_type.cc | 20 + compiler/cpp/src/thrift/parse/t_doc.h | 2 +- compiler/cpp/src/thrift/parse/t_enum.cc | 21 + compiler/cpp/src/thrift/parse/t_field.cc | 59 ++ compiler/cpp/src/thrift/parse/t_field.h | 32 + compiler/cpp/src/thrift/parse/t_list.cc | 20 + compiler/cpp/src/thrift/parse/t_map.cc | 20 + compiler/cpp/src/thrift/parse/t_service.cc | 20 + compiler/cpp/src/thrift/parse/t_set.cc | 20 + compiler/cpp/src/thrift/parse/t_struct.cc | 20 + compiler/cpp/src/thrift/parse/t_type.h | 1 + compiler/cpp/src/thrift/parse/t_typedef.cc | 1 + compiler/cpp/src/thrift/parse/t_visitor.h | 194 +++++ 18 files changed, 1182 insertions(+), 15 deletions(-) create mode 100644 compiler/cpp/src/thrift/parse/t_base_type.cc create mode 100644 compiler/cpp/src/thrift/parse/t_enum.cc create mode 100644 compiler/cpp/src/thrift/parse/t_field.cc create mode 100644 compiler/cpp/src/thrift/parse/t_list.cc create mode 100644 compiler/cpp/src/thrift/parse/t_map.cc create mode 100644 compiler/cpp/src/thrift/parse/t_service.cc create mode 100644 compiler/cpp/src/thrift/parse/t_set.cc create mode 100644 compiler/cpp/src/thrift/parse/t_struct.cc create mode 100644 compiler/cpp/src/thrift/parse/t_visitor.h diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt index 5da28aad441..844ec0f6de8 100644 --- a/compiler/cpp/CMakeLists.txt +++ b/compiler/cpp/CMakeLists.txt @@ -52,6 +52,14 @@ add_library(parse STATIC ${parse_SOURCES}) set(compiler_core src/thrift/common.cc src/thrift/generate/t_generator.cc + src/thrift/parse/t_base_type.cc + src/thrift/parse/t_enum.cc + src/thrift/parse/t_field.cc + src/thrift/parse/t_list.cc + src/thrift/parse/t_map.cc + src/thrift/parse/t_service.cc + src/thrift/parse/t_set.cc + src/thrift/parse/t_struct.cc src/thrift/parse/t_typedef.cc src/thrift/parse/parse.cc ${CMAKE_CURRENT_BINARY_DIR}/thrift/version.h @@ -199,6 +207,7 @@ if(${WITH_PLUGIN}) "src/thrift/parse/t_struct.h" "src/thrift/parse/t_typedef.h" "src/thrift/parse/t_type.h" + "src/thrift/parse/t_visitor.h" DESTINATION "${INCLUDE_INSTALL_DIR}/thrift/parse") install(FILES "src/thrift/plugin/plugin.h" diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am index 482a89a4d1a..4fae76b3431 100644 --- a/compiler/cpp/Makefile.am +++ b/compiler/cpp/Makefile.am @@ -52,22 +52,31 @@ compiler_core = src/thrift/common.h \ src/thrift/parse/t_doc.h \ src/thrift/parse/t_type.h \ src/thrift/parse/t_base_type.h \ + src/thrift/parse/t_base_type.cc \ src/thrift/parse/t_enum.h \ + src/thrift/parse/t_enum.cc \ src/thrift/parse/t_enum_value.h \ src/thrift/parse/t_typedef.h \ src/thrift/parse/t_typedef.cc \ src/thrift/parse/t_container.h \ src/thrift/parse/t_list.h \ + src/thrift/parse/t_list.cc \ src/thrift/parse/t_set.h \ + src/thrift/parse/t_set.cc \ src/thrift/parse/t_map.h \ + src/thrift/parse/t_map.cc \ src/thrift/parse/t_struct.h \ + src/thrift/parse/t_struct.cc \ src/thrift/parse/t_field.h \ + src/thrift/parse/t_field.cc \ src/thrift/parse/t_service.h \ + src/thrift/parse/t_service.cc \ src/thrift/parse/t_function.h \ src/thrift/parse/t_program.h \ src/thrift/parse/t_scope.h \ src/thrift/parse/t_const.h \ src/thrift/parse/t_const_value.h \ + src/thrift/parse/t_visitor.h \ src/thrift/parse/parse.cc \ src/thrift/generate/t_generator.h \ src/thrift/generate/t_oop_generator.h \ @@ -173,7 +182,8 @@ include_parse_HEADERS = src/thrift/parse/t_service.h \ src/thrift/parse/t_function.h \ src/thrift/parse/t_type.h \ src/thrift/parse/t_doc.h \ - src/thrift/parse/t_struct.h + src/thrift/parse/t_struct.h \ + src/thrift/parse/t_visitor.h include_plugindir = $(include_thriftdir)/plugin include_plugin_HEADERS = src/thrift/plugin/plugin.h \ diff --git a/compiler/cpp/src/Makefile.am b/compiler/cpp/src/Makefile.am index bc2c5cbacea..c0dac5addb1 100644 --- a/compiler/cpp/src/Makefile.am +++ b/compiler/cpp/src/Makefile.am @@ -54,23 +54,32 @@ thrift_thrift_bootstrap_SOURCES = \ thrift/parse/t_doc.h \ thrift/parse/t_type.h \ thrift/parse/t_base_type.h \ + thrift/parse/t_base_type.cc \ thrift/parse/t_enum.h \ + thrift/parse/t_enum.cc \ thrift/parse/t_enum_value.h \ thrift/parse/t_typedef.h \ thrift/parse/t_typedef.cc \ thrift/parse/t_container.h \ thrift/parse/t_list.h \ + thrift/parse/t_list.cc \ thrift/parse/t_set.h \ + thrift/parse/t_set.cc \ thrift/parse/t_map.h \ + thrift/parse/t_map.cc \ thrift/parse/t_struct.h \ + thrift/parse/t_struct.cc \ thrift/parse/t_field.h \ + thrift/parse/t_field.cc \ thrift/parse/t_service.h \ + thrift/parse/t_service.cc \ thrift/parse/t_function.h \ thrift/parse/t_program.h \ thrift/parse/t_scope.h \ thrift/parse/t_const.h \ thrift/parse/t_const_value.h \ thrift/parse/parse.cc \ + thrift/parse/t_visitor.h \ thrift/generate/t_generator.h \ thrift/generate/t_oop_generator.h \ thrift/generate/t_html_generator.h \ diff --git a/compiler/cpp/src/thrift/generate/t_cocoa_generator.cc b/compiler/cpp/src/thrift/generate/t_cocoa_generator.cc index c2f09e8e68d..c39b9d866a1 100644 --- a/compiler/cpp/src/thrift/generate/t_cocoa_generator.cc +++ b/compiler/cpp/src/thrift/generate/t_cocoa_generator.cc @@ -19,6 +19,9 @@ #include #include +// Begin-MS-Specific +#include +// End-MS-Specific #include #include @@ -27,6 +30,9 @@ #include #include "thrift/platform.h" #include "thrift/generate/t_oop_generator.h" +// Begin-MS-Specific +#include "thrift/parse/t_visitor.h" +// End-MS-Specific using std::map; using std::ostream; @@ -36,6 +42,158 @@ using std::string; using std::stringstream; using std::vector; +// Begin-MS-Specific +namespace { + +/** + * Gets the name of the given type as used in Thrift IDL. + * + * That is, for an i8 t_basic_type, would return the string "i8", + * and for a map of strings to doubles would return "map { + public: + virtual ~NameVisitor() = default; + + virtual string visit_base(t_base_type* base) { + return t_base_type::t_base_name(base->get_base()); + } + + virtual string visit_binary(t_base_type*) { + // t_base_name doesn't distinguish binary from string, so we'll do that ourselves. + return "binary"; + } + + virtual string visit_list(t_list* list) { + return "list<" + name_of(list->get_elem_type()) + ">"; + } + + virtual string visit_set(t_set* set) { + return "set<" + name_of(set->get_elem_type()) + ">"; + } + + virtual string visit_map(t_map* map) { + return "map<" + name_of(map->get_key_type()) + ", " + name_of(map->get_val_type()) + ">"; + } + + virtual string visit_enum(t_enum* tenum) { + return tenum->get_name(); + } + + virtual string visit_struct(t_struct* tstruct) { + return tstruct->get_name(); + } + + virtual string visit_service(t_service* service) { + return service->get_name(); + } + }; + + NameVisitor visitor; + return visit(t, visitor); +} + +string obfuscate(t_field* field, const string& accessor) { + class FieldObfuscatingVisitor : public t_visitor { + public: + string result; + string field_name; + bool is_container_element; + + FieldObfuscatingVisitor(const string& fieldName, bool isContainerElement = false) + : field_name(fieldName) + , is_container_element(isContainerElement) { + } + + virtual ~FieldObfuscatingVisitor() = default; + + virtual string visit_bool(t_base_type*) { + return "HashBool(" + field_name + ")"; + } + + virtual string visit_i8(t_base_type*) { + return "HashI8(" + field_name + ")"; + } + + virtual string visit_i16(t_base_type*) { + return "HashI16(" + field_name + ")"; + } + + virtual string visit_i32(t_base_type*) { + return "HashI32(" + field_name + ")"; + } + + virtual string visit_i64(t_base_type*) { + return "HashI64(" + field_name + ")"; + } + + virtual string visit_double(t_base_type*) { + return "HashDouble(" + field_name + ")"; + } + + virtual string visit_string(t_base_type*) { + return "HashValue(" + field_name + ")"; + } + + virtual string visit_binary(t_base_type*) { + return "HashValue(" + field_name + ")"; + } + + virtual string visit_list(t_list* list) { + return "[NSString stringWithFormat:@\"" + name_of(list) + "(size=%lu)\", (unsigned long) [" + field_name + " count]]"; + } + + virtual string visit_set(t_set* set) { + return "[NSString stringWithFormat:@\"" + name_of(set) + "(size=%lu)\", (unsigned long) [" + field_name + " count]]"; + } + + virtual string visit_map(t_map* map) { + return "[NSString stringWithFormat:@\"" + name_of(map) + "(size=%lu)\", (unsigned long) [" + field_name + " count]]"; + } + + virtual string visit_enum(t_enum*) { + return "HashI32(" + field_name + ")"; + } + + virtual string visit_struct(t_struct*) { + return "HashValue(" + field_name + ")"; + } + }; + + FieldObfuscatingVisitor visitor(accessor); + return visit(field->get_type(), visitor); +} + +string json(const string& text) { + stringstream ss; + + for (const auto& c : text) { + switch (c) { + case '"': ss << "\\\""; break; + case '\\': ss << "\\\\"; break; + case '\b': ss << "\\b"; break; + case '\f': ss << "\\f"; break; + case '\n': ss << "\\n"; break; + case '\r': ss << "\\r"; break; + case '\t': ss << "\\t"; break; + default: + if (static_cast(c) < 32 || static_cast(c) >= 127) { + ss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast(c); + } else { + ss << c; + } + break; + } + } + + return ss.str(); +} + +} // end namespace +// End-MS-Specific + static const string endl = "\n"; // avoid ostream << std::endl flushes /** @@ -58,6 +216,10 @@ class t_cocoa_generator : public t_oop_generator { promise_kit_ = false; debug_descriptions_ = false; pods_ = false; + // Begin-MS-Specific + redaction_ = false; + emit_to_json_ = false; + // End-MS-Specific for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { if( iter->first.compare("log_unexpected") == 0) { log_unexpected_ = true; @@ -71,6 +233,12 @@ class t_cocoa_generator : public t_oop_generator { debug_descriptions_ = true; } else if( iter->first.compare("pods") == 0) { pods_ = true; + // Begin-MS-Specific + } else if( iter->first.compare("redaction") == 0) { + redaction_ = true; + } else if( iter->first.compare("emit_to_json") == 0) { + emit_to_json_ = true; + // End-MS-Specific } else { throw "unknown option cocoa:" + iter->first; } @@ -88,6 +256,14 @@ class t_cocoa_generator : public t_oop_generator { void generate_consts(std::vector consts); + // Begin-MS-Specific + void generate_hash_function_prototypes(); + void generate_hash_function_impl(); + + void generate_json_function_prototypes(); + void generate_json_function_impl(); + // End-MS-Specific + /** * Program-level generation functions */ @@ -98,6 +274,10 @@ class t_cocoa_generator : public t_oop_generator { void generate_xception(t_struct* txception); void generate_service(t_service* tservice); + // Begin-MS-Specific + void generate_enum_name_lookup(t_enum* tenum); + // End-MS-Specific + void print_const_value(ostream& out, string name, t_type* type, @@ -139,6 +319,16 @@ class t_cocoa_generator : public t_oop_generator { void generate_cocoa_struct_validator(std::ofstream& out, t_struct* tstruct); void generate_cocoa_struct_description(std::ofstream& out, t_struct* tstruct); + // Begin-MS-specific + void generate_cocoa_struct_tojson(std::ostream& out, t_struct* tstruct); + void generate_cocoa_struct_tojson_method(std::ostream& out, + t_field* field, + int& counter, + size_t levels, + bool is_container_element, + bool is_map_element = false); + // End-MS-specific + std::string function_result_helper_struct_type(t_service *tservice, t_function* tfunction); std::string function_args_helper_struct_type(t_service* tservice, t_function* tfunction); void generate_function_helpers(t_service *tservice, t_function* tfunction); @@ -235,6 +425,7 @@ class t_cocoa_generator : public t_oop_generator { std::string call_field_setter(t_field* tfield, std::string fieldName); std::string box(t_type *ttype, std::string field_name); std::string unbox(t_type* ttype, std::string field_name); + std::string field_name(t_field* field); std::string getter_name(string field_name); std::string setter_name(string field_name); @@ -263,6 +454,8 @@ class t_cocoa_generator : public t_oop_generator { bool promise_kit_; bool debug_descriptions_; bool pods_; + bool redaction_; + bool emit_to_json_; }; /** @@ -293,6 +486,16 @@ void t_cocoa_generator::init_generator() { f_impl_ << cocoa_imports() << cocoa_thrift_imports() << "#import \"" << f_header_name << "\"" << endl << endl; + // Begin-MS-Specific + if (redaction_ || emit_to_json_) { + generate_hash_function_prototypes(); + } + + if (emit_to_json_) { + generate_json_function_prototypes(); + } + // End-MS-Specific + error_constant_ = 60000; } @@ -302,7 +505,13 @@ void t_cocoa_generator::init_generator() { * @return List of imports for Cocoa libraries */ string t_cocoa_generator::cocoa_imports() { - return string() + "#import \n" + "\n"; + // Begin-MS-Specific + string imports = "#import \n\n"; + if (emit_to_json_) { + imports += "#import \n\n"; + } + return imports; + // End-MS-Specific } /** @@ -369,8 +578,171 @@ void t_cocoa_generator::close_generator() { // stick our constants declarations at the end of the header file // since they refer to things we are defining. f_header_ << constants_declarations_ << endl; + + // Begin-MS-Specific + if (redaction_ || emit_to_json_) { + generate_hash_function_impl(); + } + + if (emit_to_json_) { + generate_json_function_impl(); + } + // End-MS-Specific +} + +// Begin-MS-Specific +void t_cocoa_generator::generate_hash_function_prototypes() { + f_impl_ << R"objc( +// Functions to support value obfuscation +static NSString* HexEncode(NSData*); +static NSString* HashBool(BOOL); +static NSString* HashI8(SInt8); +static NSString* HashI16(SInt16); +static NSString* HashI32(SInt32); +static NSString* HashI64(SInt64); +static NSString* HashDouble(double); +static NSString* HashValue(id); + +)objc"; +} + +void t_cocoa_generator::generate_hash_function_impl() { + f_impl_ << R"objc( +static NSString* HexEncode(NSData *toEncode) { + static char const *kHexAlphabet = "0123456789ABCDEF"; + NSUInteger numBytes = toEncode.length; + + if (numBytes > 0) { + const unsigned char *data = toEncode.bytes; + char *hex = malloc(sizeof(char) * numBytes * 2); + char *p = hex; + for (NSUInteger i = 0; i < numBytes; ++i) { + unsigned char c = *data++; + *p++ = kHexAlphabet[((c & 0xF0) >> 4)]; + *p++ = kHexAlphabet[ (c & 0x0F) ]; + } + return [[NSString alloc] initWithBytesNoCopy:hex + length:(numBytes * 2) + encoding:NSASCIIStringEncoding + freeWhenDone:YES]; + } else { + return @""; + } +} + +typedef union { + SInt64 i64; + double d; + SInt32 i32; + SInt16 i16; + SInt8 i8; + BOOL b; +} t_IntConverter; + +static NSString* HashBool(BOOL b) { + t_IntConverter ic = {.i64 = 0}; + ic.b = b; + return HashI64(ic.i64); +} + +static NSString* HashI8(SInt8 i8) { + t_IntConverter ic = {.i64 = 0}; + ic.i8 = i8; + return HashI64(ic.i64); +} + +static NSString* HashI16(SInt16 i16) { + t_IntConverter ic = {.i64 = 0}; + ic.i16 = i16; + return HashI64(ic.i64); } +static NSString* HashI32(SInt32 i32) { + t_IntConverter ic = {.i64 = 0}; + ic.i32 = i32; + return HashI64(ic.i64); +} + +static NSString* HashI64(SInt64 i64) { + return HexEncode([NSData dataWithBytes:((const void *) (&i64)) length:sizeof(SInt64)]); +} + +static NSString* HashDouble(double d) { + t_IntConverter ic = {.i64 = 0}; + ic.d = d; + return HashI64(ic.i64); +} + +static NSString* HashValue(id value) { + NSUInteger hash; + if (value == nil || ![value respondsToSelector:@selector(hash)]) { + hash = 0x9e3779b9; + } else { + hash = [value hash]; + } + NSData *data = [NSData dataWithBytes:&hash length:sizeof(hash)]; + return HexEncode(data); +} + +)objc"; +} + +void t_cocoa_generator::generate_json_function_prototypes() { + f_header_ << R"objc( +@protocol ThriftToJson +- (void) toJson: (NSMutableString *) builder; +@end + +)objc"; + + f_impl_ << R"objc( +static void AppendJsonEscapedString(NSMutableString*, NSString*); +static void AppendJsonEscapedBinary(NSMutableString*, NSData*); + +)objc"; +} + +void t_cocoa_generator::generate_json_function_impl() { + f_impl_ << R"objc( +static void AppendJsonEscapedString(NSMutableString *ms, NSString *toEscape) { + NSUInteger len = toEscape.length; + unichar buffer[len]; + + [toEscape getCharacters:buffer range:NSMakeRange(0, len)]; + + [ms appendString:@"\""]; + for (int i = 0; i < len; ++i) { + unichar c = buffer[i]; + switch (c) { + case '\\': [ms appendString:@"\\\\"]; break; + case '\"': [ms appendString:@"\\\""]; break; + case '\b': [ms appendString:@"\\b"]; break; + case '\f': [ms appendString:@"\\f"]; break; + case '\n': [ms appendString:@"\\n"]; break; + case '\r': [ms appendString:@"\\r"]; break; + case '\t': [ms appendString:@"\\r"]; break; + default: + if (c < 32 || c >= 127) { + [ms appendFormat:@"\\u%04x", (int) c]; + } else { + [ms appendFormat:@"%C", c]; + } + break; + } + } + [ms appendString:@"\""]; +} + +static void AppendJsonEscapedBinary(NSMutableString *ms, NSData *toEscape) { + [ms appendString:@"\""]; + [ms appendString:HexEncode(toEscape)]; + [ms appendString:@"\""]; +} + +)objc"; +} +// End-MS-Specific + /** * Generates a typedef. This is just a simple 1-liner in objective-c * @@ -434,8 +806,34 @@ void t_cocoa_generator::generate_enum(t_enum* tenum) { indent_down(); f_header_ << endl << "};" << endl << endl; + + // Begin-MS-Specific + if (emit_to_json_) { + generate_enum_name_lookup(tenum); + } + // End-MS-Specific } +// Begin-MS-Specific +void t_cocoa_generator::generate_enum_name_lookup(t_enum* tenum) { + string enumName = cocoa_prefix_ + tenum->get_name(); + string functionName = "NameOf" + enumName; + string prototype = "NSString* " + functionName + "(" + enumName + " value)"; + + f_header_ << prototype << ";" << endl << endl; + + f_impl_ << prototype << " {" << endl; + indent(f_impl_, 1) << "switch (value) {" << endl; + for (const auto& member : tenum->get_constants()) { + string memberName = cocoa_prefix_ + tenum->get_name() + member->get_name(); + indent(f_impl_, 2) << "case " << memberName << ": return @\"" << member->get_name() << "\";" << endl; + } + indent(f_impl_, 2) << "default: return @\"UNKNOWN\";" << endl; + indent(f_impl_, 1) << "}" << endl; + f_impl_ << "}" << endl << endl; +} +// End-MS-Specific + /** * Generates a class that holds all the constants. */ @@ -586,7 +984,14 @@ void t_cocoa_generator::generate_cocoa_struct_interface(ofstream& out, } else { out << "NSObject "; } - out << " " << endl; + + // Begin-MS-Specific + out << "" << endl; out << endl; @@ -1009,6 +1414,12 @@ void t_cocoa_generator::generate_cocoa_struct_implementation(ofstream& out, generate_cocoa_struct_validator(out, tstruct); generate_cocoa_struct_description(out, tstruct); + // Begin-MS-Specific + if (emit_to_json_) { + generate_cocoa_struct_tojson(out, tstruct); + } + // End-MS-Specific + out << "@end" << endl << endl; } @@ -1316,6 +1727,11 @@ void t_cocoa_generator::generate_cocoa_struct_description(ofstream& out, t_struc vector::const_iterator f_iter; bool first = true; for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // Begin-MS-Specific + string fieldName = field_name(*f_iter); + string isSet = fieldName + "IsSet"; + // End-MS-Specific + if (first) { first = false; indent(out) << "[ms appendString: @\"" << (*f_iter)->get_name() << ":\"];" << endl; @@ -1323,8 +1739,22 @@ void t_cocoa_generator::generate_cocoa_struct_description(ofstream& out, t_struc indent(out) << "[ms appendString: @\"," << (*f_iter)->get_name() << ":\"];" << endl; } t_type* ttype = (*f_iter)->get_type(); - indent(out) << "[ms appendFormat: @\"" << format_string_for_type(ttype) << "\", " - << format_cast_for_type(ttype) << "_" << (*f_iter)->get_name() << "];" << endl; + // Begin-MS-Specific + if ((*f_iter)->is_redacted()) { + indent(out) << "[ms appendString:@\"\"];" << endl; + } else if ((*f_iter)->is_obfuscated()) { + string isReallySet = isSet; + if (type_can_be_null(ttype)) { + isReallySet += " && " + fieldName + " != nil"; + } + indent(out, 1) << "if (" << isReallySet << ") {" << endl; + indent(out, 2) << "[ms appendString:" << obfuscate(*f_iter, fieldName) << "];" << endl; + indent(out, 1) << "}" << endl; + } else { + indent(out) << "[ms appendFormat: @\"" << format_string_for_type(ttype) << "\", " + << format_cast_for_type(ttype) << fieldName << "];" << endl; + } + // End-MS-Specific } out << indent() << "[ms appendString: @\")\"];" << endl << indent() << "return [NSString stringWithString: ms];" << endl; @@ -1333,6 +1763,265 @@ void t_cocoa_generator::generate_cocoa_struct_description(ofstream& out, t_struc indent(out) << "}" << endl << endl; } +// Begin-MS-Specific + +void t_cocoa_generator::generate_cocoa_struct_tojson(std::ostream& out, t_struct* tstruct) { + out << indent() << "- (void) toJson:(NSMutableString *)builder {" << endl; + + indent(out, 1) << R"objc([builder appendString:@"{\"__type\": \")objc" << json(tstruct->get_name()) << "\\\"\"];" << endl; + + int counter = 1; + for (const auto& field : tstruct->get_members()) { + indent(out, 1) << R"objc([builder appendString:@", \")objc" << field->get_name() << R"objc(\": "];)objc" << endl; + generate_cocoa_struct_tojson_method(out, field, counter, 1, false); + } + + indent(out, 1) << R"objc([builder appendString:@"}"];)objc" << endl; + out << indent() << "}" << endl << endl; +} + +void t_cocoa_generator::generate_cocoa_struct_tojson_method( + std::ostream& out, + t_field* field, + int& counter, + size_t levels, + bool is_container_element, + bool is_map_element) { + + t_type* type = field->get_type()->get_true_type(); + + if (field->is_redacted()) { + indent(out, levels) << R"objc([builder appendString:@"\"\""];)objc" << endl; + return; + } + + string fieldName; + size_t nl; // next_level, but shorter + bool isObfuscated = false; + + if (is_container_element) { + nl = levels; + fieldName = field->get_name(); + } else { + nl = levels + 1; + fieldName = this->field_name(field); + + // Make sure the field is set (and non-nil, if applicable). + // We'll just output a JSON "null" if it isn't. + string isSet = fieldName + "IsSet"; + if (type_can_be_null(type)) { + isSet += " && " + fieldName + " != nil"; + } + isObfuscated = field->is_obfuscated(); + + indent(out, levels) << "if (" << isSet << ") {" << endl; + } + + if (type->is_base_type()) { + t_base_type* base_type = static_cast(type); + auto base = base_type->get_base(); + + if (base == t_base_type::TYPE_BOOL) { + string accessor = (is_container_element && !is_map_element) // NOTE: https://github.com/outlook/thrift/pull/5 + ? "[" + fieldName + " boolValue]" + : fieldName; + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, accessor) << ");" << endl; + } else { + indent(out, nl) << "[builder appendString:(" << accessor << " ? @\"true\" : @\"false\")];" << endl; + } + + } else if (base == t_base_type::TYPE_I8) { + string accessor = is_container_element && !is_map_element ? "[" + fieldName + " unsignedCharValue]" : fieldName; + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, accessor) << ");" << endl; + } else { + indent(out, nl) << "[builder appendFormat:@\"%2X\", " << accessor << "];" << endl; + } + + } else if (base == t_base_type::TYPE_I16 || base == t_base_type::TYPE_I32 || base == t_base_type::TYPE_I64) { + string formatSpec; + if (is_container_element && !is_map_element) { // NOTE: https://github.com/outlook/thrift/pull/4 + formatSpec = "@\"%@\""; + } else if (base == t_base_type::TYPE_I16) { + formatSpec = "@\"%\" PRId16"; + } else if (base == t_base_type::TYPE_I32) { + formatSpec = "@\"%\" PRId32"; + } else if (base == t_base_type::TYPE_I64) { + formatSpec = "@\"%\" PRId64"; + } else { + throw "COMPILER ERROR: Unexpected non-container base type: " + type->get_name(); + } + + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + indent(out, nl) << "[builder appendFormat:" << formatSpec << ", " << fieldName << "];" << endl; + } + + } else if (base == t_base_type::TYPE_DOUBLE) { + string formatSpec; + if (is_container_element) { + formatSpec = "@\"%@\""; + } else { + formatSpec = "@\"%f\""; + } + + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + indent(out, nl) << "[builder appendFormat:" << formatSpec << ", " << fieldName << "];" << endl; + } + + } else if (base == t_base_type::TYPE_STRING && base_type->is_binary()) { + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + indent(out, nl) << "AppendJsonEscapedBinary(builder, " << fieldName << ");" << endl; + } + + } else if (base == t_base_type::TYPE_STRING) { + string accessor = isObfuscated ? obfuscate(field, fieldName) : fieldName; + indent(out, nl) << "AppendJsonEscapedString(builder, " << accessor << ");" << endl; + + } else { + throw "Unexpected t_base_type::base value: " + boost::lexical_cast(base); + } + + } else if (type->is_list() || type->is_set()) { + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + t_type* element; + if (type->is_list()) { + element = static_cast(type)->get_elem_type()->get_true_type(); + } else if (type->is_set()) { + element = static_cast(type)->get_elem_type()->get_true_type(); + } else { + throw "wtf"; + } + + string elementName = element_type_name(element); + string firstFlag = "first_" + boost::lexical_cast(counter++); + string iter = "elem_" + boost::lexical_cast(counter++); + + t_field fakeField(element, iter); + if (element->is_enum()) { + string unboxedName = unbox(element, iter); + fakeField = t_field{element, unboxedName}; + } + + indent(out, nl) << "[builder appendString:@\"[\"];" << endl; + indent(out, nl) << elementName << " " << iter << ";" << endl; + indent(out, nl) << "BOOL " << firstFlag << " = YES;" << endl; + indent(out, nl) << "for (" << iter << " in " << fieldName << ") {" << endl; + indent(out, nl + 1) << "if (!" << firstFlag << ") {" << endl; + indent(out, nl + 2) << "[builder appendString:@\", \"];" << endl; + indent(out, nl + 1) << "} else {" << endl; + indent(out, nl + 2) << firstFlag << " = NO;" << endl; + indent(out, nl + 1) << "}" << endl; + + // Recursive call + generate_cocoa_struct_tojson_method(out, &fakeField, counter, nl + 1, /* is_container_element = */ true); + + indent(out, nl) << "}" << endl; + indent(out, nl) << R"([builder appendString:@"]"];)" << endl; + } + + } else if (type->is_map()) { + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + t_map* map = static_cast(type); + + t_type* keyType = map->get_key_type()->get_true_type(); + t_type* valType = map->get_val_type()->get_true_type(); + + string keyTypeName = element_type_name(keyType); + string valTypeName = element_type_name(valType); + + string keyName = "key_" + boost::lexical_cast(counter++); + string valName = "val_" + boost::lexical_cast(counter++); + string firstFlag = "first_" + boost::lexical_cast(counter++); + + indent(out, nl) << "[builder appendString:@\"{\"];" << endl; + indent(out, nl) << keyTypeName << " " << keyName << ";" << endl; + indent(out, nl) << "BOOL " << firstFlag << " = YES;" << endl; + indent(out, nl) << "for (" << keyName << " in " << fieldName << ") {" << endl; + indent(out, nl + 1) << "if (!" << firstFlag << ") {" << endl; + indent(out, nl + 2) << "[builder appendString:@\", \"];" << endl; + indent(out, nl + 1) << "} else {" << endl; + indent(out, nl + 2) << firstFlag << " = NO;" << endl; + indent(out, nl + 1) << "}" << endl; + + indent(out, nl + 1) << valTypeName << " " << valName << " = [" << fieldName << " objectForKey:" << keyName << "];" << endl; + + t_field fake_key_field(keyType, keyName); + t_field fake_val_field(valType, unbox(valType, valName)); + + // HACK ATTACK + // This assumes keys are strings, and coerces them as such if they aren't. + // JSON object keys must be strings, but thrift map keys have no such constraint. + if (keyType->is_string()) { + string appendMethod; + if (keyType->is_binary()) { + appendMethod = "AppendJsonEscapedBinary"; + } else { + appendMethod = "AppendJsonEscapedString"; + } + + indent(out, nl + 1) << appendMethod << "(builder, " << keyName << ");" << endl; + } else { + indent(out, nl + 1) << R"([builder appendFormat:@"\"%@\"", )" << keyName << "];" << endl; + } + + indent(out, nl + 1) << R"([builder appendString:@": "];)" << endl; + + // Recursive call + generate_cocoa_struct_tojson_method( + out, + &fake_val_field, + counter, + nl + 1, + true, // is_container_element + true // is_map_element + ); + + // Close up the map + indent(out, nl) << "}" << endl; + indent(out, nl) << "[builder appendString:@\"}\"];" << endl; + } + + } else if (type->is_enum()) { + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + string nameOf = "NameOf" + cocoa_prefix_ + type->get_name(); + indent(out, nl) << R"([builder appendFormat:@"\"%@\"", )" << nameOf << "(" << fieldName << ")];" << endl; + } + } else if (type->is_struct() || type->is_xception()) { + if (isObfuscated) { + indent(out, nl) << "AppendJsonEscapedString(builder, " << obfuscate(field, fieldName) << ");" << endl; + } else { + indent(out, nl) << "[" << fieldName << " toJson:builder];" << endl; + } + } else if (type->is_service()) { + throw "Cannot JSONify a service"; + } else if (type->is_typedef()) { + throw "generating toJson: typedefs should have been resolved by now"; + } else { + throw "Neither fish nor fowl: " + type->get_name(); + } + + if (!is_container_element) { + indent(out, levels) << "} else {" << endl; + indent(out, levels + 1) << "[builder appendString:@\"null\"];" << endl; + indent(out, levels) << "}" << endl; + } +} + +// End-MS-Specific + /** * Generates a thrift service. In Objective-C this consists of a * protocol definition, a client interface and a client implementation. @@ -2345,6 +3034,10 @@ string t_cocoa_generator::unbox(t_type* ttype, string field_name) { return field_name; } +string t_cocoa_generator::field_name(t_field* field) { + return "_" + field->get_name(); +} + /** * Generates code to deserialize a map element */ @@ -2733,7 +3426,7 @@ void t_cocoa_generator::print_const_value(ostream& out, indent(out); const vector& fields = ((t_struct*)type)->get_members(); vector::const_iterator f_iter; - const map& val = value->get_map(); + auto val = value->get_map(); map::const_iterator v_iter; if (defval) out << type_name(type) << " "; @@ -2758,11 +3451,11 @@ void t_cocoa_generator::print_const_value(ostream& out, indent(mapout); t_type* ktype = ((t_map*)type)->get_key_type(); t_type* vtype = ((t_map*)type)->get_val_type(); - const map& val = value->get_map(); + auto val = value->get_map(); map::const_iterator v_iter; if (defval) mapout << type_name(type) << " "; - mapout << name << " = @{"; + mapout << name << " = [NSMutableDictionary dictionaryWithDictionary:@{"; for (v_iter = val.begin(); v_iter != val.end();) { mapout << render_const_value(out, ktype, v_iter->first, true) << ": " << render_const_value(out, vtype, v_iter->second, true); @@ -2770,8 +3463,11 @@ void t_cocoa_generator::print_const_value(ostream& out, mapout << ", "; } } - mapout << "}"; + mapout << "}]"; out << mapout.str(); + if (!defval) { + out << ";" << endl; + } } else if (type->is_list()) { ostringstream listout; indent(listout); @@ -2780,15 +3476,18 @@ void t_cocoa_generator::print_const_value(ostream& out, vector::const_iterator v_iter; if (defval) listout << type_name(type) << " "; - listout << name << " = @["; + listout << name << " = [NSMutableArray arrayWithArray:@["; for (v_iter = val.begin(); v_iter != val.end();) { listout << render_const_value(out, etype, *v_iter, true); if (++v_iter != val.end()) { listout << ", "; } } - listout << "]"; + listout << "]]"; out << listout.str(); + if (!defval) { + out << ";" << endl; + } } else if (type->is_set()) { ostringstream setout; indent(setout); @@ -2797,7 +3496,7 @@ void t_cocoa_generator::print_const_value(ostream& out, vector::const_iterator v_iter; if (defval) setout << type_name(type) << " "; - setout << name << " = [NSSet setWithArray:@["; + setout << name << " = [NSMutableSet setWithArray:@["; for (v_iter = val.begin(); v_iter != val.end();) { setout << render_const_value(out, etype, *v_iter, true); if (++v_iter != val.end()) { @@ -2806,6 +3505,9 @@ void t_cocoa_generator::print_const_value(ostream& out, } setout << "]]"; out << setout.str(); + if (!defval) { + out << ";" << endl; + } } else { throw "compiler error: no const of type " + type->get_name(); } @@ -3040,7 +3742,9 @@ string t_cocoa_generator::declare_property(t_field* tfield) { * @param tfield The field to declare a property for */ string t_cocoa_generator::declare_property_isset(t_field* tfield) { - return "@property (assign, nonatomic) BOOL " + decapitalize(tfield->get_name()) + "IsSet;"; + // Begin-MS-Specific (originally, this was 'capitalize(tfield->get_name())', but that seemed buggy) + return "@property (assign, nonatomic) BOOL " + tfield->get_name() + "IsSet;"; + // End-MS-Specific } /** diff --git a/compiler/cpp/src/thrift/generate/t_generator.h b/compiler/cpp/src/thrift/generate/t_generator.h index fc3f3232190..13cc33d1976 100644 --- a/compiler/cpp/src/thrift/generate/t_generator.h +++ b/compiler/cpp/src/thrift/generate/t_generator.h @@ -178,7 +178,7 @@ class t_generator { int indent_count() { return indent_; } void indent_validate( int expected, const char * func_name) { - if (indent_ != expected) { + if (indent_ != expected) { pverbose("Wrong indent count in %s: difference = %i \n", func_name, (expected - indent_)); } } @@ -201,6 +201,13 @@ class t_generator { */ std::ostream& indent(std::ostream& os) { return os << indent(); } + std::ostream& indent(std::ostream& os, size_t times) { + for (size_t i = 0; i < times; ++i) { + os << indent_str(); + } + return os; + } + /** * Capitalization helpers */ diff --git a/compiler/cpp/src/thrift/parse/t_base_type.cc b/compiler/cpp/src/thrift/parse/t_base_type.cc new file mode 100644 index 00000000000..38f69fe29e5 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_base_type.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "t_base_type.h" diff --git a/compiler/cpp/src/thrift/parse/t_doc.h b/compiler/cpp/src/thrift/parse/t_doc.h index 7bcb8f5e4b9..79ed52413c7 100644 --- a/compiler/cpp/src/thrift/parse/t_doc.h +++ b/compiler/cpp/src/thrift/parse/t_doc.h @@ -45,7 +45,7 @@ class t_doc { const std::string& get_doc() const { return doc_; } - bool has_doc() { return has_doc_; } + bool has_doc() const { return has_doc_; } private: std::string doc_; diff --git a/compiler/cpp/src/thrift/parse/t_enum.cc b/compiler/cpp/src/thrift/parse/t_enum.cc new file mode 100644 index 00000000000..fdee6635f51 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_enum.cc @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_enum.h" + diff --git a/compiler/cpp/src/thrift/parse/t_field.cc b/compiler/cpp/src/thrift/parse/t_field.cc new file mode 100644 index 00000000000..d8e4557695c --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_field.cc @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_field.h" + +#include +#include + +namespace { + +bool ci_find(const std::string& haystack, const std::string& needle) { + auto it = std::search( + haystack.begin(), + haystack.end(), + needle.begin(), + needle.end(), + [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }); + + return it != haystack.end(); +} + +} + +bool t_field::is_redacted() const { + return has_doc_text("@redacted") || has_anno("redacted") || has_anno("thrifty.redacted"); +} + +bool t_field::is_obfuscated() const { + return has_doc_text("@obfuscated") || has_anno("obfuscated") || has_anno("thrifty.obfuscated"); +} + +bool t_field::has_doc_text(const std::string& text) const { + bool result = false; + if (has_doc()) { + result = ci_find(get_doc(), text); + } + return result; +} + +bool t_field::has_anno(const std::string& anno) const { + auto it = annotations_.find(anno); + return it != annotations_.end(); +} diff --git a/compiler/cpp/src/thrift/parse/t_field.h b/compiler/cpp/src/thrift/parse/t_field.h index c5f1f800c73..bbdf4eed71f 100644 --- a/compiler/cpp/src/thrift/parse/t_field.h +++ b/compiler/cpp/src/thrift/parse/t_field.h @@ -24,6 +24,7 @@ #include #include +#include "thrift/parse/t_const_value.h" #include "thrift/parse/t_doc.h" #include "thrift/parse/t_type.h" @@ -109,6 +110,37 @@ class t_field : public t_doc { void set_reference(bool reference) { reference_ = reference; } + /** + * True if this field's value is to be redacted when printed. + */ + bool is_redacted() const; + + /** + * True if this field's value is to be obfuscated when printed. + * + * Obfuscation typically preserves "some" information about the + * value, in contrast with redaction which preserves no information + * at all. + * + * For example, printing an obfuscated field might yield a hash + * of the value. The precise nature of obfuscation is left to + * individual code generators. + */ + bool is_obfuscated() const; + +private: + /** + * Performs a case-insensitive search of this field's documentation + * for the given @c text. + */ + bool has_doc_text(const std::string& text) const; + + /** + * Returns true if this field is annotated with the given annotation + * name, regardless of the annotation's value. + */ + bool has_anno(const std::string& anno) const; + private: t_type* type_; std::string name_; diff --git a/compiler/cpp/src/thrift/parse/t_list.cc b/compiler/cpp/src/thrift/parse/t_list.cc new file mode 100644 index 00000000000..8bb422b9e36 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_list.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "t_list.h" diff --git a/compiler/cpp/src/thrift/parse/t_map.cc b/compiler/cpp/src/thrift/parse/t_map.cc new file mode 100644 index 00000000000..a0c5a01d330 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_map.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_map.h" diff --git a/compiler/cpp/src/thrift/parse/t_service.cc b/compiler/cpp/src/thrift/parse/t_service.cc new file mode 100644 index 00000000000..79ed84016e3 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_service.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_service.h" diff --git a/compiler/cpp/src/thrift/parse/t_set.cc b/compiler/cpp/src/thrift/parse/t_set.cc new file mode 100644 index 00000000000..9803069dfd1 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_set.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_set.h" diff --git a/compiler/cpp/src/thrift/parse/t_struct.cc b/compiler/cpp/src/thrift/parse/t_struct.cc new file mode 100644 index 00000000000..c44ffe13fe8 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_struct.cc @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_struct.h" diff --git a/compiler/cpp/src/thrift/parse/t_type.h b/compiler/cpp/src/thrift/parse/t_type.h index 3a6d1e04484..4f36f8f2a4d 100644 --- a/compiler/cpp/src/thrift/parse/t_type.h +++ b/compiler/cpp/src/thrift/parse/t_type.h @@ -24,6 +24,7 @@ #include #include #include + #include "thrift/parse/t_doc.h" class t_program; diff --git a/compiler/cpp/src/thrift/parse/t_typedef.cc b/compiler/cpp/src/thrift/parse/t_typedef.cc index 99ffdb8bdc6..e8aa93ca815 100644 --- a/compiler/cpp/src/thrift/parse/t_typedef.cc +++ b/compiler/cpp/src/thrift/parse/t_typedef.cc @@ -20,6 +20,7 @@ #include "thrift/parse/t_typedef.h" #include "thrift/parse/t_program.h" +#include "thrift/parse/t_visitor.h" t_type* t_typedef::get_type() const { if (type_ == NULL) { diff --git a/compiler/cpp/src/thrift/parse/t_visitor.h b/compiler/cpp/src/thrift/parse/t_visitor.h new file mode 100644 index 00000000000..757d0d28295 --- /dev/null +++ b/compiler/cpp/src/thrift/parse/t_visitor.h @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_VISITOR_H +#define T_VISITOR_H + +#include + +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_base_type.h" +#include "thrift/parse/t_list.h" +#include "thrift/parse/t_set.h" +#include "thrift/parse/t_map.h" +#include "thrift/parse/t_enum.h" +#include "thrift/parse/t_struct.h" +#include "thrift/parse/t_service.h" +#include "thrift/parse/t_typedef.h" + +#include + +template +class t_visitor; + +template +T visit(t_type* type, t_visitor& visitor); + +template +class t_visitor { +public: + virtual ~t_visitor() = default; + + virtual T visit_void(t_base_type *) { + throw "visit_void: unimplemented"; + } + + virtual T visit_binary(t_base_type *) { + throw "visit_binary: unimplemented"; + } + + virtual T visit_string(t_base_type *) { + throw "visit_string: unimplemented"; + } + + virtual T visit_bool(t_base_type *) { + throw "visit_bool: unimplemented"; + } + + virtual T visit_i8(t_base_type *) { + throw "visit_i8: unimplemented"; + } + + virtual T visit_i16(t_base_type *) { + throw "visit_i16: unimplemented"; + } + + virtual T visit_i32(t_base_type *) { + throw "visit_i32: unimplemented"; + } + + virtual T visit_i64(t_base_type *) { + throw "visit_i64: unimplemented"; + } + + virtual T visit_double(t_base_type *) { + throw "visit_double: unimplemented"; + } + + virtual T visit_list(t_list *) { + throw "visit_list: unimplemented"; + } + + virtual T visit_set(t_set *) { + throw "visit_set: unimplemented"; + } + + virtual T visit_map(t_map *) { + throw "visit_map: unimplemented"; + } + + virtual T visit_struct(t_struct *) { + throw "visit_struct: unimplemented"; + } + + virtual T visit_enum(t_enum *) { + throw "visit_enum: unimplemented"; + } + + virtual T visit_service(t_service *) { + throw "visit_service: unimplemented"; + } + + virtual T visit_typedef(t_typedef *td) { + // As a convenience, we'll provide a default implementation for + // typedefs that forwards to their aliased type. + return visit(td->get_true_type(), *this); + } +}; + +template +class t_simple_visitor : public t_visitor { +public: + virtual ~t_simple_visitor() = default; + + virtual T visit_base(t_base_type*) { + throw "visit_base: unimplemented"; + } + + virtual T visit_void(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_binary(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_string(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_bool(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_i8(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_i16(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_i32(t_base_type *t) { + return visit_base(t); + } + + virtual T visit_i64(t_base_type *t) { + return visit_base(t); + } +}; + +template +T visit(t_type* type, t_visitor& visitor) { + if (type->is_base_type()) { + t_base_type* base_type = static_cast(type); + switch (base_type->get_base()) { + case t_base_type::TYPE_VOID: return visitor.visit_void(base_type); + case t_base_type::TYPE_BOOL: return visitor.visit_bool(base_type); + case t_base_type::TYPE_I8: return visitor.visit_i8(base_type); + case t_base_type::TYPE_I16: return visitor.visit_i16(base_type); + case t_base_type::TYPE_I32: return visitor.visit_i32(base_type); + case t_base_type::TYPE_I64: return visitor.visit_i64(base_type); + case t_base_type::TYPE_DOUBLE: return visitor.visit_double(base_type); + case t_base_type::TYPE_STRING: return base_type->is_binary() + ? visitor.visit_binary(base_type) + : visitor.visit_string(base_type); + default: + throw "Unexpected base value: " + boost::lexical_cast(base_type->get_base()); + } + } else if (type->is_list()) { + return visitor.visit_list(static_cast(type)); + } else if (type->is_set()) { + return visitor.visit_set(static_cast(type)); + } else if (type->is_map()) { + return visitor.visit_map(static_cast(type)); + } else if (type->is_enum()) { + return visitor.visit_enum(static_cast(type)); + } else if (type->is_struct()) { + return visitor.visit_struct(static_cast(type)); + } else if (type->is_typedef()) { + return visitor.visit_typedef(static_cast(type)); + } else if (type->is_service()) { + return visitor.visit_service(static_cast(type)); + } else { + throw std::string{"Unexpected type: "} + type->get_name(); + } +} + +#endif