Navigation Menu

Skip to content

Commit

Permalink
HHBBC shouldn't step through unreachable code while inserting RAT ass…
Browse files Browse the repository at this point in the history
…ertions

Summary:
While inserting RAT type assertions at the end of the optimize phase, HHBBC
shouldn't step through unreachable bytecode. There's an invariant that at this
point, the analysis of the function should have reached a fixed
point. Therefore, stepping through the bytecodes should not infer anything new
about the program. We do assert this in a few places (related to function
folding). However, unreachable parts of the function may not be analyzed fully,
and therefore stepping through them might break that invariant.

Instead, as soon as we hit an unreachable part of the program, just insert a
static analysis error guard and stop processing that block (same as we do
elsewhere).

Include a test case which triggers an assertion without this. The root cause of
why this test case triggers it is because of the lack of a pattern-match
optimization for IsType[C/L]; Not; Jmp[N]Z during analysis. The peephole
optimizer will remove the Not and flip the jump, but only during the
optimization phase. Add a suitable pattern matching optimization as well.

Reviewed By: markw65

Differential Revision: D6905679

fbshipit-source-id: f4e30540e589158216dc8117c742b340507f21f5
  • Loading branch information
ricklavoie authored and hhvm-bot committed Feb 7, 2018
1 parent b145d0a commit edc1b48
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 10 deletions.
147 changes: 137 additions & 10 deletions hphp/hhbbc/interp.cpp
Expand Up @@ -772,11 +772,8 @@ void sameImpl(ISS& env) {
push(env, std::move(pair.first)); push(env, std::move(pair.first));
} }


}

template<class Same, class JmpOp> template<class Same, class JmpOp>
void group(ISS& env, const Same& same, const JmpOp& jmp) { void sameJmpImpl(ISS& env, const Same& same, const JmpOp& jmp) {

auto bail = [&] { impl(env, same, jmp); }; auto bail = [&] { impl(env, same, jmp); };


constexpr auto NSame = Same::op == Op::NSame; constexpr auto NSame = Same::op == Op::NSame;
Expand Down Expand Up @@ -875,6 +872,21 @@ void group(ISS& env, const Same& same, const JmpOp& jmp) {
sameIsJmpTarget ? handle_differ() : handle_same(); sameIsJmpTarget ? handle_differ() : handle_same();
} }


bc::JmpNZ invertJmp(const bc::JmpZ& jmp) { return bc::JmpNZ { jmp.target }; }
bc::JmpZ invertJmp(const bc::JmpNZ& jmp) { return bc::JmpZ { jmp.target }; }

}

template<class Same, class JmpOp>
void group(ISS& env, const Same& same, const JmpOp& jmp) {
sameJmpImpl(env, same, jmp);
}

template<class Same, class JmpOp>
void group(ISS& env, const Same& same, const bc::Not&, const JmpOp& jmp) {
sameJmpImpl(env, same, invertJmp(jmp));
}

void in(ISS& env, const bc::Same&) { sameImpl<false>(env); } void in(ISS& env, const bc::Same&) { sameImpl<false>(env); }
void in(ISS& env, const bc::NSame&) { sameImpl<true>(env); } void in(ISS& env, const bc::NSame&) { sameImpl<true>(env); }


Expand Down Expand Up @@ -1286,12 +1298,12 @@ void typeTestPropagate(ISS& env, Type valTy, Type testTy,
push(env, std::move(takenOnSuccess ? failTy : testTy)); push(env, std::move(takenOnSuccess ? failTy : testTy));
} }


}

// After a StaticLocCheck, we know the local is bound on the true path, // After a StaticLocCheck, we know the local is bound on the true path,
// and not changed on the false path. // and not changed on the false path.
template<class JmpOp> template<class JmpOp>
void group(ISS& env, const bc::StaticLocCheck& slc, const JmpOp& jmp) { void staticLocCheckJmpImpl(ISS& env,
const bc::StaticLocCheck& slc,
const JmpOp& jmp) {
auto const takenOnInit = jmp.op == Op::JmpNZ; auto const takenOnInit = jmp.op == Op::JmpNZ;
auto save = env.state; auto save = env.state;


Expand Down Expand Up @@ -1319,11 +1331,30 @@ void group(ISS& env, const bc::StaticLocCheck& slc, const JmpOp& jmp) {
} }
} }


}

template<class JmpOp>
void group(ISS& env, const bc::StaticLocCheck& slc, const JmpOp& jmp) {
staticLocCheckJmpImpl(env, slc, jmp);
}

template<class JmpOp>
void group(ISS& env, const bc::StaticLocCheck& slc,
const bc::Not&, const JmpOp& jmp) {
staticLocCheckJmpImpl(env, slc, invertJmp(jmp));
}

