BOOL checkMethodSignatureAgainstTypeEncodings()

in ComponentKit/Core/Action/CKAction.mm [360:442]


BOOL checkMethodSignatureAgainstTypeEncodings(SEL selector, Method method, const std::vector<const char *> &typeEncodings)
{
  if (selector == NULL) {
    return NO;
  }

  if (typeEncodings.size() + 3 < method_getNumberOfArguments(method)) {
    RCCFailAssert(@"Expected action method %@ to take less than %llu arguments, but it supports %llu", NSStringFromSelector(selector), (unsigned long long)typeEncodings.size(), (unsigned long long)method_getNumberOfArguments(method) - 3);
    return NO;
  }

  char *return_type = method_copyReturnType(method);
  if (return_type == NULL) {
    return NO;
  }
  const bool has_return_type = strcmp(return_type, "v") != 0; // "v" is void
  free(return_type);

  if (has_return_type) {
    RCCFailAssert(@"Component action methods should not have any return value. Any objects returned from this method will be leaked.");
    return NO;
  }

  // Skipping self, _cmd, and sender (the component).
  for (int i = 0; i + 3 < method_getNumberOfArguments(method) && i < typeEncodings.size(); i++) {
    char *cp_argType = method_copyArgumentType(method, i + 3); // freed later - DON'T early exit!
    char *methodEncoding = cp_argType; // a pointer we can move around
    const char *typeEncoding = typeEncodings[i];

    // Type Encoding: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

    // ref types get '^' prefixed to them. Since C++ would implicitly
    // use pass-by-ref or pass-by-value based the called function, we
    // treat a mismatch between ref & copy as valid.
    if (methodEncoding != NULL && *methodEncoding == '^') {
      methodEncoding++;
    }
    if (typeEncoding != NULL && *typeEncoding == '^') {
      typeEncoding++;
    }

    BOOL doEncodingsMatch = NO;
    if (methodEncoding == NULL || typeEncoding == NULL) {
      // nothing to compare
      doEncodingsMatch = YES;
    } else if (*methodEncoding == '{' && *typeEncoding == '{') {
      // types are structures. Due to an issue with c++ types not always being
      // encoded the same even thought they are basically the same, we only
      // compare the structure name. (see T23131874)
      const char *nameEnd = strchr(methodEncoding, '=');
      const size_t nameSize = nameEnd - methodEncoding;
      doEncodingsMatch =
      (nameEnd
       && strlen(typeEncoding) >= nameSize
       && strncmp(methodEncoding, typeEncoding, nameSize) == 0);
    } else {
      doEncodingsMatch = strcmp(methodEncoding, typeEncoding) == 0;
    }

    NSString *safe_methodEncoding = [NSString stringWithFormat:@"%s", methodEncoding];
    free(cp_argType);

    if (!doEncodingsMatch) {
      RCCFailAssert(@"Implementation of %@ does not match expected types.\nExpected type %s, got %@", NSStringFromSelector(selector), typeEncoding, safe_methodEncoding);
      return NO;
    }

    safe_methodEncoding = nil; // avoids -Wunused-variable
  }

  if (method_getNumberOfArguments(method) >= 3) {
    char *const unasfe_methodEncoding = method_copyArgumentType(method, 2);
    NSString *methodEncoding = [NSString stringWithFormat:@"%s", unasfe_methodEncoding ?: ""];
    free(unasfe_methodEncoding);

    if (methodEncoding != nil && [methodEncoding isEqualToString:@"@"] == NO) {
      RCCFailAssert(@"Sender of %@ is not an object.\nGot %@ instead. Please add the component as the first argument when sending an action", NSStringFromSelector(selector), methodEncoding);
      return NO;
    }
  }

  return YES;
}