void Class::setProperties()

in hphp/runtime/vm/class.cpp [2841:3219]


void Class::setProperties() {
  int numInaccessible = 0;
  PropMap::Builder curPropMap;
  SPropMap::Builder curSPropMap;
  m_hasDeepInitProps = false;
  std::vector<uint16_t> slotIndex;

  if (m_parent.get() != nullptr) {
    // m_hasDeepInitProps indicates if there are properties that require
    // deep initialization. Note there are cases where m_hasDeepInitProps is
    // true but none of the properties require deep initialization; this can
    // happen if a derived class redeclares a public or protected property
    // from an ancestor class. We still get correct behavior in these cases,
    // so it works out okay.
    m_hasDeepInitProps = m_parent->m_hasDeepInitProps;
    for (auto const& parentProp : m_parent->declProperties()) {
      // Copy parent's declared property.  Protected properties may be
      // weakened to public below, but otherwise, the parent's properties
      // will stay the same for this class.
      Prop prop;
      prop.preProp             = parentProp.preProp;
      prop.cls                 = parentProp.cls;
      prop.baseCls             = parentProp.baseCls;
      prop.mangledName         = parentProp.mangledName;
      prop.attrs               = parentProp.attrs | AttrNoBadRedeclare;
      prop.typeConstraint      = parentProp.typeConstraint;
      prop.ubs                 = parentProp.ubs;
      prop.name                = parentProp.name;
      prop.repoAuthType        = parentProp.repoAuthType;

      if (!(parentProp.attrs & AttrPrivate)) {
        curPropMap.add(prop.name, prop);
      } else {
        ++numInaccessible;
        curPropMap.addUnnamed(prop);
      }
    }
    m_declPropInit = m_parent->m_declPropInit;
    m_needsPropInitialCheck = m_parent->m_needsPropInitialCheck;
    auto& parentSlotIndex = m_parent->m_slotIndex;
    slotIndex.insert(slotIndex.end(),
                     parentSlotIndex.begin(), parentSlotIndex.end());
    for (auto const& parentProp : m_parent->staticProperties()) {
      if ((parentProp.attrs & AttrPrivate) &&
          !(parentProp.attrs & AttrLSB)) continue;

      // Alias parent's static property.
      SProp sProp;
      sProp.preProp        = parentProp.preProp;
      sProp.name           = parentProp.name;
      sProp.attrs          = parentProp.attrs | AttrNoBadRedeclare;
      sProp.typeConstraint = parentProp.typeConstraint;
      sProp.ubs            = parentProp.ubs;
      sProp.cls            = parentProp.cls;
      sProp.repoAuthType   = parentProp.repoAuthType;
      tvWriteUninit(sProp.val);
      curSPropMap.add(sProp.name, sProp);
    }
  }

  Slot traitIdx = m_preClass->numProperties();
  if (m_preClass->attrs() & AttrNoExpandTrait) {
    while (traitIdx &&
           (m_preClass->properties()[traitIdx - 1].attrs() & AttrTrait)) {
      traitIdx--;
    }
  }

  Slot serializationIdx = 0;
  std::vector<bool> serializationVisited(curPropMap.size(), false);
  Slot staticSerializationIdx = 0;
  std::vector<bool> staticSerializationVisited(curSPropMap.size(), false);

  static_assert(AttrPublic < AttrProtected && AttrProtected < AttrPrivate, "");
  auto const firstOwnPropSlot = curPropMap.size();

  for (Slot slot = 0; slot < traitIdx; ++slot) {
    const PreClass::Prop* preProp = &m_preClass->properties()[slot];

    if (!(preProp->attrs() & AttrStatic)) {
      // Overlay/append this class's protected and public properties onto/to
      // those of the parent, and append this class's private properties.
      // Append order doesn't matter here (unlike in setMethods()).
      // Prohibit static-->non-static redeclaration.
      SPropMap::Builder::iterator it5 = curSPropMap.find(preProp->name());
      if (it5 != curSPropMap.end()) {
        raise_error("Cannot redeclare static %s::$%s as non-static %s::$%s",
                    curSPropMap[it5->second].cls->name()->data(),
                    preProp->name()->data(), m_preClass->name()->data(),
                    preProp->name()->data());
      }
      // Get parent's equivalent property, if one exists.
      const Prop* parentProp = nullptr;
      if (m_parent.get() != nullptr) {
        Slot id = m_parent->m_declProperties.findIndex(preProp->name());
        if (id != kInvalidSlot) {
          parentProp = &m_parent->m_declProperties[id];
        }
      }
      // Prohibit strengthening.
      if (parentProp
          && (preProp->attrs() & VisibilityAttrs)
             > (parentProp->attrs & VisibilityAttrs)) {
        raise_error(
          "Access level to %s::$%s must be %s (as in class %s) or weaker",
          m_preClass->name()->data(), preProp->name()->data(),
          attrToVisibilityStr(parentProp->attrs),
          m_parent->name()->data());
      }
      if (preProp->attrs() & AttrDeepInit) {
        m_hasDeepInitProps = true;
      }

      auto addNewProp = [&] {
        Prop prop;
        initProp(prop, preProp);

        curPropMap.add(preProp->name(), prop);
        curPropMap[serializationIdx++].serializationIdx = curPropMap.size() - 1;
        serializationVisited.push_back(true);
        m_declPropInit.push_back(preProp->val());
        slotIndex.push_back(slotIndex.size());
      };

      auto const lateInitCheck = [&] (const Class::Prop& prop) {
        if ((prop.attrs ^ preProp->attrs()) & AttrLateInit) {
          raise_error(
            "Property %s::$%s must %sbe <<__LateInit>> (as in class %s)",
            m_preClass->name()->data(),
            preProp->name()->data(),
            prop.attrs & AttrLateInit ? "" : "not ",
            m_parent->name()->data()
          );
        }
      };

      auto const redeclareProp = [&] (const Slot slot) {
        // For duplicate property name, add the slot # defined in curPropMap,
        // and mark the property visited.
        assertx(serializationVisited.size() > slot);
        if (!serializationVisited[slot]) {
          curPropMap[serializationIdx++].serializationIdx = slot;
          serializationVisited[slot] = true;
        }

        auto& prop = curPropMap[slot];
        assertx((preProp->attrs() & VisibilityAttrs)
                <= (prop.attrs & VisibilityAttrs));
        assertx(!(prop.attrs & AttrNoImplicitNullable) ||
                (preProp->attrs() & AttrNoImplicitNullable));
        assertx(prop.attrs & AttrNoBadRedeclare);

        if ((preProp->attrs() & AttrIsConst) != (prop.attrs & AttrIsConst)) {
          raise_error("Cannot redeclare property %s of class %s with different "
                      "constness in class %s", preProp->name()->data(),
                      m_parent->name()->data(), m_preClass->name()->data());
        }

        lateInitCheck(prop);

        prop.preProp = preProp;
        prop.cls = this;
        if ((preProp->attrs() & VisibilityAttrs)
            < (prop.attrs & VisibilityAttrs)) {
          assertx((prop.attrs & VisibilityAttrs) == AttrProtected);
          assertx((preProp->attrs() & VisibilityAttrs) == AttrPublic);
          // Weaken protected property to public.
          prop.mangledName = preProp->mangledName();
          prop.attrs = Attr(prop.attrs ^ (AttrProtected|AttrPublic));
        }

        auto const& tc = preProp->typeConstraint();
        if (RuntimeOption::EvalCheckPropTypeHints > 0 &&
            !(preProp->attrs() & AttrNoBadRedeclare) &&
            (tc.maybeInequivalentForProp(prop.typeConstraint) ||
             !preProp->upperBounds().empty() ||
             !prop.ubs.empty())) {
          // If this property isn't obviously not redeclaring a property in
          // the parent, we need to check that when we initialize the class.
          prop.attrs = Attr(prop.attrs & ~AttrNoBadRedeclare);
          m_selfMaybeRedefsPropTy = true;
          m_maybeRedefsPropTy = true;
        }
        prop.typeConstraint = tc;

        prop.ubs = preProp->upperBounds();
        prop.repoAuthType = preProp->repoAuthType();

        if (preProp->attrs() & AttrNoImplicitNullable) {
          prop.attrs |= AttrNoImplicitNullable;
        }
        attrSetter(prop.attrs,
                   preProp->attrs() & AttrSystemInitialValue,
                   AttrSystemInitialValue);
        attrSetter(prop.attrs,
                   preProp->attrs() & AttrInitialSatisfiesTC,
                   AttrInitialSatisfiesTC);
        attrSetter(prop.attrs,
                   preProp->attrs() & AttrPersistent,
                   AttrPersistent);

        checkPrePropVal(prop, preProp);
        auto index = slotIndex[slot];

        tvCopy(preProp->val(), m_declPropInit[index].val);
        copyDeepInitAttr(preProp, &prop);
      };

      switch (preProp->attrs() & VisibilityAttrs) {
        case AttrPrivate: {
          addNewProp();
          break;
        }
        case AttrProtected: {
          // Check whether a superclass has already declared this protected
          // property.
          PropMap::Builder::iterator it2 = curPropMap.find(preProp->name());
          if (it2 == curPropMap.end()) {
            addNewProp();
          } else {
            redeclareProp(it2->second);
          }
          break;
        }
        case AttrPublic: {
          // Check whether a superclass has already declared this as a
          // protected/public property.
          auto it2 = curPropMap.find(preProp->name());
          if (it2 == curPropMap.end()) {
            addNewProp();
          } else {
            redeclareProp(it2->second);
          }
          break;
        }
        default: always_assert(false);
      }
    } else { // Static property.
      // Prohibit non-static-->static redeclaration.
      auto const it2 = curPropMap.find(preProp->name());
      if (it2 != curPropMap.end()) {
        auto& prop = curPropMap[it2->second];
        raise_error("Cannot redeclare non-static %s::$%s as static %s::$%s",
                    prop.cls->name()->data(),
                    preProp->name()->data(),
                    m_preClass->name()->data(),
                    preProp->name()->data());
      }
      // Get parent's equivalent property, if one exists.
      auto const it3 = curSPropMap.find(preProp->name());
      Slot sPropInd = kInvalidSlot;
      // Prohibit strengthening.
      if (it3 != curSPropMap.end()) {
        const SProp& parentSProp = curSPropMap[it3->second];
        if ((preProp->attrs() & VisibilityAttrs)
            > (parentSProp.attrs & VisibilityAttrs)) {
          raise_error(
            "Access level to %s::$%s must be %s (as in class %s) or weaker",
            m_preClass->name()->data(), preProp->name()->data(),
            attrToVisibilityStr(parentSProp.attrs),
            m_parent->name()->data());
        }
        // Prohibit overlaying LSB static properties.
        if (parentSProp.attrs & AttrLSB) {
          raise_error(
            "Cannot redeclare LSB static %s::%s as %s::%s",
            parentSProp.cls->name()->data(),
            preProp->name()->data(),
            m_preClass->name()->data(),
            preProp->name()->data());
        }
        sPropInd = it3->second;
      }

      if (sPropInd == kInvalidSlot) {
        SProp sProp;
        initProp(sProp, preProp);
        curSPropMap.add(sProp.name, sProp);

        curSPropMap[staticSerializationIdx++].serializationIdx =
          curSPropMap.size() - 1;
        staticSerializationVisited.push_back(true);
      } else {
        // Overlay ancestor's property.
        auto& sProp = curSPropMap[sPropInd];
        initProp(sProp, preProp);

        assertx(staticSerializationVisited.size() > sPropInd);
        if (!staticSerializationVisited[sPropInd]) {
          staticSerializationVisited[sPropInd] = true;
          curSPropMap[staticSerializationIdx++].serializationIdx = sPropInd;
        }
      }
    }
  }

  importTraitProps(traitIdx, curPropMap, curSPropMap, slotIndex,
                   serializationIdx, serializationVisited,
                   staticSerializationIdx, staticSerializationVisited);

  auto const pastOwnPropSlot = curPropMap.size();
  sortOwnProps(curPropMap, firstOwnPropSlot, pastOwnPropSlot, slotIndex);
  assertx(slotIndex.size() == curPropMap.size());

  // set serialization index for parent properties at the end
  if (m_parent.get() != nullptr) {
    for (Slot i = 0; i < m_parent->declProperties().size(); ++i) {
      // For non-static properties, slot is always equal to parentSlot
      Slot slot = m_parent->declProperties()[i].serializationIdx;
      assertx(serializationVisited.size() > slot);
      if (!serializationVisited[slot]) {
        curPropMap[serializationIdx++].serializationIdx = slot;
      }
    }

    for (Slot i = 0; i < m_parent->staticProperties().size(); ++i) {
      Slot parentSlot = m_parent->staticProperties()[i].serializationIdx;
      auto const& prop = m_parent->staticProperties()[parentSlot];

      if ((prop.attrs & AttrPrivate) && !(prop.attrs & AttrLSB)) continue;

      auto it = curSPropMap.find(prop.name);
      assertx(it != curSPropMap.end());

      Slot slot = it->second;
      assertx(staticSerializationVisited.size() > slot);
      if (!staticSerializationVisited[slot]) {
        curSPropMap[staticSerializationIdx++].serializationIdx = slot;
      }
    }

    assertx(serializationIdx == curPropMap.size());
    assertx(staticSerializationIdx == curSPropMap.size());
  }

  // LSB static properties that were inherited must be initialized separately.
  for (Slot slot = 0; slot < curSPropMap.size(); ++slot) {
    auto& sProp = curSPropMap[slot];
    if ((sProp.attrs & AttrLSB) && sProp.cls != this) {
      auto const& prevSProps = sProp.cls->m_staticProperties;
      auto prevInd = prevSProps.findIndex(sProp.name);
      assertx(prevInd != kInvalidSlot);
      sProp.val = prevSProps[prevInd].val;
    }
  }

  m_declProperties.create(curPropMap);
  m_staticProperties.create(curSPropMap);

  std::vector<ObjectProps::quick_index> slotQuickIndex;
  slotQuickIndex.reserve(slotIndex.size());
  for (auto i : slotIndex) {
    slotQuickIndex.push_back(ObjectProps::quickIndex(i));
  }
  m_slotIndex = slotQuickIndex;

  setupSProps();

  m_declPropNumAccessible = m_declProperties.size() - numInaccessible;

  // To speed up ObjectData::release, we only iterate over property indices
  // up to the last countable property index. Here, "index" refers to the
  // position of the property in memory, and "slot" to its logical slot.
  uint16_t countablePropsEnd = 0;
  for (Slot slot = 0; slot < m_declProperties.size(); ++slot) {
    if (jit::irgen::propertyMayBeCountable(m_declProperties[slot])) {
      auto const index =
        safe_cast<uint16_t>(propSlotToIndex(slot) + uint16_t{1});
      countablePropsEnd = std::max(countablePropsEnd, index);
    }
  }

  assertx(m_declProperties.size() <= ObjectProps::max_index + 1);
  assertx(countablePropsEnd <= ObjectProps::max_index);

  m_countablePropsEnd = ObjectProps::quickIndex(countablePropsEnd);
  FTRACE(3, "numDeclProperties = {}\n", m_declProperties.size());
  FTRACE(3, "countablePropsEnd = {}\n", countablePropsEnd);
}