template<class JmpOp> template<class JmpOp>
void group(ISS& env, const bc::IsTypeL& istype, const JmpOp& jmp) { void group(ISS& env, const bc::IsTypeL& istype, const JmpOp& jmp) {
isTypeHelper(env, istype.subop2, istype.loc1, istype, jmp); isTypeHelper(env, istype.subop2, istype.loc1, istype, jmp);
} }


template<class JmpOp>
void group(ISS& env, const bc::IsTypeL& istype,
const bc::Not&, const JmpOp& jmp) {
isTypeHelper(env, istype.subop2, istype.loc1, istype, invertJmp(jmp));
}

// If we duplicate a value, and then test its type and Jmp based on that result, // If we duplicate a value, and then test its type and Jmp based on that result,
// we can narrow the type of the top of the stack. Only do this for null checks // we can narrow the type of the top of the stack. Only do this for null checks
// right now (because its useful in memoize wrappers). // right now (because its useful in memoize wrappers).
Expand All @@ -1334,6 +1365,14 @@ void group(ISS& env, const bc::IsTypeC& istype, const JmpOp& jmp) {
isTypeHelper(env, istype.subop1, location, istype, jmp); isTypeHelper(env, istype.subop1, location, istype, jmp);
} }


template<class JmpOp>
void group(ISS& env, const bc::IsTypeC& istype,
const bc::Not& negate, const JmpOp& jmp) {
auto const location = topStkEquiv(env);
if (location == NoLocalId) return impl(env, istype, negate, jmp);
isTypeHelper(env, istype.subop1, location, istype, invertJmp(jmp));
}

// If we do an IsUninit check and then Jmp based on the check, one branch will // If we do an IsUninit check and then Jmp based on the check, one branch will
// be the original type minus the Uninit, and the other will be // be the original type minus the Uninit, and the other will be
// Uninit. (IsUninit does not pop the value). // Uninit. (IsUninit does not pop the value).
Expand All @@ -1343,6 +1382,12 @@ void group(ISS& env, const bc::IsUninit&, const JmpOp& jmp) {
typeTestPropagate(env, valTy, TUninit, remove_uninit(valTy), jmp); typeTestPropagate(env, valTy, TUninit, remove_uninit(valTy), jmp);
} }


template<class JmpOp>
void group(ISS& env, const bc::IsUninit&, const bc::Not&, const JmpOp& jmp) {
auto const valTy = popCU(env);
typeTestPropagate(env, valTy, TUninit, remove_uninit(valTy), invertJmp(jmp));
}

// A MemoGet, followed by an IsUninit, followed by a Jmp, can have the type of // A MemoGet, followed by an IsUninit, followed by a Jmp, can have the type of
// the stack inferred very well. The IsUninit success path will be Uninit and // the stack inferred very well. The IsUninit success path will be Uninit and
// the failure path will be the inferred return type of the wrapped // the failure path will be the inferred return type of the wrapped
Expand All @@ -1359,10 +1404,12 @@ void group(ISS& env, const bc::MemoGet& get, const bc::IsUninit& /*isuninit*/,
typeTestPropagate(env, popCU(env), TUninit, memoizeImplRetType(env), jmp); typeTestPropagate(env, popCU(env), TUninit, memoizeImplRetType(env), jmp);
} }


