Generator::Action Generator::inferAction()

in hphp/tools/type-info-gens/gen-type-scanners.cpp [1606:2142]


Generator::Action Generator::inferAction(const Object& object,
                                         bool conservative_everything) const {
  if (object.incomplete) {
    throw Exception{
      folly::sformat(
        "Trying to infer actions on object type '{}' at ({},{}) "
        "which is incomplete",
        object.name.name,
        object.key.object_id,
        object.key.compile_unit_id
      )
    };
  }

  Action action;

  if (conservative_everything) {
    action.conservative_all = true;
    action.conservative_all_bases = true;
    return action;
  }

  // White-listing and forbidden templates are determined by just checking the
  // name against explicit lists.
  if (HPHP::type_scan::detail::isIgnoredType(object.name.name)) {
    action.whitelisted = true;
    return action;
  }

  if (HPHP::type_scan::detail::isForbiddenTemplate(object.name.name)) {
    sanityCheckTemplateParams(object);
    action.forbidden_template = true;
    return action;
  }

  if (HPHP::type_scan::detail::isForcedConservativeTemplate(object.name.name)) {
    sanityCheckTemplateParams(object);
    action.conservative_all = true;
    action.conservative_all_bases = true;
    return action;
  }

  const auto find_member = [&](const std::string& field) {
    return findMemberHelper(field, object);
  };

  const auto find_base = [&](const Object& base) {
    return std::any_of(
      object.bases.begin(),
      object.bases.end(),
      [&](const Object::Base& b) { return &getObject(b.type) == &base; }
    );
  };

  for (const auto& fun : object.functions) {
    // Sanity check special member function. All the functions should take a
    // const pointer to the contained object type as the first parameter (the
    // this pointer), and a non-const reference to HPHP::type_scan::Scanner as
    // the second (and nothing else). The return type should be void.
    auto verify_func = [&](const Object::Function& func) {
      if (func.kind != Object::Function::Kind::k_member) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "which is not a non-static, non-virtual member",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name
          )
        };
      }

      if (!func.ret_type.isVoid()) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "which does not have a void return type",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name
          )
        };
      }

      if (func.arg_types.size() != 2) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "which does not take exactly two parameter ({})",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            func.arg_types.size()
          )
        };
      }

      const auto& this_arg = func.arg_types[0];
      const auto* this_ptr_arg = this_arg.asPtr();
      if (!this_ptr_arg) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose first parameter isn't a pointer type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            this_arg.toString()
          )
        };
      }

      const auto* this_const_arg = this_ptr_arg->pointee.asConst();
      if (!this_const_arg) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose first parameter isn't a const pointer type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            this_arg.toString()
          )
        };
      }

      const auto* this_obj_arg = this_const_arg->modified.asObject();
      if (!this_obj_arg) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose first parameter isn't a pointer type to object type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            this_arg.toString()
          )
        };
      }

      if (&getObject(*this_obj_arg) != &object) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose first parameter isn't a valid this pointer '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            this_arg.toString()
          )
        };
      }

      const auto& scanner_arg = func.arg_types[1];
      const auto* scanner_ref_arg = scanner_arg.asRef();
      if (!scanner_ref_arg) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose second parameter isn't a reference '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            scanner_arg.toString()
          )
        };
      }

      const auto* scanner_obj_arg = scanner_ref_arg->referenced.asObject();
      if (!scanner_obj_arg) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose second parameter isn't a reference to object-type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            scanner_arg.toString()
          )
        };
      }

      const auto& scanner_obj = getObject(*scanner_obj_arg);
      if (scanner_obj.name.name != s_scanner_name) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains scanner func '{}' "
            "whose second parameter isn't a reference to "
            "{} '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            func.name,
            std::string{s_scanner_name},
            scanner_arg.toString()
          )
        };
      }
    };

    // Custom scanner for particular field.
    auto custom_field = splitFieldName(
      fun.name,
      HPHP::type_scan::detail::kCustomFieldName
    );
    if (!custom_field.empty()) {
      verify_func(fun);

      if (!find_member(custom_field)) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains custom field marker "
            "referring to unknown non-static field '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            custom_field
          )
        };
      }

      action.custom_fields.emplace(
        std::move(custom_field),
        fun.linkage_name
      );
    }

    // Custom scanner for entire object.
    if (fun.name == HPHP::type_scan::detail::kCustomName) {
      verify_func(fun);
      action.custom_all = fun.linkage_name;
      continue;
    }

    // Custom scanner for base classes.
    if (fun.name == HPHP::type_scan::detail::kCustomBasesScannerName) {
      verify_func(fun);
      action.custom_bases_scanner = fun.linkage_name;
      continue;
    }
  }

  for (const auto& member : object.members) {
    // All special member markers should be static, so ignore anything that's
    // not.
    if (member.offset) continue;

    // Ignore a field.
    auto ignore_field = splitFieldName(
      member.name,
      HPHP::type_scan::detail::kIgnoreFieldName
    );
    if (!ignore_field.empty()) {
      if (!find_member(ignore_field)) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains ignore field marker "
            "referring to unknown non-static field '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            ignore_field
          )
        };
      }

      action.ignore_fields.emplace(std::move(ignore_field));
      continue;
    }

    // Scan field conservatively.
    auto conservative_field = splitFieldName(
      member.name,
      HPHP::type_scan::detail::kConservativeFieldName
    );
    if (!conservative_field.empty()) {
      if (!find_member(conservative_field)) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains conservative field marker "
            "referring to unknown non-static field '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            conservative_field
          )
        };
      }

      action.conservative_fields.emplace(std::move(conservative_field));
      continue;
    }

    // Marks flexible array field. There can only be one of these per object
    // type.
    auto flexible_array_field = splitFieldName(
      member.name,
      HPHP::type_scan::detail::kFlexibleArrayFieldName
    );
    if (!flexible_array_field.empty()) {
      if (!action.flexible_array_field.empty()) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains more than one flexible "
            "array field marker",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id
          )
        };
      }

      if (!find_member(flexible_array_field)) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains flexible array field marker "
            "referring to unknown non-static field '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            flexible_array_field
          )
        };
      }

      action.flexible_array_field = std::move(flexible_array_field);
    }

    // Ignore entire object.
    if (member.name == HPHP::type_scan::detail::kIgnoreName) {
      action.ignore_all = true;
      continue;
    }

    // Conservative scan entire object.
    if (member.name == HPHP::type_scan::detail::kConservativeName) {
      action.conservative_all = true;
      continue;
    }

    // Ignore specific base.
    if (member.name == HPHP::type_scan::detail::kIgnoreBaseName) {
      const auto* ignore_type = stripModifiers(member.type).asObject();
      if (!ignore_type) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains an ignore base marker "
            "for a non-object type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            member.type.toString()
          )
        };
      }

      const auto& ignore = getObject(*ignore_type);
      // This is a variadic template, so sanity check it.
      sanityCheckTemplateParams(ignore);
      for (const auto& param : ignore.template_params) {
        const auto* ignored_type = stripModifiers(param.type).asObject();
        if (!ignored_type) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains an ignore base marker "
              "instantiated on non-object type '{}'",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              param.type.toString()
            )
          };
        }

        const auto& ignored = getObject(*ignored_type);
        if (!find_base(ignored)) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains an ignore base marker "
              "instantiated on object-type '{}' which isn't a base class",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              ignored.name.name
            )
          };
        }
        action.ignored_bases.emplace(&ignored);
      }
      continue;
    }

    // Don't complain about a particular base class violating a forbidden
    // template check.
    if (member.name == HPHP::type_scan::detail::kSilenceForbiddenBaseName) {
      const auto* silence_type = stripModifiers(member.type).asObject();
      if (!silence_type) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains a silence base marker "
            "for a non-object type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            member.type.toString()
          )
        };
      }

      const auto& silence = getObject(*silence_type);
      // This is a variadic template, so sanity check it.
      sanityCheckTemplateParams(silence);
      for (const auto& param : silence.template_params) {
        const auto* silenced_type = stripModifiers(param.type).asObject();
        if (!silenced_type) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains a silence base marker "
              "instantiated on non-object type '{}'",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              param.type.toString()
            )
          };
        }

        const auto& silenced = getObject(*silenced_type);
        if (!find_base(silenced)) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains a silence base marker "
              "instantiated on object-type '{}' which isn't a base class",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              silenced.name.name
            )
          };
        }
        action.silenced_bases.emplace(&silenced);
      }
      continue;
    }

    // List of base classes to apply the custom bases scan to.
    if (action.custom_bases_scanner &&
        member.name == HPHP::type_scan::detail::kCustomBasesName) {
      const auto* custom_list_type = stripModifiers(member.type).asObject();
      if (!custom_list_type) {
        throw Exception{
          folly::sformat(
            "Object type '{}' at ({},{}) contains a custom base marker "
            "for a non-object type '{}'",
            object.name.name,
            object.key.object_id,
            object.key.compile_unit_id,
            member.type.toString()
          )
        };
      }

      const auto& custom_list = getObject(*custom_list_type);
      // This is a variadic template, so sanity check it.
      sanityCheckTemplateParams(custom_list);
      for (const auto& param : custom_list.template_params) {
        const auto* custom_type = stripModifiers(param.type).asObject();
        if (!custom_type) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains a custom base marker "
              "instantiated on non-object type '{}'",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              param.type.toString()
            )
          };
        }

        const auto& custom = getObject(*custom_type);
        if (!find_base(custom)) {
          throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains a custom base marker "
              "instantiated on object-type '{}' which isn't a base class",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              custom.name.name
            )
          };
        }
        action.custom_bases.emplace(&custom);
      }
      continue;
    }

    // If there's a custom scanner for the entire object, list of types to guard
    // on.
    if (action.custom_all &&
        member.name == HPHP::type_scan::detail::kCustomGuardName) {
      const auto* guard_type = stripModifiers(member.type).asObject();
      if (!guard_type) {
        throw Exception{
            folly::sformat(
              "Object type '{}' at ({},{}) contains a custom guard marker "
              "instantiated on non-object type '{}'",
              object.name.name,
              object.key.object_id,
              object.key.compile_unit_id,
              member.type.toString()
            )
        };
      }

      const auto& guard = getObject(*guard_type);
      // This is a variadic template, so sanity check it.
      sanityCheckTemplateParams(guard);
      for (const auto& param : guard.template_params) {
        action.custom_guards.emplace(&param.type);
      }
      continue;
    }
  }

  return action;
}