|
| 1 | +// Protocol Buffers - Google's data interchange format |
| 2 | +// Copyright 2025 Google Inc. All rights reserved. |
| 3 | +// |
| 4 | +// Use of this source code is governed by a BSD-style |
| 5 | +// license that can be found in the LICENSE file or at |
| 6 | +// https://developers.google.com/open-source/licenses/bsd |
| 7 | + |
| 8 | +#import <stdatomic.h> |
| 9 | + |
| 10 | +#import "GPBDescriptor_PackagePrivate.h" |
| 11 | +#import "GPBExtensionRegistry.h" |
| 12 | +#import "GPBMessage.h" |
| 13 | +#import "GPBProtocolBuffers_RuntimeSupport.h" |
| 14 | +#import "GPBRootObject_PackagePrivate.h" |
| 15 | +#import "GPBTestUtilities.h" |
| 16 | + |
| 17 | +// This is a fake version, so the ptr check will fail. |
| 18 | +static const int32_t FAKE_GOOGLE_PROTOBUF_OBJC_EXPECTED_GENCODE_VERSION_40311 = 40311; |
| 19 | + |
| 20 | +@interface MessageBadVersionFormatTest : GPBTestCase |
| 21 | +@end |
| 22 | + |
| 23 | +// clang-format off |
| 24 | +// NOLINTBEGIN |
| 25 | + |
| 26 | +// ------------------------------------------------------------------------------------------------- |
| 27 | +// |
| 28 | +// This is extracted from generated code with the 40311 format but then edited so the pass in |
| 29 | +// unknown values for the version support, the original proto was as follows: |
| 30 | +// |
| 31 | +// syntax = "proto2"; |
| 32 | +// |
| 33 | +// enum EnumBadVersion { |
| 34 | +// FOO = 0; |
| 35 | +// BAR = 1; |
| 36 | +// } |
| 37 | +// |
| 38 | +// message MessageBadVersion { |
| 39 | +// optional EnumBadVersion value = 1; |
| 40 | +// extensions 100 to max; |
| 41 | +// } |
| 42 | +// |
| 43 | +// extend MessageBadVersion { |
| 44 | +// optional MessageBadVersion other_m = 100; |
| 45 | +// optional EnumBadVersion other_e = 101; |
| 46 | +// } |
| 47 | +// |
| 48 | +// ------------------------------------------------------------------------------------------------- |
| 49 | + |
| 50 | +NS_ASSUME_NONNULL_BEGIN |
| 51 | + |
| 52 | +typedef GPB_ENUM(EnumBadVersion) { |
| 53 | + EnumBadVersion_Foo = 0, |
| 54 | + EnumBadVersion_Bar = 1, |
| 55 | +}; |
| 56 | + |
| 57 | +GPBEnumDescriptor *EnumBadVersion_EnumDescriptor(void); |
| 58 | + |
| 59 | +BOOL EnumBadVersion_IsValidValue(int32_t value); |
| 60 | + |
| 61 | +GPB_FINAL @interface TestBadVersionRoot : GPBRootObject |
| 62 | +@end |
| 63 | + |
| 64 | +@interface TestBadVersionRoot (DynamicMethods) |
| 65 | ++ (GPBExtensionDescriptor *)otherM; |
| 66 | ++ (GPBExtensionDescriptor *)otherE; |
| 67 | +@end |
| 68 | + |
| 69 | +typedef GPB_ENUM(MessageBadVersion_FieldNumber) { |
| 70 | + MessageBadVersion_FieldNumber_Value = 1, |
| 71 | +}; |
| 72 | + |
| 73 | +GPB_FINAL @interface MessageBadVersion : GPBMessage |
| 74 | + |
| 75 | +@property(nonatomic, readwrite) EnumBadVersion value; |
| 76 | +@property(nonatomic, readwrite) BOOL hasValue; |
| 77 | + |
| 78 | +@end |
| 79 | + |
| 80 | +NS_ASSUME_NONNULL_END |
| 81 | + |
| 82 | +#pragma clang diagnostic push |
| 83 | +#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| 84 | +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" |
| 85 | + |
| 86 | +GPBObjCClassDeclaration(MessageBadVersion); |
| 87 | + |
| 88 | +@implementation TestBadVersionRoot |
| 89 | + |
| 90 | ++ (GPBExtensionRegistry*)extensionRegistry { |
| 91 | + // This is called by +initialize so there is no need to worry |
| 92 | + // about thread safety and initialization of registry. |
| 93 | + static GPBExtensionRegistry* registry = nil; |
| 94 | + if (!registry) { |
| 95 | + registry = [[GPBExtensionRegistry alloc] init]; |
| 96 | + static GPBExtensionDescription descriptions[] = { |
| 97 | + { |
| 98 | + .defaultValue.valueMessage = nil, |
| 99 | + .singletonName = GPBStringifySymbol(TestBadVersionRoot) "_otherM", |
| 100 | + .extendedClass.clazz = GPBObjCClass(MessageBadVersion), |
| 101 | + .messageOrGroupClass.clazz = GPBObjCClass(MessageBadVersion), |
| 102 | + .enumDescriptorFunc = NULL, |
| 103 | + .fieldNumber = 100, |
| 104 | + .dataType = GPBDataTypeMessage, |
| 105 | + .options = GPBExtensionNone, |
| 106 | + }, |
| 107 | + { |
| 108 | + .defaultValue.valueEnum = EnumBadVersion_Foo, |
| 109 | + .singletonName = GPBStringifySymbol(TestBadVersionRoot) "_otherE", |
| 110 | + .extendedClass.clazz = GPBObjCClass(MessageBadVersion), |
| 111 | + .messageOrGroupClass.clazz = Nil, |
| 112 | + .enumDescriptorFunc = EnumBadVersion_EnumDescriptor, |
| 113 | + .fieldNumber = 101, |
| 114 | + .dataType = GPBDataTypeEnum, |
| 115 | + .options = GPBExtensionNone, |
| 116 | + }, |
| 117 | + }; |
| 118 | + for (size_t i = 0; i < sizeof(descriptions) / sizeof(descriptions[0]); ++i) { |
| 119 | + GPBExtensionDescriptor *extension = |
| 120 | + [[GPBExtensionDescriptor alloc] initWithExtensionDescription:&descriptions[i] |
| 121 | + runtimeSupport:&FAKE_GOOGLE_PROTOBUF_OBJC_EXPECTED_GENCODE_VERSION_40311]; |
| 122 | + [registry addExtension:extension]; |
| 123 | + [self globallyRegisterExtension:extension]; |
| 124 | + [extension release]; |
| 125 | + } |
| 126 | + // None of the imports (direct or indirect) defined extensions, so no need to add |
| 127 | + // them to this registry. |
| 128 | + } |
| 129 | + return registry; |
| 130 | +} |
| 131 | + |
| 132 | +@end |
| 133 | + |
| 134 | +static GPBFilePackageAndPrefix TestBadVersionRoot_FileDescription = { |
| 135 | + .package = NULL, |
| 136 | + .prefix = NULL |
| 137 | +}; |
| 138 | + |
| 139 | +GPBEnumDescriptor *EnumBadVersion_EnumDescriptor(void) { |
| 140 | + static _Atomic(GPBEnumDescriptor*) descriptor = nil; |
| 141 | + if (!descriptor) { |
| 142 | + static const char *valueNames = |
| 143 | + "Foo\000Bar\000"; |
| 144 | + static const int32_t values[] = { |
| 145 | + EnumBadVersion_Foo, |
| 146 | + EnumBadVersion_Bar, |
| 147 | + }; |
| 148 | + GPBEnumDescriptor *worker = |
| 149 | + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(EnumBadVersion) |
| 150 | + runtimeSupport:&FAKE_GOOGLE_PROTOBUF_OBJC_EXPECTED_GENCODE_VERSION_40311 |
| 151 | + valueNames:valueNames |
| 152 | + values:values |
| 153 | + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) |
| 154 | + enumVerifier:EnumBadVersion_IsValidValue |
| 155 | + flags:GPBEnumDescriptorInitializationFlag_IsClosed]; |
| 156 | + GPBEnumDescriptor *expected = nil; |
| 157 | + if (!atomic_compare_exchange_strong(&descriptor, &expected, worker)) { |
| 158 | + [worker release]; |
| 159 | + } |
| 160 | + } |
| 161 | + return descriptor; |
| 162 | +} |
| 163 | + |
| 164 | +BOOL EnumBadVersion_IsValidValue(int32_t value__) { |
| 165 | + switch (value__) { |
| 166 | + case EnumBadVersion_Foo: |
| 167 | + case EnumBadVersion_Bar: |
| 168 | + return YES; |
| 169 | + default: |
| 170 | + return NO; |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +#pragma mark - MessageBadVersion |
| 175 | + |
| 176 | +@implementation MessageBadVersion |
| 177 | + |
| 178 | +@dynamic hasValue, value; |
| 179 | + |
| 180 | +typedef struct MessageBadVersion__storage_ { |
| 181 | + uint32_t _has_storage_[1]; |
| 182 | + EnumBadVersion value; |
| 183 | +} MessageBadVersion__storage_; |
| 184 | + |
| 185 | ++ (GPBDescriptor *)descriptor { |
| 186 | + static GPBDescriptor *descriptor = nil; |
| 187 | + if (!descriptor) { |
| 188 | + static GPBMessageFieldDescription fields[] = { |
| 189 | + { |
| 190 | + .name = "value", |
| 191 | + .dataTypeSpecific.enumDescFunc = EnumBadVersion_EnumDescriptor, |
| 192 | + .number = MessageBadVersion_FieldNumber_Value, |
| 193 | + .hasIndex = 0, |
| 194 | + .offset = (uint32_t)offsetof(MessageBadVersion__storage_, value), |
| 195 | + .flags = GPBFieldNone, |
| 196 | + .dataType = GPBDataTypeEnum, |
| 197 | + }, |
| 198 | + }; |
| 199 | + GPBDescriptor *localDescriptor = |
| 200 | + [GPBDescriptor allocDescriptorForClass:GPBObjCClass(MessageBadVersion) |
| 201 | + messageName:@"MessageBadVersion" |
| 202 | + runtimeSupport:&FAKE_GOOGLE_PROTOBUF_OBJC_EXPECTED_GENCODE_VERSION_40311 |
| 203 | + fileDescription:&TestBadVersionRoot_FileDescription |
| 204 | + fields:fields |
| 205 | + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) |
| 206 | + storageSize:sizeof(MessageBadVersion__storage_) |
| 207 | + flags:GPBDescriptorInitializationFlag_None]; |
| 208 | + static const GPBExtensionRange ranges[] = { |
| 209 | + { .start = 100, .end = 536870912 }, |
| 210 | + }; |
| 211 | + [localDescriptor setupExtensionRanges:ranges |
| 212 | + count:(uint32_t)(sizeof(ranges) / sizeof(GPBExtensionRange))]; |
| 213 | + #if defined(DEBUG) && DEBUG |
| 214 | + NSAssert(descriptor == nil, @"Startup recursed!"); |
| 215 | + #endif // DEBUG |
| 216 | + descriptor = localDescriptor; |
| 217 | + } |
| 218 | + return descriptor; |
| 219 | +} |
| 220 | + |
| 221 | +@end |
| 222 | + |
| 223 | +#pragma clang diagnostic pop |
| 224 | + |
| 225 | +// NOLINTEND |
| 226 | +// clang-format on |
| 227 | + |
| 228 | +// ------------------------------------------------------------------------------------------------- |
| 229 | + |
| 230 | +@implementation MessageBadVersionFormatTest |
| 231 | + |
| 232 | +- (void)testMessageBadVersionFormat { |
| 233 | + // Calling each one should try to start it up and result in a throw for an unknown version marker. |
| 234 | + // Mostly this shouldn't happen as the symbol should be coming out of the runtime library so |
| 235 | + // things should result in a link error before getting to the runtime check; this is just an added |
| 236 | + // safety check. |
| 237 | + XCTAssertThrowsSpecificNamed(EnumBadVersion_EnumDescriptor(), NSException, |
| 238 | + NSInternalInconsistencyException); |
| 239 | + |
| 240 | + XCTAssertThrowsSpecificNamed([TestBadVersionRoot otherM], NSException, |
| 241 | + NSInternalInconsistencyException); |
| 242 | + |
| 243 | + XCTAssertThrowsSpecificNamed([MessageBadVersion class], NSException, |
| 244 | + NSInternalInconsistencyException); |
| 245 | +} |
| 246 | + |
| 247 | +@end |
0 commit comments