namespace {

template<class JmpOp> template<class JmpOp>
void group(ISS& env, void instanceOfJmpImpl(ISS& env,
const bc::InstanceOfD& inst, const bc::InstanceOfD& inst,
const JmpOp& jmp) { const JmpOp& jmp) {
auto bail = [&] { impl(env, inst, jmp); }; auto bail = [&] { impl(env, inst, jmp); };


auto const locId = topStkEquiv(env); auto const locId = topStkEquiv(env);
Expand Down Expand Up @@ -1394,6 +1441,23 @@ void group(ISS& env,
refineLocation(env, locId, pre, jmp.target, post); refineLocation(env, locId, pre, jmp.target, post);
} }


}

template<class JmpOp>
void group(ISS& env,
const bc::InstanceOfD& inst,
const JmpOp& jmp) {
instanceOfJmpImpl(env, inst, jmp);
}

template<class JmpOp>
void group(ISS& env,
const bc::InstanceOfD& inst,
const bc::Not&,
const JmpOp& jmp) {
instanceOfJmpImpl(env, inst, invertJmp(jmp));
}

void in(ISS& env, const bc::Switch& op) { void in(ISS& env, const bc::Switch& op) {
auto v = tv(popC(env)); auto v = tv(popC(env));


Expand Down Expand Up @@ -3992,6 +4056,15 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) {
switch (o1) { switch (o1) {
case Op::InstanceOfD: case Op::InstanceOfD:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].InstanceOfD, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].InstanceOfD, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: case Op::JmpZ:
return group(env, it, it[0].InstanceOfD, it[1].JmpZ); return group(env, it, it[0].InstanceOfD, it[1].JmpZ);
case Op::JmpNZ: case Op::JmpNZ:
Expand All @@ -4001,20 +4074,47 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) {
break; break;
case Op::IsTypeL: case Op::IsTypeL:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].IsTypeL, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].IsTypeL, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: return group(env, it, it[0].IsTypeL, it[1].JmpZ); case Op::JmpZ: return group(env, it, it[0].IsTypeL, it[1].JmpZ);
case Op::JmpNZ: return group(env, it, it[0].IsTypeL, it[1].JmpNZ); case Op::JmpNZ: return group(env, it, it[0].IsTypeL, it[1].JmpNZ);
default: break; default: break;
} }
break; break;
case Op::IsUninit: case Op::IsUninit:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].IsUninit, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].IsUninit, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: return group(env, it, it[0].IsUninit, it[1].JmpZ); case Op::JmpZ: return group(env, it, it[0].IsUninit, it[1].JmpZ);
case Op::JmpNZ: return group(env, it, it[0].IsUninit, it[1].JmpNZ); case Op::JmpNZ: return group(env, it, it[0].IsUninit, it[1].JmpNZ);
default: break; default: break;
} }
break; break;
case Op::IsTypeC: case Op::IsTypeC:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].IsTypeC, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].IsTypeC, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: case Op::JmpZ:
return group(env, it, it[0].IsTypeC, it[1].JmpZ); return group(env, it, it[0].IsTypeC, it[1].JmpZ);
case Op::JmpNZ: case Op::JmpNZ:
Expand All @@ -4038,6 +4138,15 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) {
break; break;
case Op::StaticLocCheck: case Op::StaticLocCheck:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].StaticLocCheck, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].StaticLocCheck, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: case Op::JmpZ:
return group(env, it, it[0].StaticLocCheck, it[1].JmpZ); return group(env, it, it[0].StaticLocCheck, it[1].JmpZ);
case Op::JmpNZ: case Op::JmpNZ:
Expand All @@ -4047,6 +4156,15 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) {
break; break;
case Op::Same: case Op::Same:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].Same, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].Same, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: case Op::JmpZ:
return group(env, it, it[0].Same, it[1].JmpZ); return group(env, it, it[0].Same, it[1].JmpZ);
case Op::JmpNZ: case Op::JmpNZ:
Expand All @@ -4056,6 +4174,15 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) {
break; break;
case Op::NSame: case Op::NSame:
switch (o2) { switch (o2) {
case Op::Not:
switch (o3) {
case Op::JmpZ:
return group(env, it, it[0].NSame, it[1].Not, it[2].JmpZ);
case Op::JmpNZ:
return group(env, it, it[0].NSame, it[1].Not, it[2].JmpNZ);
default: break;
}
break;
case Op::JmpZ: case Op::JmpZ:
return group(env, it, it[0].NSame, it[1].JmpZ); return group(env, it, it[0].NSame, it[1].JmpZ);
case Op::JmpNZ: case Op::JmpNZ:
Expand Down
10 changes: 10 additions & 0 deletions hphp/hhbbc/optimize.cpp
Expand Up @@ -335,6 +335,16 @@ void insert_assertions(const Index& index,
FTRACE(2, " + {}\n", show(ctx.func, newBCs.back())); FTRACE(2, " + {}\n", show(ctx.func, newBCs.back()));
}; };


if (state.unreachable) {
blk->fallthrough = NoBlockId;
if (!(instrFlags(op.op) & TF)) {
gen(bc::BreakTraceHint {});
gen(bc::String { s_unreachable.get() });
gen(bc::Fatal { FatalOp::Runtime });
}
break;
}

auto const preState = state; auto const preState = state;
auto const flags = step(interp, op); auto const flags = step(interp, op);


Expand Down
16 changes: 16 additions & 0 deletions hphp/test/slow/hhbbc/insert-assertions-unreachable.php
@@ -0,0 +1,16 @@
<?hh
// Copyright 2004-present Facebook. All Rights Reserved.

function byref(&$x) {}
function build() {
$d = dict[];
$k = __hhvm_intrinsics\launder_value('key');
while (__hhvm_intrinsics\launder_value(false)) {
$d[$k] = $d[$k] ?? dict[];
$d[$k] = $d[$k] ?? dict[];
byref(&$d[$k]['k2']);
}
return $d;
}

var_dump(build());
2 changes: 2 additions & 0 deletions hphp/test/slow/hhbbc/insert-assertions-unreachable.php.expect
@@ -0,0 +1,2 @@
dict(0) {
}

0 comments on commit edc1b48

Please sign in to comment.