Skip to content

Commit 2638538

Browse files
swtaarrsptarjan
authored andcommitted
Properly invoke setter/getter for unset declared properties
If someone manually unsets a declared property, Zend invokes __get and __set (if present) for any future accesses to it. We were getting this right in the interpreter but not the jit, so I fixed it by skipping the propSpecialized case for operations that might hit this case. Closes facebook#684 Reviewed By: @jdelong Differential Revision: D1158063
1 parent c957d50 commit 2638538

File tree

6 files changed

+100
-52
lines changed

6 files changed

+100
-52
lines changed

hphp/runtime/vm/hhbc.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,12 @@ struct MInstrInfo {
251251
return m_instr;
252252
}
253253

254-
const MInstrAttr& getAttr(LocationCode lc) const {
254+
MInstrAttr getAttr(LocationCode lc) const {
255255
assert(lc < NumLocationCodes);
256256
return m_baseOps[lc];
257257
}
258258

259-
const MInstrAttr& getAttr(MemberCode mc) const {
259+
MInstrAttr getAttr(MemberCode mc) const {
260260
assert(mc < NumMemberCodes);
261261
return m_intermediateOps[mc];
262262
}

hphp/runtime/vm/jit/minstr-translator.cpp

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,41 @@ SSATmp* HhbcTranslator::MInstrTranslator::genMisPtr() {
307307
}
308308
}
309309

310+
311+
namespace {
312+
bool mightCallMagicPropMethod(MInstrAttr mia, const Class* cls,
313+
PropInfo propInfo) {
314+
auto const objAttr = (mia & Define) ? ObjectData::UseSet : ObjectData::UseGet;
315+
auto const clsHasMagicMethod = !cls || (cls->getODAttrs() & objAttr);
316+
317+
return clsHasMagicMethod &&
318+
convertToType(propInfo.repoAuthType).maybe(Type::Uninit);
319+
}
320+
321+
bool
322+
mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
323+
const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
324+
unsigned mi = 0;
325+
unsigned ii = mii.valCount() + 1;
326+
for (; mi < ni.immVecM.size(); ++mi) {
327+
MemberCode mc = ni.immVecM[mi];
328+
if (mcodeIsProp(mc)) {
329+
const Class* cls = nullptr;
330+
auto propInfo = getPropertyOffset(ni, context, cls, mii, mi, ii);
331+
if (propInfo.offset == -1 ||
332+
mightCallMagicPropMethod(mii.getAttr(mc), cls, propInfo)) {
333+
return true;
334+
}
335+
++ii;
336+
} else {
337+
return true;
338+
}
339+
}
340+
341+
return false;
342+
}
343+
}
344+
310345
// Inspect the instruction we're about to translate and determine if
311346
// it can be executed without using an MInstrState struct.
312347
void HhbcTranslator::MInstrTranslator::checkMIState() {
@@ -320,60 +355,54 @@ void HhbcTranslator::MInstrTranslator::checkMIState() {
320355
// about the operation, this function doesn't care what the type is.
321356
auto baseVal = getBase(DataTypeGeneric);
322357
Type baseType = baseVal->type();
358+
const bool baseArr = baseType <= Type::Arr;
323359
const bool isCGetM = m_ni.mInstrOp() == OpCGetM;
324360
const bool isSetM = m_ni.mInstrOp() == OpSetM;
325361
const bool isIssetM = m_ni.mInstrOp() == OpIssetM;
326362
const bool isUnsetM = m_ni.mInstrOp() == OpUnsetM;
327363
const bool isSingle = m_ni.immVecM.size() == 1;
364+
const bool unknownOffsets = mInstrHasUnknownOffsets(m_ni, contextClass());
328365

329366
if (baseType.maybeBoxed() && !baseType.isBoxed()) {
330367
// We don't need to bother with weird base types.
331368
return;
332369
}
333370
baseType = baseType.unbox();
334371

372+
335373
// CGetM or SetM with no unknown property offsets
336-
const bool simpleProp = !mInstrHasUnknownOffsets(m_ni, contextClass()) &&
337-
(isCGetM || isSetM);
374+
const bool simpleProp = !unknownOffsets && (isCGetM || isSetM);
338375

339-
// SetM with only one element
340-
const bool singlePropSet = isSingle && isSetM &&
341-
mcodeIsProp(m_ni.immVecM[0]);
376+
// SetM with only one vector element, for props and elems
377+
const bool singleSet = isSingle && isSetM;
342378

343-
// Array access with one element in the vector
379+
// Element access with one element in the vector
344380
const bool singleElem = isSingle && mcodeIsElem(m_ni.immVecM[0]);
345381

346-
// SetM with one vector array element
347-
const bool simpleArraySet = isSetM && singleElem;
348-
349382
// IssetM with one vector array element and an Arr base
350-
const bool simpleArrayIsset = isIssetM && singleElem &&
351-
baseType <= Type::Arr;
383+
const bool simpleArrayIsset = isIssetM && singleElem && baseArr;
384+
352385
// IssetM with one vector array element and a collection type
353386
const bool simpleCollectionIsset = isIssetM && singleElem &&
354-
baseType.strictSubtypeOf(Type::Obj) &&
355-
isOptimizableCollectionClass(baseType.getClass());
387+
baseType < Type::Obj && isOptimizableCollectionClass(baseType.getClass());
356388

357389
// UnsetM on an array with one vector element
358-
const bool simpleArrayUnset = isUnsetM && singleElem;
359-
360-
// UnsetM on a non-standard base. Always a noop or fatal.
361-
const bool badUnset = isUnsetM && baseType.not(Type::Arr | Type::Obj);
390+
const bool simpleArrayUnset = isUnsetM && singleElem &&
391+
baseType <= Type::Arr;
362392

363393
// CGetM on an array with a base that won't use MInstrState. Str
364394
// will use tvScratch and Obj will fatal or use tvRef.
365395
const bool simpleArrayGet = isCGetM && singleElem &&
366396
baseType.not(Type::Str | Type::Obj);
367397
const bool simpleCollectionGet = isCGetM && singleElem &&
368-
baseType.strictSubtypeOf(Type::Obj) &&
369-
isOptimizableCollectionClass(baseType.getClass());
398+
baseType < Type::Obj && isOptimizableCollectionClass(baseType.getClass());
370399
const bool simpleStringOp = (isCGetM || isIssetM) && isSingle &&
371400
isSimpleBase() && mcodeMaybeArrayIntKey(m_ni.immVecM[0]) &&
372401
baseType <= Type::Str;
373402

374-
if (simpleProp || singlePropSet ||
375-
simpleArraySet || simpleArrayGet || simpleCollectionGet ||
376-
simpleArrayUnset || badUnset || simpleCollectionIsset ||
403+
if (simpleProp || singleSet ||
404+
simpleArrayGet || simpleCollectionGet ||
405+
simpleArrayUnset || simpleCollectionIsset ||
377406
simpleArrayIsset || simpleStringOp) {
378407
setNoMIState();
379408
if (simpleCollectionGet || simpleCollectionIsset) {
@@ -861,14 +890,14 @@ void HhbcTranslator::MInstrTranslator::emitIntermediateOp() {
861890
}
862891
}
863892

864-
865893
void HhbcTranslator::MInstrTranslator::emitProp() {
866894
const Class* knownCls = nullptr;
867895
const auto propInfo = getPropertyOffset(m_ni, contextClass(),
868896
knownCls, m_mii,
869897
m_mInd, m_iInd);
870898
auto mia = m_mii.getAttr(m_ni.immVecM[m_mInd]);
871-
if (propInfo.offset == -1 || (mia & Unset)) {
899+
if (propInfo.offset == -1 || (mia & Unset) ||
900+
mightCallMagicPropMethod(mia, knownCls, propInfo)) {
872901
emitPropGeneric();
873902
} else {
874903
emitPropSpecialized(mia, propInfo);
@@ -1338,7 +1367,9 @@ void HhbcTranslator::MInstrTranslator::emitCGetProp() {
13381367
const Class* knownCls = nullptr;
13391368
const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls,
13401369
m_mii, m_mInd, m_iInd);
1341-
if (propInfo.offset != -1) {
1370+
1371+
if (propInfo.offset != -1 &&
1372+
!mightCallMagicPropMethod(None, knownCls, propInfo)) {
13421373
emitPropSpecialized(MIA_warn, propInfo);
13431374
SSATmp* cellPtr = gen(UnboxPtr, m_base);
13441375
m_result = gen(LdMem, Type::Cell, cellPtr, cns(0));
@@ -1463,7 +1494,8 @@ void HhbcTranslator::MInstrTranslator::emitSetProp() {
14631494
const Class* knownCls = nullptr;
14641495
const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls,
14651496
m_mii, m_mInd, m_iInd);
1466-
if (propInfo.offset != -1) {
1497+
if (propInfo.offset != -1 &&
1498+
!mightCallMagicPropMethod(Define, knownCls, propInfo)) {
14671499
emitPropSpecialized(MIA_define, propInfo);
14681500
SSATmp* cellPtr = gen(UnboxPtr, m_base);
14691501
SSATmp* oldVal = gen(LdMem, Type::Cell, cellPtr, cns(0));

hphp/runtime/vm/jit/translator-x64.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,6 @@ bool isNormalPropertyAccess(const NormalizedInstruction& i,
352352
int propInput,
353353
int objInput);
354354

355-
bool mInstrHasUnknownOffsets(const NormalizedInstruction& i,
356-
Class* contextClass);
357-
358355
struct PropInfo {
359356
PropInfo()
360357
: offset(-1)

hphp/runtime/vm/jit/translator.cpp

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -375,27 +375,6 @@ isNormalPropertyAccess(const NormalizedInstruction& i,
375375
i.inputs[objInput]->valueType() == KindOfObject;
376376
}
377377

378-
bool
379-
mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
380-
const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
381-
unsigned mi = 0;
382-
unsigned ii = mii.valCount() + 1;
383-
for (; mi < ni.immVecM.size(); ++mi) {
384-
MemberCode mc = ni.immVecM[mi];
385-
if (mcodeIsProp(mc)) {
386-
const Class* cls = nullptr;
387-
if (getPropertyOffset(ni, context, cls, mii, mi, ii).offset == -1) {
388-
return true;
389-
}
390-
++ii;
391-
} else {
392-
return true;
393-
}
394-
}
395-
396-
return false;
397-
}
398-
399378
PropInfo getPropertyOffset(const NormalizedInstruction& ni,
400379
Class* ctx,
401380
const Class*& baseClass,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
// Copyright 2004-present Facebook. All Rights Reserved.
3+
4+
class c {
5+
private $prop = 'ohai';
6+
7+
public function doit() {
8+
unset($this->prop);
9+
}
10+
11+
public function showProp() {
12+
var_dump($this->prop);
13+
}
14+
15+
public function setProp() {
16+
$this->prop = 'yo';
17+
}
18+
19+
public function __get($name) {
20+
return 'prop-'.$name;
21+
}
22+
23+
public function __set($name, $value) {
24+
echo "setting $name to $value\n";
25+
}
26+
}
27+
28+
function main() {
29+
$c = new c;
30+
$c->showProp();
31+
$c->setProp();
32+
33+
$c->doit();
34+
$c->showProp();
35+
$c->setProp();
36+
}
37+
main();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
string(4) "ohai"
2+
string(9) "prop-prop"
3+
setting prop to yo

0 commit comments

Comments
 (0)