cbool parseEOLplus(parse *p){ return ( maybe(parseWS(p)) && parseEOL(p) && many(maybe(parseWS(p)) && parseEOL(p)) ); }
// Class GeneBool ----------------------------- void GeneBool :: ranStart() { geneVal = 0.0; if (maybe()) { if (maybe()) { geneVal = 1.0; } } }
TEST(Type, PtrKinds) { auto const frameGen = TGen.ptr(Ptr::Frame); auto const frameUninit = TUninit.ptr(Ptr::Frame); auto const frameBool = TBool.ptr(Ptr::Frame); auto const unknownBool = TBool.ptr(Ptr::Unk); auto const unknownGen = TGen.ptr(Ptr::Unk); auto const stackObj = TObj.ptr(Ptr::Stk); auto const stackBool = TBool.ptr(Ptr::Stk); EXPECT_EQ("PtrToFrameGen", frameGen.toString()); EXPECT_EQ("PtrToFrameBool", frameBool.toString()); EXPECT_EQ("PtrToBool", unknownBool.toString()); EXPECT_EQ("PtrToStkObj", stackObj.toString()); EXPECT_EQ("Nullptr|PtrToPropCell", (TPtrToPropCell|TNullptr).toString()); EXPECT_EQ(Ptr::Frame, (frameUninit|frameBool).ptrKind()); EXPECT_TRUE(frameBool <= unknownBool); EXPECT_TRUE(frameBool <= frameGen); EXPECT_FALSE(frameBool <= frameUninit); EXPECT_TRUE(frameBool.maybe(frameGen)); EXPECT_TRUE(frameBool.maybe(unknownBool)); EXPECT_TRUE(!frameUninit.maybe(frameBool)); EXPECT_TRUE(frameUninit.maybe(frameGen)); EXPECT_TRUE(!frameUninit.maybe(unknownBool)); EXPECT_TRUE(!TPtrToUninit.maybe(TPtrToBool)); EXPECT_FALSE(unknownBool <= frameBool); EXPECT_EQ(unknownBool, frameBool | unknownBool); EXPECT_EQ(unknownGen, frameGen | unknownBool); EXPECT_EQ(TBottom, frameBool & stackBool); EXPECT_EQ(frameBool, frameBool & unknownBool); EXPECT_EQ(TPtrToCell, TPtrToPropCell|TPtrToFrameCell); EXPECT_EQ(Ptr::Prop, (TPtrToPropCell|TNullptr).ptrKind()); EXPECT_EQ(Ptr::RProp, (TPtrToRPropCell|TNullptr).ptrKind()); EXPECT_EQ(TPtrToPropCell, (TPtrToPropCell|TNullptr) - TNullptr); auto const frameGenOrCell = frameGen | TCell; auto const frameOrRefGenOrCell = frameGenOrCell | TGen.ptr(Ptr::Ref); auto const stackOrRefArrOrInt = TArr.ptr(Ptr::RStk) | TInt; EXPECT_EQ(frameGenOrCell & stackOrRefArrOrInt, TInt); EXPECT_EQ(frameOrRefGenOrCell & stackOrRefArrOrInt, TArr.ptr(Ptr::Ref) | TInt); }
int ai_block_projectile(controller *ctrl, ctrl_event **ev) { ai *a = ctrl->data; object *o = ctrl->har; iterator it; object **o_tmp; vector_iter_begin(&a->active_projectiles, &it); while((o_tmp = iter_next(&it)) != NULL) { object *o_prj = *o_tmp; if(projectile_get_owner(o_prj) == o) { continue; } if(o_prj->cur_sprite && maybe(a->difficulty)) { vec2i pos_prj = vec2i_add(object_get_pos(o_prj), o_prj->cur_sprite->pos); vec2i size_prj = object_get_size(o_prj); if (object_get_direction(o_prj) == OBJECT_FACE_LEFT) { pos_prj.x = object_get_pos(o_prj).x + ((o_prj->cur_sprite->pos.x * -1) - size_prj.x); } if(fabsf(pos_prj.x - o->pos.x) < 120) { a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_DOWN|ACT_LEFT : ACT_DOWN|ACT_RIGHT); controller_cmd(ctrl, a->cur_act, ev); return 1; } } } return 0; }
static char *maybeAddOption(const char *msg, char *buf, size_t nbuf, const char *option) { if (maybe(msg)) { buf = addOption(buf, nbuf, option); } return buf; }
double LineParser::doubleLiteral() { double signum; if (!maybe(signum, [this]() { return this->oneOfMap(signSignums); } )) { signum = 1.0; } return signum * unsignedDoubleLiteral(); }
int LineParser::intLiteral() { int signum; if (!maybe(signum, [this]() { return this->oneOfMap(intSignSignums); } )) { signum = 1; } return signum * unsignedIntLiteral(); }
TEST(Type, PtrKinds) { auto const frameGen = Type::Gen.ptr(Ptr::Frame); auto const frameUninit = Type::Uninit.ptr(Ptr::Frame); auto const frameBool = Type::Bool.ptr(Ptr::Frame); auto const unknownBool = Type::Bool.ptr(Ptr::Unk); auto const unknownGen = Type::Gen.ptr(Ptr::Unk); auto const stackObj = Type::Obj.ptr(Ptr::Stk); auto const stackBool = Type::Bool.ptr(Ptr::Stk); EXPECT_EQ("PtrToFrameGen", frameGen.toString()); EXPECT_EQ("PtrToFrameBool", frameBool.toString()); EXPECT_EQ("PtrToBool", unknownBool.toString()); EXPECT_EQ("PtrToStkObj", stackObj.toString()); EXPECT_EQ("Nullptr|PtrToPropCell", (Type::PtrToPropCell|Type::Nullptr).toString()); EXPECT_EQ(Ptr::Frame, (frameUninit|frameBool).ptrKind()); EXPECT_TRUE(frameBool.subtypeOf(unknownBool)); EXPECT_TRUE(frameBool.subtypeOf(frameGen)); EXPECT_TRUE(!frameBool.subtypeOf(frameUninit)); EXPECT_TRUE(frameBool.maybe(frameGen)); EXPECT_TRUE(frameBool.maybe(unknownBool)); EXPECT_TRUE(!frameUninit.maybe(frameBool)); EXPECT_TRUE(frameUninit.maybe(frameGen)); EXPECT_TRUE(!frameUninit.maybe(unknownBool)); EXPECT_TRUE(!Type::PtrToUninit.maybe(Type::PtrToBool)); EXPECT_TRUE(!unknownBool.subtypeOf(frameBool)); EXPECT_EQ(unknownBool, frameBool | unknownBool); EXPECT_EQ(unknownGen, Type::unionOf(frameGen, unknownBool)); EXPECT_EQ(Type::Bottom, frameBool & stackBool); EXPECT_EQ(frameBool, frameBool & unknownBool); EXPECT_EQ(Type::PtrToCell, Type::PtrToPropCell|Type::PtrToFrameCell); EXPECT_EQ(Ptr::Prop, (Type::PtrToPropCell|Type::Nullptr).ptrKind()); EXPECT_EQ(Ptr::RProp, (Type::PtrToRPropCell|Type::Nullptr).ptrKind()); EXPECT_EQ(Type::PtrToPropCell, (Type::PtrToPropCell|Type::Nullptr) - Type::Nullptr); }
int GeneDouble :: mutate(double howMuch) { if (maybe()) { howMuch = 1.0 / howMuch; geneVal *= howMuch + rnd(1.0 - howMuch); } else { geneVal *= 1.0 + rnd(howMuch - 1.0); } return 0; }
Approximation Approximator::find(const UnaryRelation& pos, const UnaryRelation& neg, const Approximation& arg) { if (arg.ob and pos.find(arg.ob)) { return truthy(); } else if (arg.ob and neg.find(arg.ob)) { return falsey(); } else { return maybe(); } }
Approximation Approximator::find(const BinaryRelation& pos, const BinaryRelation& neg, const Approximation& lhs, const Approximation& rhs) { if (lhs.ob and rhs.ob and pos.find(lhs.ob, rhs.ob)) { return truthy(); } else if (lhs.ob and rhs.ob and neg.find(lhs.ob, rhs.ob)) { return falsey(); } else { return maybe(); } }
Type negativeCheckType(Type srcType, Type typeParam) { if (srcType <= typeParam) return TBottom; if (!srcType.maybe(typeParam)) return srcType; // Checks relating to StaticStr and StaticArr are not, in general, precise. // They may reject some Statics in some situations, where we only guard using // the type tag and not by loading the count field. auto tmp = srcType - typeParam; if (typeParam.maybe(TPersistent)) { if (tmp.maybe(TCountedStr)) tmp |= TStr; if (tmp.maybe(TCountedArr)) tmp |= TArr; } return tmp; }
GlobObj * GlobObj::get_glob(const Token &t) { pair <glob_map::const_iterator, glob_map::const_iterator> maybe(all.equal_range(t.get_parts_begin()->get_tokid())); glob_map::const_iterator i; for (i = maybe.first; i != maybe.second; i++) { if (DP()) cout << "Compare " << t << " with " << i->second->get_token() << "\n"; if (t.equals(i->second->get_token())) { if (DP()) cout << "Get glob for " << t << " returns " << &(i->second) << "\n"; return i->second; } } return NULL; }
DataType Type::toDataType() const { assertx(!maybe(TPtrToGen) || m_bits == kBottom); assertx(isKnownDataType()); // Order is important here: types must progress from more specific // to less specific to return the most specific DataType. if (*this <= TUninit) return KindOfUninit; if (*this <= TInitNull) return KindOfNull; if (*this <= TBool) return KindOfBoolean; if (*this <= TInt) return KindOfInt64; if (*this <= TDbl) return KindOfDouble; if (*this <= TStaticStr) return KindOfStaticString; if (*this <= TStr) return KindOfString; if (*this <= TArr) return KindOfArray; if (*this <= TObj) return KindOfObject; if (*this <= TRes) return KindOfResource; if (*this <= TBoxedCell) return KindOfRef; if (*this <= TCls) return KindOfClass; always_assert_flog(false, "Bad Type {} in Type::toDataType()", *this); }
// return 1 on block int ai_block_har(controller *ctrl, ctrl_event **ev) { ai *a = ctrl->data; object *o = ctrl->har; har *h = object_get_userdata(o); object *o_enemy = game_state_get_player(o->gs, h->player_id == 1 ? 0 : 1)->har; har *h_enemy = object_get_userdata(o_enemy); // XXX TODO get maximum move distance from the animation object if(fabsf(o_enemy->pos.x - o->pos.x) < 100) { if(h_enemy->executing_move && maybe(a->difficulty)) { if(har_is_crouching(h_enemy)) { a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_DOWN|ACT_LEFT : ACT_DOWN|ACT_RIGHT); controller_cmd(ctrl, a->cur_act, ev); } else { a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_LEFT : ACT_RIGHT); controller_cmd(ctrl, a->cur_act, ev); } return 1; } } return 0; }
T maybe_gen( const optional<T>& op, TGx&& defaultgen ) { return maybe( op, detail::pass(), std::forward<TGx>( defaultgen ) ); }
TEST(Type, Const) { auto five = Type::cns(5); EXPECT_LT(five, TInt); EXPECT_NE(five, TInt); EXPECT_TRUE(five.hasConstVal()); EXPECT_EQ(5, five.intVal()); EXPECT_TRUE(five.hasConstVal(TInt)); EXPECT_TRUE(five.hasConstVal(5)); EXPECT_FALSE(five.hasConstVal(5.0)); EXPECT_TRUE(TGen.maybe(five)); EXPECT_EQ(TInt, five | TInt); EXPECT_EQ(TInt, five | Type::cns(10)); EXPECT_EQ(five, five | Type::cns(5)); EXPECT_EQ(five, Type::cns(5) & five); EXPECT_EQ(five, five & TInt); EXPECT_EQ(five, TGen & five); EXPECT_EQ("Int<5>", five.toString()); EXPECT_EQ(five, five - TArr); EXPECT_EQ(five, five - Type::cns(1)); EXPECT_EQ(TInt, TInt - five); // conservative EXPECT_EQ(TBottom, five - TInt); EXPECT_EQ(TBottom, five - five); EXPECT_EQ(TPtrToGen, (TPtrToGen|TNullptr) - TNullptr); EXPECT_EQ(TInt, five.dropConstVal()); EXPECT_TRUE(!five.maybe(Type::cns(2))); auto True = Type::cns(true); EXPECT_EQ("Bool<true>", True.toString()); EXPECT_LT(True, TBool); EXPECT_NE(True, TBool); EXPECT_TRUE(True.hasConstVal()); EXPECT_EQ(true, True.boolVal()); EXPECT_TRUE(TUncounted.maybe(True)); EXPECT_FALSE(five <= True); EXPECT_FALSE(five > True); EXPECT_TRUE(!five.maybe(True)); EXPECT_EQ(TInt | TBool, five | True); EXPECT_EQ(TBottom, five & True); EXPECT_EQ(Type::cns(false), TBool - True); auto array = make_packed_array(1, 2, 3, 4); auto arrData = ArrayData::GetScalarArray(array.get()); auto constArray = Type::cns(arrData); auto packedArray = Type::Array(ArrayData::kPackedKind); auto mixedArray = Type::Array(ArrayData::kMixedKind); EXPECT_TRUE(constArray <= packedArray); EXPECT_TRUE(constArray < packedArray); EXPECT_FALSE(packedArray <= constArray); EXPECT_TRUE(constArray <= constArray); EXPECT_FALSE(packedArray <= mixedArray); EXPECT_FALSE(mixedArray <= packedArray); EXPECT_FALSE(constArray <= mixedArray); EXPECT_EQ(constArray, constArray & packedArray); ArrayTypeTable::Builder ratBuilder; auto rat1 = ratBuilder.packedn(RepoAuthType::Array::Empty::No, RepoAuthType(RepoAuthType::Tag::Str)); auto ratArray1 = Type::Array(rat1); auto rat2 = ratBuilder.packedn(RepoAuthType::Array::Empty::No, RepoAuthType(RepoAuthType::Tag::Int)); auto ratArray2 = Type::Array(rat2); EXPECT_EQ(TArr, ratArray1 & ratArray2); EXPECT_TRUE(ratArray1 < TArr); EXPECT_TRUE(ratArray1 <= ratArray1); EXPECT_TRUE(ratArray1 < (TArr|TObj)); EXPECT_FALSE(ratArray1 < ratArray2); EXPECT_NE(ratArray1, ratArray2); auto packedRat = packedArray & ratArray1; EXPECT_EQ("Arr=PackedKind:N([Str])", packedRat.toString()); EXPECT_TRUE(packedRat <= packedArray); EXPECT_TRUE(packedRat < packedArray); EXPECT_TRUE(packedRat <= ratArray1); EXPECT_TRUE(packedRat < ratArray1); EXPECT_EQ(packedRat, packedRat & packedArray); EXPECT_EQ(packedRat, packedRat & ratArray1); }
MemEffects memory_effects_impl(const IRInstruction& inst) { switch (inst.op()) { ////////////////////////////////////////////////////////////////////// // Region exits // These exits don't leave the current php function, and could head to code // that could read or write anything as far as we know (including frame // locals). case ReqBindJmp: return ExitEffects { AUnknown, stack_below(inst.src(0), inst.extra<ReqBindJmp>()->irSPOff.offset - 1) }; case JmpSwitchDest: return ExitEffects { AUnknown, *stack_below(inst.src(1), inst.extra<JmpSwitchDest>()->irSPOff.offset - 1). precise_union(AMIStateAny) }; case JmpSSwitchDest: return ExitEffects { AUnknown, *stack_below(inst.src(1), inst.extra<JmpSSwitchDest>()->offset.offset - 1). precise_union(AMIStateAny) }; case ReqRetranslate: case ReqRetranslateOpt: return UnknownEffects {}; ////////////////////////////////////////////////////////////////////// // Unusual instructions /* * The ReturnHook sets up the ActRec so the unwinder knows everything is * already released (i.e. it calls ar->setLocalsDecRefd()). * * The eval stack is also dead at this point (the return value is passed to * ReturnHook as src(1), and the ReturnHook may not access the stack). */ case ReturnHook: // Note, this instruction can re-enter, but doesn't need the may_reenter() // treatmeant because of the special kill semantics for locals and stack. return may_load_store_kill( AHeapAny, AHeapAny, *AStackAny.precise_union(AFrameAny)->precise_union(AMIStateAny) ); // The suspend hooks can load anything (re-entering the VM), but can't write // to frame locals. case SuspendHookE: case SuspendHookR: // TODO: may-load here probably doesn't need to include AFrameAny normally. return may_reenter(inst, may_load_store_kill(AUnknown, AHeapAny, AMIStateAny)); /* * If we're returning from a function, it's ReturnEffects. The RetCtrl * opcode also suspends resumables, which we model as having any possible * effects. * * Note that marking AFrameAny as dead isn't quite right, because that * ought to mean that the preceding StRetVal is dead; but memory effects * ignores StRetVal so the AFrameAny is fine. */ case RetCtrl: if (inst.extra<RetCtrl>()->suspendingResumed) { // Suspending can go anywhere, and doesn't even kill locals. return UnknownEffects {}; } return ReturnEffects { AStackAny | AFrameAny | AMIStateAny }; case AsyncRetFast: case AsyncRetCtrl: if (inst.extra<RetCtrlData>()->suspendingResumed) { return UnknownEffects {}; } return ReturnEffects { *stack_below( inst.src(0), inst.extra<RetCtrlData>()->spOffset.offset - 1 ).precise_union(AMIStateAny) }; case GenericRetDecRefs: /* * The may-store information here is AUnknown: even though we know it * doesn't really "store" to the frame locals, the values that used to be * there are no longer available because they are DecRef'd, which we are * required to report as may-store information to make it visible to * reference count optimizations. It's conceptually the same as if it was * storing an Uninit over each of the locals, but the stores of uninits * would be dead so we're not actually doing that. */ return may_reenter(inst, may_load_store_kill(AUnknown, AUnknown, AMIStateAny)); case EndCatch: { auto const stack_kills = stack_below( inst.src(1), inst.extra<EndCatch>()->offset.offset - 1 ); return ExitEffects { AUnknown, stack_kills | AMIStateTempBase | AMIStateBase }; } /* * DefInlineFP has some special treatment here. * * It's logically `publishing' a pointer to a pre-live ActRec, making it * live. It doesn't actually load from this ActRec, but after it's done this * the set of things that can load from it is large enough that the easiest * way to model this is to consider it as a load on behalf of `publishing' * the ActRec. Once it's published, it's a live activation record, and * doesn't get written to as if it were a stack slot anymore (we've * effectively converted AStack locations into a frame until the * InlineReturn). * * TODO(#3634984): Additionally, DefInlineFP is marking may-load on all the * locals of the outer frame. This is probably not necessary anymore, but we * added it originally because a store sinking prototype needed to know it * can't push StLocs past a DefInlineFP, because of reserved registers. * Right now it's just here because we need to think about and test it before * removing that set. */ case DefInlineFP: return may_load_store_kill( AFrameAny | inline_fp_frame(&inst), /* * This prevents stack slots from the caller from being sunk into the * callee. Note that some of these stack slots overlap with the frame * locals of the callee-- those slots are inacessible in the inlined * call as frame and stack locations may not alias. */ stack_below(inst.dst(), 0), /* * While not required for correctness adding these slots to the kill set * will hopefully avoid some extra stores. */ stack_below(inst.dst(), 0) ); case InlineReturn: return ReturnEffects { stack_below(inst.src(0), 2) | AMIStateAny }; case InlineReturnNoFrame: return ReturnEffects { AliasClass(AStack { inst.extra<InlineReturnNoFrame>()->frameOffset.offset, std::numeric_limits<int32_t>::max() }) | AMIStateAny }; case InterpOne: return interp_one_effects(inst); case InterpOneCF: return ExitEffects { AUnknown, stack_below(inst.src(1), -inst.marker().spOff().offset - 1) | AMIStateAny }; case NativeImpl: return UnknownEffects {}; // NB: on the failure path, these C++ helpers do a fixup and read frame // locals before they throw. They can also invoke the user error handler and // go do whatever they want to non-frame locations. // // TODO(#5372569): if we combine dv inits into the same regions we could // possibly avoid storing KindOfUninits if we modify this. case VerifyParamCallable: case VerifyParamCls: case VerifyParamFail: return may_raise(inst, may_load_store(AUnknown, AHeapAny)); // However the following ones can't read locals from our frame on the way // out, except as a side effect of raising a warning. case VerifyRetCallable: case VerifyRetCls: return may_raise(inst, may_load_store(AHeapAny, AHeapAny)); // In PHP 7 VerifyRetFail can coerce the return type in weak files-- even in // a strict file we may still coerce int to float. This is not true of HH // files. case VerifyRetFail: { auto func = inst.marker().func(); auto mayCoerce = RuntimeOption::PHP7_ScalarTypes && !RuntimeOption::EnableHipHopSyntax && !func->unit()->isHHFile(); auto stores = mayCoerce ? AHeapAny | AStackAny : AHeapAny; return may_raise(inst, may_load_store(AHeapAny | AStackAny, stores)); } case CallArray: return CallEffects { inst.extra<CallArray>()->destroyLocals, AMIStateAny, // The AStackAny on this is more conservative than it could be; see Call // and CallBuiltin. AStackAny }; case ContEnter: return CallEffects { false, AMIStateAny, AStackAny }; case Call: { auto const extra = inst.extra<Call>(); return CallEffects { extra->destroyLocals, // kill stack_below(inst.src(0), extra->spOffset.offset - 1) | AMIStateAny, // We might side-exit inside the callee, and interpret a return. So we // can read anything anywhere on the eval stack above the call's entry // depth here. AStackAny }; } case CallBuiltin: { auto const extra = inst.extra<CallBuiltin>(); auto const stk = [&] () -> AliasClass { AliasClass ret = AEmpty; for (auto i = uint32_t{2}; i < inst.numSrcs(); ++i) { if (inst.src(i)->type() <= TPtrToGen) { auto const cls = pointee(inst.src(i)); if (cls.maybe(AStackAny)) { ret = ret | cls; } } } return ret; }(); auto const locs = extra->destroyLocals ? AFrameAny : AEmpty; return may_raise( inst, may_load_store_kill(stk | AHeapAny | locs, locs, AMIStateAny)); } // Resumable suspension takes everything from the frame and moves it into the // heap. case CreateAFWH: case CreateAFWHNoVV: case CreateCont: return may_load_store_move(AFrameAny, AHeapAny, AFrameAny); // This re-enters to call extension-defined instance constructors. case ConstructInstance: return may_reenter(inst, may_load_store(AHeapAny, AHeapAny)); case CheckStackOverflow: case CheckSurpriseFlagsEnter: case CheckSurpriseAndStack: return may_raise(inst, may_load_store(AEmpty, AEmpty)); case InitExtraArgs: return UnknownEffects {}; ////////////////////////////////////////////////////////////////////// // Iterator instructions case IterInit: case MIterInit: case WIterInit: return iter_effects( inst, inst.src(1), AFrame { inst.src(1), inst.extra<IterData>()->valId } ); case IterNext: case MIterNext: case WIterNext: return iter_effects( inst, inst.src(0), AFrame { inst.src(0), inst.extra<IterData>()->valId } ); case IterInitK: case MIterInitK: case WIterInitK: { AliasClass key = AFrame { inst.src(1), inst.extra<IterData>()->keyId }; AliasClass val = AFrame { inst.src(1), inst.extra<IterData>()->valId }; return iter_effects(inst, inst.src(1), key | val); } case IterNextK: case MIterNextK: case WIterNextK: { AliasClass key = AFrame { inst.src(0), inst.extra<IterData>()->keyId }; AliasClass val = AFrame { inst.src(0), inst.extra<IterData>()->valId }; return iter_effects(inst, inst.src(0), key | val); } ////////////////////////////////////////////////////////////////////// // Instructions that explicitly manipulate locals case StLoc: return PureStore { AFrame { inst.src(0), inst.extra<StLoc>()->locId }, inst.src(1) }; case StLocRange: { auto const extra = inst.extra<StLocRange>(); auto acls = AEmpty; for (auto locId = extra->start; locId < extra->end; ++locId) { acls = acls | AFrame { inst.src(0), locId }; } return PureStore { acls, inst.src(1) }; } case LdLoc: return PureLoad { AFrame { inst.src(0), inst.extra<LocalId>()->locId } }; case CheckLoc: case LdLocPseudoMain: // Note: LdLocPseudoMain is both a guard and a load, so it must not be a // PureLoad. return may_load_store( AFrame { inst.src(0), inst.extra<LocalId>()->locId }, AEmpty ); case StLocPseudoMain: // This can store to globals or locals, but we don't have globals supported // in AliasClass yet. return PureStore { AUnknown, inst.src(1) }; case ClosureStaticLocInit: return may_load_store(AFrameAny, AFrameAny); ////////////////////////////////////////////////////////////////////// // Pointer-based loads and stores case LdMem: return PureLoad { pointee(inst.src(0)) }; case StMem: return PureStore { pointee(inst.src(0)), inst.src(1) }; // TODO(#5962341): These take non-constant offset arguments, and are // currently only used for collections and class property inits, so we aren't // hooked up yet. case StElem: return PureStore { inst.src(0)->type() <= TPtrToRMembCell ? AHeapAny : AUnknown, inst.src(2) }; case LdElem: return PureLoad { inst.src(0)->type() <= TPtrToRMembCell ? AHeapAny : AUnknown }; case LdMBase: return PureLoad { AMIStateBase }; case StMBase: return PureStore { AMIStateBase, inst.src(0) }; case FinishMemberOp: return may_load_store_kill(AEmpty, AEmpty, AMIStateAny); case BoxPtr: { auto const mem = pointee(inst.src(0)); return may_load_store(mem, mem); } case UnboxPtr: return may_load_store(pointee(inst.src(0)), AEmpty); case IsNTypeMem: case IsTypeMem: case CheckTypeMem: case CheckInitMem: case DbgAssertPtr: return may_load_store(pointee(inst.src(0)), AEmpty); ////////////////////////////////////////////////////////////////////// // Object/Ref loads/stores case CheckRefInner: return may_load_store(ARef { inst.src(0) }, AEmpty); case LdRef: return PureLoad { ARef { inst.src(0) } }; case StRef: return PureStore { ARef { inst.src(0) }, inst.src(1) }; case InitObjProps: return may_load_store(AEmpty, APropAny); ////////////////////////////////////////////////////////////////////// // Array loads and stores case InitPackedArray: return PureStore { AElemI { inst.src(0), inst.extra<InitPackedArray>()->index }, inst.src(1) }; case LdStructArrayElem: assertx(inst.src(1)->strVal()->isStatic()); return PureLoad { AElemS { inst.src(0), inst.src(1)->strVal() } }; case InitPackedArrayLoop: { auto const extra = inst.extra<InitPackedArrayLoop>(); auto const stack_in = AStack { inst.src(1), extra->offset.offset + static_cast<int32_t>(extra->size) - 1, static_cast<int32_t>(extra->size) }; return may_load_store_move(stack_in, AElemIAny, stack_in); } case NewStructArray: { // NewStructArray is reading elements from the stack, but writes to a // completely new array, so we can treat the store set as empty. auto const extra = inst.extra<NewStructArray>(); auto const stack_in = AStack { inst.src(0), extra->offset.offset + static_cast<int32_t>(extra->numKeys) - 1, static_cast<int32_t>(extra->numKeys) }; return may_load_store_move(stack_in, AEmpty, stack_in); } case ArrayIdx: return may_load_store(AElemAny | ARefAny, AEmpty); case MapIdx: return may_load_store(AHeapAny, AEmpty); case AKExistsArr: return may_load_store(AElemAny, AEmpty); case AKExistsObj: return may_reenter(inst, may_load_store(AHeapAny, AHeapAny)); ////////////////////////////////////////////////////////////////////// // Member instructions /* * Various minstr opcodes that take a PtrToGen in src 0, which may or may not * point to a frame local or the evaluation stack. These instructions can * all re-enter the VM and access arbitrary heap locations, and some of them * take pointers to MinstrState locations, which they may both load and store * from if present. */ case CGetElem: case EmptyElem: case IssetElem: case SetElem: case SetNewElemArray: case SetNewElem: case UnsetElem: case ElemArrayD: case ElemArrayU: // Right now we generally can't limit any of these better than general // re-entry rules, since they can raise warnings and re-enter. assertx(inst.src(0)->type() <= TPtrToGen); return may_raise(inst, may_load_store( AHeapAny | all_pointees(inst), AHeapAny | all_pointees(inst) )); case ElemX: case ElemDX: case ElemUX: case BindElem: case BindNewElem: case IncDecElem: case SetOpElem: case SetWithRefElem: case SetWithRefNewElem: case VGetElem: assertx(inst.src(0)->isA(TPtrToGen)); return minstr_with_tvref(inst); /* * These minstr opcodes either take a PtrToGen or an Obj as the base. The * pointer may point at frame locals or the stack. These instructions can * all re-enter the VM and access arbitrary non-frame/stack locations, as * well. */ case CGetProp: case CGetPropQ: case EmptyProp: case IssetProp: case UnsetProp: case IncDecProp: case SetProp: return may_raise(inst, may_load_store( AHeapAny | all_pointees(inst), AHeapAny | all_pointees(inst) )); case PropX: case PropDX: case PropQ: case BindProp: case SetOpProp: case VGetProp: return minstr_with_tvref(inst); /* * Collection accessors can read from their inner array buffer, but stores * COW and behave as if they only affect collection memory locations. We * don't track those, so it's returning AEmpty for now. */ case MapIsset: case PairIsset: case VectorDoCow: case VectorIsset: return may_load_store(AHeapAny, AEmpty /* Note */); case MapGet: case MapSet: return may_reenter(inst, may_load_store(AHeapAny, AEmpty /* Note */)); ////////////////////////////////////////////////////////////////////// // Instructions that allocate new objects, without reading any other memory // at all, so any effects they have on some types of memory locations we // track are isolated from anything else we care about. case NewArray: case NewCol: case NewInstanceRaw: case NewMixedArray: case AllocPackedArray: case ConvBoolToArr: case ConvDblToStr: case ConvDblToArr: case ConvIntToArr: case ConvIntToStr: case Box: // conditional allocation return IrrelevantEffects {}; case AllocObj: // AllocObj re-enters to call constructors, but if it weren't for that we // could ignore its loads and stores since it's a new object. return may_reenter(inst, may_load_store(AEmpty, AEmpty)); ////////////////////////////////////////////////////////////////////// // Instructions that explicitly manipulate the stack. case LdStk: return PureLoad { AStack { inst.src(0), inst.extra<LdStk>()->offset.offset, 1 } }; case StStk: return PureStore { AStack { inst.src(0), inst.extra<StStk>()->offset.offset, 1 }, inst.src(1) }; case SpillFrame: { auto const spOffset = inst.extra<SpillFrame>()->spOffset; return PureSpillFrame { AStack { inst.src(0), // SpillFrame's spOffset is to the bottom of where it will store the // ActRec, but AliasClass needs an offset to the highest cell it will // store. spOffset.offset + int32_t{kNumActRecCells} - 1, int32_t{kNumActRecCells} }, AStack { inst.src(0), // The context is in the highest slot. spOffset.offset + int32_t{kNumActRecCells} - 1, 1 } }; } case CheckStk: return may_load_store( AStack { inst.src(0), inst.extra<CheckStk>()->irSpOffset.offset, 1 }, AEmpty ); case CufIterSpillFrame: return may_load_store(AEmpty, AStackAny); // The following may re-enter, and also deal with a stack slot. case CastStk: { auto const stk = AStack { inst.src(0), inst.extra<CastStk>()->offset.offset, 1 }; return may_raise(inst, may_load_store(stk, stk)); } case CoerceStk: { auto const stk = AStack { inst.src(0), inst.extra<CoerceStk>()->offset.offset, 1 }; return may_raise(inst, may_load_store(stk, stk)); } case CastMem: case CoerceMem: { auto aInst = inst.src(0)->inst(); if (aInst->is(LdLocAddr)) { return may_raise(inst, may_load_store(AFrameAny, AFrameAny)); } return may_raise(inst, may_load_store(AUnknown, AUnknown)); } case LdARFuncPtr: // This instruction is essentially a PureLoad, but we don't handle non-TV's // in PureLoad so we have to treat it as may_load_store. We also treat it // as loading an entire ActRec-sized part of the stack, although it only // loads the slot containing the Func. return may_load_store( AStack { inst.src(0), inst.extra<LdARFuncPtr>()->offset.offset + int32_t{kNumActRecCells} - 1, int32_t{kNumActRecCells} }, AEmpty ); ////////////////////////////////////////////////////////////////////// // Instructions that never do anything to memory case AssertStk: case HintStkInner: case AbsDbl: case AddDbl: case AddInt: case AddIntO: case AndInt: case AssertLoc: case AssertType: case DefFP: case DefSP: case EndGuards: case EqBool: case EqCls: case EqDbl: case EqInt: case GteBool: case GteInt: case GtBool: case GtInt: case HintLocInner: case Jmp: case JmpNZero: case JmpZero: case LdPropAddr: case LdStkAddr: case LdPackedArrayElemAddr: case LteBool: case LteDbl: case LteInt: case LtBool: case LtInt: case GtDbl: case GteDbl: case LtDbl: case DivDbl: case DivInt: case MulDbl: case MulInt: case MulIntO: case NeqBool: case NeqDbl: case NeqInt: case SameObj: case NSameObj: case EqRes: case NeqRes: case CmpBool: case CmpInt: case CmpDbl: case SubDbl: case SubInt: case SubIntO: case XorBool: case XorInt: case OrInt: case AssertNonNull: case CheckNonNull: case CheckNullptr: case Ceil: case Floor: case DefLabel: case CheckInit: case Nop: case Mod: case Conjure: case Halt: case ConvBoolToInt: case ConvBoolToDbl: case DbgAssertType: case DbgAssertFunc: case DefConst: case LdLocAddr: case Sqrt: case LdResumableArObj: case Shl: case Shr: case IsNType: case IsType: case Mov: case ConvClsToCctx: case ConvDblToBool: case ConvDblToInt: case IsScalarType: case LdMIStateAddr: case LdPairBase: case LdStaticLocCached: case CheckCtxThis: case CastCtxThis: case LdARNumParams: case LdRDSAddr: case ExitPlaceholder: case CheckRange: case ProfileObjClass: case LdIfaceMethod: case InstanceOfIfaceVtable: case CheckARMagicFlag: case LdARNumArgsAndFlags: case StARNumArgsAndFlags: case LdTVAux: case StTVAux: case LdARInvName: case StARInvName: case MethodExists: return IrrelevantEffects {}; ////////////////////////////////////////////////////////////////////// // Instructions that technically do some things w/ memory, but not in any way // we currently care about. They however don't return IrrelevantEffects // because we assume (in refcount-opts) that IrrelevantEffects instructions // can't even inspect Countable reference count fields, and several of these // can. All GeneralEffects instructions are assumed to possibly do so. case DecRefNZ: case AFWHBlockOn: case IncRef: case IncRefCtx: case LdClosureCtx: case StClosureCtx: case StClosureArg: case StContArKey: case StContArValue: case StRetVal: case ConvStrToInt: case ConvResToInt: case OrdStr: case CreateSSWH: case NewLikeArray: case CheckRefs: case LdClsCctx: case BeginCatch: case CheckSurpriseFlags: case CheckType: case FreeActRec: case RegisterLiveObj: case StContArResume: case StContArState: case ZeroErrorLevel: case RestoreErrorLevel: case CheckCold: case CheckInitProps: case CheckInitSProps: case ContArIncIdx: case ContArIncKey: case ContArUpdateIdx: case ContValid: case ContStarted: case IncProfCounter: case IncStat: case IncStatGrouped: case CountBytecode: case ContPreNext: case ContStartedCheck: case ConvArrToBool: case ConvArrToDbl: case ConvArrToInt: case NewColFromArray: case ConvBoolToStr: case CountArray: case CountArrayFast: case StAsyncArResult: case StAsyncArResume: case StAsyncArSucceeded: case InstanceOf: case InstanceOfBitmask: case NInstanceOfBitmask: case InstanceOfIface: case InterfaceSupportsArr: case InterfaceSupportsDbl: case InterfaceSupportsInt: case InterfaceSupportsStr: case IsWaitHandle: case IsCol: case HasToString: case DbgAssertRefCount: case GtStr: case GteStr: case LtStr: case LteStr: case EqStr: case NeqStr: case SameStr: case NSameStr: case CmpStr: case GtStrInt: case GteStrInt: case LtStrInt: case LteStrInt: case EqStrInt: case NeqStrInt: case CmpStrInt: case SameArr: case NSameArr: case GtRes: case GteRes: case LtRes: case LteRes: case CmpRes: case IncTransCounter: case LdBindAddr: case LdAsyncArParentChain: case LdSSwitchDestFast: case RBTraceEntry: case RBTraceMsg: case ConvIntToBool: case ConvIntToDbl: case ConvStrToArr: // decrefs src, but src is a string case ConvStrToBool: case ConvStrToDbl: case ConvResToDbl: case DerefClsRDSHandle: case EagerSyncVMRegs: case ExtendsClass: case LdUnwinderValue: case GetCtxFwdCall: case LdCtx: case LdCctx: case LdClosure: case LdClsName: case LdAFWHActRec: case LdClsCtx: case LdContActRec: case LdContArKey: case LdContArValue: case LdContField: case LdContResumeAddr: case LdClsCachedSafe: case LdClsInitData: case UnwindCheckSideExit: case LdCns: case LdClsMethod: case LdClsMethodCacheCls: case LdClsMethodCacheFunc: case LdClsMethodFCacheFunc: case ProfilePackedArray: case ProfileStructArray: case ProfileSwitchDest: case LdFuncCachedSafe: case LdFuncNumParams: case LdGblAddr: case LdGblAddrDef: case LdObjClass: case LdObjInvoke: case LdStrLen: case StringIsset: case LdSwitchDblIndex: case LdSwitchStrIndex: case LdVectorBase: case LdWHResult: case LdWHState: case LookupClsRDSHandle: case GetCtxFwdCallDyn: case DbgTraceCall: case InitCtx: case PackMagicArgs: return may_load_store(AEmpty, AEmpty); // Some that touch memory we might care about later, but currently don't: case CheckStaticLocInit: case StaticLocInitCached: case ColIsEmpty: case ColIsNEmpty: case ConvCellToBool: case ConvObjToBool: case CountCollection: case LdVectorSize: case VectorHasImmCopy: case CheckPackedArrayBounds: case LdColArray: case EnterFrame: return may_load_store(AEmpty, AEmpty); ////////////////////////////////////////////////////////////////////// // Instructions that can re-enter the VM and touch most heap things. They // also may generally write to the eval stack below an offset (see // alias-class.h above AStack for more). case DecRef: { auto const src = inst.src(0); // It could decref the inner ref. auto const maybeRef = src->isA(TBoxedCell) ? ARef { src } : src->type().maybe(TBoxedCell) ? ARefAny : AEmpty; // Need to add maybeRef to the `store' set. See comments about // `GeneralEffects' in memory-effects.h. auto const effect = may_load_store(maybeRef, maybeRef); if (inst.src(0)->type().maybe(TArr | TObj | TBoxedArr | TBoxedObj)) { // Could re-enter to run a destructor. return may_reenter(inst, effect); } return effect; } case LdArrFPushCuf: // autoloads case LdArrFuncCtx: // autoloads case LdObjMethod: // can't autoload, but can decref $this right now case LdStrFPushCuf: // autoload /* * Note that these instructions make stores to a pre-live actrec on the * eval stack. * * It is probably safe for these instructions to have may-load only from * the portion of the evaluation stack below the actrec they are * manipulating, but since there's always going to be either a Call or a * region exit following it it doesn't help us eliminate anything for now, * so we just pretend it can read/write anything on the stack. */ return may_raise(inst, may_load_store(AStackAny, AStackAny)); case LookupClsMethod: // autoload, and it writes part of the new actrec { AliasClass effects = AStack { inst.src(2), inst.extra<LookupClsMethod>()->offset.offset, int32_t{kNumActRecCells} }; return may_raise(inst, may_load_store(effects, effects)); } case LdClsPropAddrOrNull: // may run 86{s,p}init, which can autoload case LdClsPropAddrOrRaise: // raises errors, and 86{s,p}init case BaseG: case Clone: case RaiseArrayIndexNotice: case RaiseArrayKeyNotice: case RaiseUninitLoc: case RaiseUndefProp: case RaiseMissingArg: case RaiseError: case RaiseNotice: case RaiseWarning: case ConvCellToStr: case ConvObjToStr: case Count: // re-enters on CountableClass case CIterFree: // decrefs context object in iter case MIterFree: case IterFree: case GtObj: case GteObj: case LtObj: case LteObj: case EqObj: case NeqObj: case CmpObj: case GtArr: case GteArr: case LtArr: case LteArr: case EqArr: case NeqArr: case CmpArr: case DecodeCufIter: case ConvCellToArr: // decrefs src, may read obj props case ConvCellToObj: // decrefs src case ConvObjToArr: // decrefs src case GenericIdx: case InitProps: case InitSProps: case OODeclExists: case LdCls: // autoload case LdClsCached: // autoload case LdFunc: // autoload case LdFuncCached: // autoload case LdFuncCachedU: // autoload case LdSwitchObjIndex: // decrefs arg case LookupClsCns: // autoload case LookupClsMethodCache: // autoload case LookupClsMethodFCache: // autoload case LookupCns: case LookupCnsE: case LookupCnsU: case StringGet: // raise_warning case ArrayAdd: // decrefs source case AddElemIntKey: // decrefs value case AddElemStrKey: // decrefs value case AddNewElem: // decrefs value case ArrayGet: // kVPackedKind warnings case ArrayIsset: // kVPackedKind warnings case ArraySet: // kVPackedKind warnings case ArraySetRef: // kVPackedKind warnings case ElemArray: case ElemArrayW: case GetMemoKey: // re-enters to call getInstanceKey() in some cases case LdClsCtor: case ConcatStrStr: case PrintStr: case PrintBool: case PrintInt: case ConcatIntStr: case ConcatStrInt: case LdSSwitchDestSlow: case ConvObjToDbl: case ConvObjToInt: case MapAddElemC: case ColAddNewElemC: case CoerceStrToInt: case CoerceStrToDbl: case CoerceCellToDbl: case CoerceCellToInt: case CoerceCellToBool: case ConvCellToInt: case ConvResToStr: case ConcatStr3: case ConcatStr4: case ConvCellToDbl: case ThrowOutOfBounds: case ThrowInvalidOperation: case ThrowArithmeticError: case ThrowDivisionByZeroError: return may_raise(inst, may_load_store(AHeapAny, AHeapAny)); case ReleaseVVAndSkip: // can decref ExtraArgs or VarEnv and Locals return may_reenter(inst, may_load_store(AHeapAny|AFrameAny, AHeapAny|AFrameAny)); // These two instructions don't touch memory we track, except that they may // re-enter to construct php Exception objects. During this re-entry anything // can happen (e.g. a surprise flag check could cause a php signal handler to // run arbitrary code). case ABCUnblock: case AFWHPrepareChild: return may_reenter(inst, may_load_store(AEmpty, AEmpty)); ////////////////////////////////////////////////////////////////////// // The following instructions are used for debugging memory optimizations. // We can't ignore them, because they can prevent future optimizations; // eg t1 = LdStk<N>; DbgTrashStk<N>; StStk<N> t1 // If we ignore the DbgTrashStk it looks like the StStk is redundant case DbgTrashStk: return GeneralEffects { AEmpty, AEmpty, AEmpty, AStack { inst.src(0), inst.extra<DbgTrashStk>()->offset.offset, 1 } }; case DbgTrashFrame: return GeneralEffects { AEmpty, AEmpty, AEmpty, AStack { inst.src(0), // SpillFrame's spOffset is to the bottom of where it will store the // ActRec, but AliasClass needs an offset to the highest cell it will // store. inst.extra<DbgTrashFrame>()->offset.offset + int32_t{kNumActRecCells} - 1, int32_t{kNumActRecCells} } }; case DbgTrashMem: return GeneralEffects { AEmpty, AEmpty, AEmpty, pointee(inst.src(0)) }; ////////////////////////////////////////////////////////////////////// } not_reached(); }
int ai_controller_poll(controller *ctrl, ctrl_event **ev) { ai *a = ctrl->data; object *o = ctrl->har; if (!o) { return 1; } har *h = object_get_userdata(o); object *o_enemy = game_state_get_player(o->gs, h->player_id == 1 ? 0 : 1)->har; // Do not run AI while the game is paused if(game_state_is_paused(o->gs)) { return 0; } // Do not run AI while match is starting or ending // XXX this prevents the AI from doing scrap/destruction moves // XXX this could be fixed by providing a "scene changed" event if(is_arena(game_state_get_scene(o->gs)->id) && arena_get_state(game_state_get_scene(o->gs)) != ARENA_STATE_FIGHTING) { // null out selected move to fix the "AI not moving problem" a->selected_move = NULL; return 0; } // Grab all projectiles on screen vector_clear(&a->active_projectiles); game_state_get_projectiles(o->gs, &a->active_projectiles); // Try to block har if(ai_block_har(ctrl, ev)) { return 0; } // Try to block projectiles if(ai_block_projectile(ctrl, ev)) { return 0; } if(a->selected_move) { // finish doing the selected move first if(a->input_lag_timer > 0) { a->input_lag_timer--; } else { a->move_str_pos--; if(a->move_str_pos <= 0) { a->move_str_pos = 0; } a->input_lag_timer = a->input_lag; } int ch = str_at(&a->selected_move->move_string, a->move_str_pos); controller_cmd(ctrl, char_to_act(ch, o->direction), ev); } else if(rand_int(100) < a->difficulty) { af_move *selected_move = NULL; int top_value = 0; // Attack for(int i = 0; i < 70; i++) { af_move *move = NULL; if((move = af_get_move(h->af_data, i))) { move_stat *ms = &a->move_stats[i]; if(is_valid_move(move, h)) { int value = ms->value + rand_int(10); if (ms->min_hit_dist != -1){ if (ms->last_dist < ms->max_hit_dist+5 && ms->last_dist > ms->min_hit_dist+5){ value += 2; } else if (ms->last_dist > ms->max_hit_dist+10){ value -= 3; } } value -= ms->attempts/2; value -= ms->consecutive*2; if (is_special_move(move) && !maybe(a->difficulty)) { DEBUG("skipping special move %s because of difficulty", str_c(&move->move_string)); continue; } if (selected_move == NULL){ selected_move = move; top_value = value; } else if (value > top_value) { selected_move = move; top_value = value; } } } } for(int i = 0; i < 70; i++) { a->move_stats[i].consecutive /= 2; } if(selected_move) { a->move_stats[selected_move->id].attempts++; a->move_stats[selected_move->id].consecutive++; // do the move a->selected_move = selected_move; a->move_str_pos = str_size(&selected_move->move_string)-1; a->move_stats[a->selected_move->id].last_dist = fabsf(o->pos.x - o_enemy->pos.x); a->blocked = 0; DEBUG("AI selected move %s", str_c(&selected_move->move_string)); } } else { // Change action after 30 ticks if(a->act_timer <= 0 && rand_int(100) > 88){ int p = rand_int(100); if(p > 40){ // walk forward a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_RIGHT : ACT_LEFT); } else if(p > 20){ // walk backward a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_LEFT : ACT_RIGHT); } else if(p > 10){ // do nothing a->cur_act = ACT_STOP; } else { // crouch and block a->cur_act = (o->direction == OBJECT_FACE_RIGHT ? ACT_DOWN|ACT_LEFT : ACT_DOWN|ACT_RIGHT); } a->act_timer = 30; controller_cmd(ctrl, a->cur_act, ev); } else { a->act_timer--; } // Jump once in a while if(rand_int(100) == 88){ if(o->vel.x < 0) { controller_cmd(ctrl, ACT_UP|ACT_LEFT, ev); } else if(o->vel.x > 0) { controller_cmd(ctrl, ACT_UP|ACT_RIGHT, ev); } else { controller_cmd(ctrl, ACT_UP, ev); } } } return 0; }
TEST(AliasClass, IterUnion) { IRUnit unit{test_context}; auto const marker = BCMarker::Dummy(); auto const FP = unit.gen(DefFP, marker)->dst(); { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; auto const u1 = iterP0 | iterP1; EXPECT_EQ(u1, AIterPosAny); EXPECT_TRUE(iterP0 <= AIterPosAny); EXPECT_FALSE(iterP0 <= AIterBaseAny); } { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterB0 = AIterBase { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; auto const u1 = iterP0 | iterB0; EXPECT_TRUE(iterP0 <= u1); EXPECT_TRUE(iterB0 <= u1); EXPECT_FALSE(u1 <= AIterPosAny); EXPECT_FALSE(u1 <= AIterBaseAny); EXPECT_TRUE(u1 <= (AIterPosAny | AIterBaseAny)); EXPECT_FALSE(iterP1 <= u1); EXPECT_FALSE(iterP1 <= iterP0); EXPECT_FALSE(iterP1 <= iterB0); EXPECT_TRUE(!!u1.iterPos()); EXPECT_TRUE(!!u1.iterBase()); EXPECT_TRUE(!u1.is_iterPos()); EXPECT_TRUE(!u1.is_iterBase()); } { AliasClass const local = AFrame { FP, 0 }; AliasClass const iter = AIterPos { FP, 0 }; auto const u1 = local | iter; EXPECT_TRUE(local <= u1); EXPECT_TRUE(iter <= u1); EXPECT_FALSE(!!u1.is_iterPos()); EXPECT_FALSE(!!u1.is_frame()); EXPECT_TRUE(!!u1.frame()); // locals are preferred in unions to iters EXPECT_FALSE(!!u1.iterPos()); } { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterB0 = AIterBase { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; AliasClass const iterB1 = AIterBase { FP, 1 }; EXPECT_FALSE(iterP0.maybe(iterP1)); EXPECT_FALSE(iterB0.maybe(iterB1)); auto const u1 = iterP0 | iterB0; auto const u2 = iterP1 | iterB1; EXPECT_FALSE(u1 == u2); EXPECT_FALSE(u1.maybe(u2)); EXPECT_FALSE(u1 <= u2); EXPECT_FALSE(u2 <= u1); EXPECT_TRUE(iterB1 <= u2); EXPECT_TRUE(iterP1 <= u2); EXPECT_FALSE(iterP0 <= u2); EXPECT_FALSE(iterB0 <= u2); auto const u3 = u1 | iterP1; EXPECT_FALSE(!!u3.iterPos()); EXPECT_FALSE(!!u3.iterBase()); EXPECT_TRUE(iterP1 <= u3); EXPECT_TRUE(iterP0 <= u3); EXPECT_TRUE(iterB0 <= u3); EXPECT_TRUE(u1 <= u3); EXPECT_TRUE(u2.maybe(u3)); // u2 <= u3 isn't 'really' true, but operator| is conservative and makes u3 // too big for that right now. EXPECT_TRUE(!u1.precise_union(iterP1)); } }
int main(int argc, char *argv[]) { uint8_t buf[SECRET_BITS/8 + SCRATCHCODES*BYTES_PER_SCRATCHCODE]; static const char hotp[] = "\" HOTP_COUNTER 1\n"; static const char totp[] = "\" TOTP_AUTH\n"; static const char disallow[] = "\" DISALLOW_REUSE\n"; static const char window[] = "\" WINDOW_SIZE 17\n"; static const char ratelimit[] = "\" RATE_LIMIT 3 30\n"; char secret[(SECRET_BITS + BITS_PER_BASE32_CHAR-1)/BITS_PER_BASE32_CHAR + 1 /* newline */ + sizeof(hotp) + // hotp and totp are mutually exclusive. sizeof(disallow) + sizeof(window) + sizeof(ratelimit) + 5 + // NN MMM (total of five digits) SCRATCHCODE_LENGTH*(SCRATCHCODES + 1 /* newline */) + 1 /* NUL termination character */]; enum { ASK_MODE, HOTP_MODE, TOTP_MODE } mode = ASK_MODE; enum { ASK_REUSE, DISALLOW_REUSE, ALLOW_REUSE } reuse = ASK_REUSE; int force = 0, quiet = 0; int r_limit = 0, r_time = 0; char *secret_fn = NULL; char *label = NULL; int window_size = 0; int idx; for (;;) { static const char optstring[] = "+hctdDfl:qQ:r:R:us:w:W"; static struct option options[] = { { "help", 0, 0, 'h' }, { "counter-based", 0, 0, 'c' }, { "time-based", 0, 0, 't' }, { "disallow-reuse", 0, 0, 'd' }, { "allow-reuse", 0, 0, 'D' }, { "force", 0, 0, 'f' }, { "label", 1, 0, 'l' }, { "quiet", 0, 0, 'q' }, { "qr-mode", 1, 0, 'Q' }, { "rate-limit", 1, 0, 'r' }, { "rate-time", 1, 0, 'R' }, { "no-rate-limit", 0, 0, 'u' }, { "secret", 1, 0, 's' }, { "window-size", 1, 0, 'w' }, { "minimal-window", 0, 0, 'W' }, { 0, 0, 0, 0 } }; idx = -1; int c = getopt_long(argc, argv, optstring, options, &idx); if (c > 0) { for (int i = 0; options[i].name; i++) { if (options[i].val == c) { idx = i; break; } } } else if (c < 0) { break; } if (idx-- <= 0) { // Help (or invalid argument) err: usage(); if (idx < -1) { fprintf(stderr, "Failed to parse command line\n"); _exit(1); } exit(0); } else if (!idx--) { // counter-based if (mode != ASK_MODE) { fprintf(stderr, "Duplicate -c and/or -t option detected\n"); _exit(1); } if (reuse != ASK_REUSE) { reuse_err: fprintf(stderr, "Reuse of tokens is not a meaningful parameter " "when in counter-based mode\n"); _exit(1); } mode = HOTP_MODE; } else if (!idx--) { // time-based if (mode != ASK_MODE) { fprintf(stderr, "Duplicate -c and/or -t option detected\n"); _exit(1); } mode = TOTP_MODE; } else if (!idx--) { // disallow-reuse if (reuse != ASK_REUSE) { fprintf(stderr, "Duplicate -d and/or -D option detected\n"); _exit(1); } if (mode == HOTP_MODE) { goto reuse_err; } reuse = DISALLOW_REUSE; } else if (!idx--) { // allow-reuse if (reuse != ASK_REUSE) { fprintf(stderr, "Duplicate -d and/or -D option detected\n"); _exit(1); } if (mode == HOTP_MODE) { goto reuse_err; } reuse = ALLOW_REUSE; } else if (!idx--) { // force if (force) { fprintf(stderr, "Duplicate -f option detected\n"); _exit(1); } force = 1; } else if (!idx--) { // label if (label) { fprintf(stderr, "Duplicate -l option detected\n"); _exit(1); } label = strdup(optarg); } else if (!idx--) { // quiet if (quiet) { fprintf(stderr, "Duplicate -q option detected\n"); _exit(1); } quiet = 1; } else if (!idx--) { // qr-mode if (qr_mode != QR_UNSET) { fprintf(stderr, "Duplicate -Q option detected\n"); _exit(1); } if (!strcasecmp(optarg, "none")) { qr_mode = QR_NONE; } else if (!strcasecmp(optarg, "ansi")) { qr_mode = QR_ANSI; } else if (!strcasecmp(optarg, "utf8")) { qr_mode = QR_UTF8; } else { fprintf(stderr, "Invalid qr-mode \"%s\"\n", optarg); _exit(1); } } else if (!idx--) { // rate-limit if (r_limit > 0) { fprintf(stderr, "Duplicate -r option detected\n"); _exit(1); } else if (r_limit < 0) { fprintf(stderr, "-u is mutually exclusive with -r\n"); _exit(1); } char *endptr; errno = 0; long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 1 || l > 10) { fprintf(stderr, "-r requires an argument in the range 1..10\n"); _exit(1); } r_limit = (int)l; } else if (!idx--) { // rate-time if (r_time > 0) { fprintf(stderr, "Duplicate -R option detected\n"); _exit(1); } else if (r_time < 0) { fprintf(stderr, "-u is mutually exclusive with -R\n"); _exit(1); } char *endptr; errno = 0; long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 15 || l > 600) { fprintf(stderr, "-R requires an argument in the range 15..600\n"); _exit(1); } r_time = (int)l; } else if (!idx--) { // no-rate-limit if (r_limit > 0 || r_time > 0) { fprintf(stderr, "-u is mutually exclusive with -r/-R\n"); _exit(1); } if (r_limit < 0) { fprintf(stderr, "Duplicate -u option detected\n"); _exit(1); } r_limit = r_time = -1; } else if (!idx--) { // secret if (secret_fn) { fprintf(stderr, "Duplicate -s option detected\n"); _exit(1); } if (!*optarg) { fprintf(stderr, "-s must be followed by a filename\n"); _exit(1); } secret_fn = strdup(optarg); if (!secret_fn) { perror("malloc()"); _exit(1); } } else if (!idx--) { // window-size if (window_size) { fprintf(stderr, "Duplicate -w/-W option detected\n"); _exit(1); } char *endptr; errno = 0; long l = strtol(optarg, &endptr, 10); if (errno || endptr == optarg || *endptr || l < 1 || l > 21) { fprintf(stderr, "-w requires an argument in the range 1..21\n"); _exit(1); } window_size = (int)l; } else if (!idx--) { // minimal-window if (window_size) { fprintf(stderr, "Duplicate -w/-W option detected\n"); _exit(1); } window_size = -1; } else { fprintf(stderr, "Error\n"); _exit(1); } } idx = -1; if (optind != argc) { goto err; } if (reuse != ASK_REUSE && mode != TOTP_MODE) { fprintf(stderr, "Must select time-based mode, when using -d or -D\n"); _exit(1); } if ((r_time && !r_limit) || (!r_time && r_limit)) { fprintf(stderr, "Must set -r when setting -R, and vice versa\n"); _exit(1); } if (!label) { uid_t uid = getuid(); const char *user = getUserName(uid); char hostname[128] = { 0 }; if (gethostname(hostname, sizeof(hostname)-1)) { strcpy(hostname, "unix"); } label = strcat(strcat(strcpy(malloc(strlen(user) + strlen(hostname) + 2), user), "@"), hostname); free((char *)user); } int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { perror("Failed to open \"/dev/urandom\""); return 1; } if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { urandom_failure: perror("Failed to read from \"/dev/urandom\""); return 1; } base32_encode(buf, SECRET_BITS/8, (uint8_t *)secret, sizeof(secret)); int use_totp; if (mode == ASK_MODE) { use_totp = maybe("Do you want authentication tokens to be time-based"); } else { use_totp = mode == TOTP_MODE; } if (!quiet) { displayQRCode(secret, label, use_totp); printf("Your new secret key is: %s\n", secret); printf("Your verification code is %06d\n", generateCode(secret, 0)); printf("Your emergency scratch codes are:\n"); } free(label); strcat(secret, "\n"); if (use_totp) { strcat(secret, totp); } else { strcat(secret, hotp); } for (int i = 0; i < SCRATCHCODES; ++i) { new_scratch_code:; int scratch = 0; for (int j = 0; j < BYTES_PER_SCRATCHCODE; ++j) { scratch = 256*scratch + buf[SECRET_BITS/8 + BYTES_PER_SCRATCHCODE*i + j]; } int modulus = 1; for (int j = 0; j < SCRATCHCODE_LENGTH; j++) { modulus *= 10; } scratch = (scratch & 0x7FFFFFFF) % modulus; if (scratch < modulus/10) { // Make sure that scratch codes are always exactly eight digits. If they // start with a sequence of zeros, just generate a new scratch code. if (read(fd, buf + (SECRET_BITS/8 + BYTES_PER_SCRATCHCODE*i), BYTES_PER_SCRATCHCODE) != BYTES_PER_SCRATCHCODE) { goto urandom_failure; } goto new_scratch_code; } if (!quiet) { printf(" %08d\n", scratch); } snprintf(strrchr(secret, '\000'), sizeof(secret) - strlen(secret), "%08d\n", scratch); } close(fd); if (!secret_fn) { char *home = getenv("HOME"); if (!home || *home != '/') { fprintf(stderr, "Cannot determine home directory\n"); return 1; } secret_fn = malloc(strlen(home) + strlen(SECRET) + 1); if (!secret_fn) { perror("malloc()"); _exit(1); } strcat(strcpy(secret_fn, home), SECRET); } if (!force) { printf("\nDo you want me to update your \"%s\" file (y/n) ", secret_fn); fflush(stdout); char ch; do { ch = getchar(); } while (ch == ' ' || ch == '\r' || ch == '\n'); if (ch != 'y' && ch != 'Y') { exit(0); } } secret_fn = realloc(secret_fn, 2*strlen(secret_fn) + 3); if (!secret_fn) { perror("malloc()"); _exit(1); } char *tmp_fn = strrchr(secret_fn, '\000') + 1; strcat(strcpy(tmp_fn, secret_fn), "~"); // Add optional flags. if (use_totp) { if (reuse == ASK_REUSE) { maybeAddOption("Do you want to disallow multiple uses of the same " "authentication\ntoken? This restricts you to one login " "about every 30s, but it increases\nyour chances to " "notice or even prevent man-in-the-middle attacks", secret, sizeof(secret), disallow); } else if (reuse == DISALLOW_REUSE) { addOption(secret, sizeof(secret), disallow); } if (!window_size) { maybeAddOption("By default, tokens are good for 30 seconds and in order " "to compensate for\npossible time-skew between the " "client and the server, we allow an extra\ntoken before " "and after the current time. If you experience problems " "with poor\ntime synchronization, you can increase the " "window from its default\nsize of 1:30min to about 4min. " "Do you want to do so", secret, sizeof(secret), window); } else { char buf[80]; sprintf(buf, "\" WINDOW_SIZE %d\n", window_size); addOption(secret, sizeof(secret), buf); } } else { if (!window_size) { maybeAddOption("By default, three tokens are valid at any one time. " "This accounts for\ngenerated-but-not-used tokens and " "failed login attempts. In order to\ndecrease the " "likelihood of synchronization problems, this window " "can be\nincreased from its default size of 3 to 17. Do " "you want to do so", secret, sizeof(secret), window); } else { char buf[80]; sprintf(buf, "\" WINDOW_SIZE %d\n", window_size > 0 ? window_size : use_totp ? 3 : 1); addOption(secret, sizeof(secret), buf); } } if (!r_limit && !r_time) { maybeAddOption("If the computer that you are logging into isn't hardened " "against brute-force\nlogin attempts, you can enable " "rate-limiting for the authentication module.\nBy default, " "this limits attackers to no more than 3 login attempts " "every 30s.\nDo you want to enable rate-limiting", secret, sizeof(secret), ratelimit); } else if (r_limit > 0 && r_time > 0) { char buf[80]; sprintf(buf, "\"RATE_LIMIT %d %d\n", r_limit, r_time); addOption(secret, sizeof(secret), buf); } fd = open(tmp_fn, O_WRONLY|O_EXCL|O_CREAT|O_NOFOLLOW|O_TRUNC, 0400); if (fd < 0) { fprintf(stderr, "Failed to create \"%s\" (%s)", secret_fn, strerror(errno)); free(secret_fn); return 1; } if (write(fd, secret, strlen(secret)) != (ssize_t)strlen(secret) || rename(tmp_fn, secret_fn)) { perror("Failed to write new secret"); unlink(secret_fn); close(fd); free(secret_fn); return 1; } free(secret_fn); close(fd); return 0; }
T maybe_gen( const optional<T>& op ) { return maybe( op, detail::pass(), detail::gen() ); }
cbool parseEOL(parse *p){ return ( parseThisChar(p,'\n') || (parseThisChar(p,'\r') && maybe(parseThisChar(p,'\n'))) ); }
bool LineParser::maybeChar(QChar c) { return maybe([&]() { this->expect(c); }); }
T maybe_gen( const optional<T>& op, TGx&& defaultgen, TFx&& fx ) { maybe( op, fx, defaultgen ); }
friend spure maybe max(maybe a, maybe b) { return a.check() && b.check() ? maybe(std::max(a.m_, b.m_)) : nothing(); }
TEST(AliasClass, SpecializedUnions) { IRUnit unit{test_context}; auto const marker = BCMarker::Dummy(); auto const FP = unit.gen(DefFP, marker)->dst(); AliasClass const stk = AStack { FP, -10, 3 }; AliasClass const unrelated_stk = AStack { FP, -14, 1 }; AliasClass const related_stk = AStack { FP, -11, 2 }; auto const stk_and_frame = stk | AFrameAny; EXPECT_TRUE(!stk_and_frame.is_stack()); EXPECT_TRUE(AFrameAny <= stk_and_frame); EXPECT_TRUE(stk <= stk_and_frame); EXPECT_TRUE(AStackAny.maybe(stk_and_frame)); EXPECT_TRUE(AFrameAny.maybe(stk_and_frame)); EXPECT_FALSE(unrelated_stk <= stk_and_frame); EXPECT_FALSE(stk_and_frame.maybe(unrelated_stk)); auto const stk_and_prop = stk | APropAny; EXPECT_TRUE(stk_and_prop.maybe(stk_and_frame)); EXPECT_TRUE(stk_and_frame.maybe(stk_and_prop)); EXPECT_FALSE(stk_and_prop <= stk_and_frame); EXPECT_FALSE(stk_and_frame <= stk_and_prop); EXPECT_TRUE(APropAny.maybe(stk_and_prop)); EXPECT_TRUE(AStackAny.maybe(stk_and_prop)); auto const unrelated_stk_and_prop = unrelated_stk | APropAny; EXPECT_FALSE(stk_and_frame.maybe(unrelated_stk_and_prop)); EXPECT_FALSE(unrelated_stk_and_prop.maybe(stk_and_frame)); EXPECT_TRUE(unrelated_stk_and_prop.maybe(stk_and_prop)); // because of prop EXPECT_FALSE(unrelated_stk_and_prop <= stk_and_prop); EXPECT_FALSE(stk_and_prop <= unrelated_stk_and_prop); EXPECT_FALSE(unrelated_stk_and_prop <= stk_and_frame); EXPECT_FALSE(stk_and_frame <= unrelated_stk_and_prop); EXPECT_FALSE(stk_and_prop <= AHeapAny); EXPECT_TRUE(stk_and_prop.maybe(AHeapAny)); EXPECT_FALSE(stk_and_frame <= AHeapAny); EXPECT_FALSE(stk_and_frame.maybe(AHeapAny)); auto const rel_stk_and_frame = related_stk | AFrameAny; EXPECT_TRUE(stk_and_frame.maybe(rel_stk_and_frame)); EXPECT_TRUE(rel_stk_and_frame.maybe(stk_and_frame)); EXPECT_TRUE(related_stk <= stk); EXPECT_TRUE(rel_stk_and_frame <= stk_and_frame); EXPECT_FALSE(stk_and_frame <= rel_stk_and_frame); EXPECT_TRUE(rel_stk_and_frame.maybe(stk_and_prop)); EXPECT_TRUE(stk_and_prop.maybe(rel_stk_and_frame)); EXPECT_FALSE(rel_stk_and_frame <= stk_and_prop); auto const some_mis = AMIStateTvRef; { auto const some_heap = AElemIAny; auto const u1 = some_heap | some_mis; auto const u2 = AFrameAny | u1; EXPECT_TRUE((AHeapAny | some_heap) == AHeapAny); EXPECT_TRUE(AHeapAny <= (AHeapAny | u1)); EXPECT_TRUE(AHeapAny <= (AHeapAny | u2)); } auto const mis_stk = some_mis | stk; auto const mis_stk_any = AStackAny | mis_stk; EXPECT_EQ(some_mis, AliasClass{*mis_stk_any.mis()}); EXPECT_NE(mis_stk_any, AStackAny | AMIStateAny); auto const other_mis = AMIStateBase; EXPECT_LE(some_mis, some_mis | other_mis); EXPECT_LE(other_mis, some_mis | other_mis); EXPECT_NE(some_mis, some_mis | other_mis); EXPECT_NE(other_mis, some_mis | other_mis); }
static constexpr spure maybe nothing() { return maybe(false); }
Spelling Note::SpellNote(int mapped_midi) { // Convert to pitch class, octave represnetation int pitch_class_mult = (mapped_midi - 1); int pitch_class = pitch_class_mult%12; // Octave from Middle C to higher C considered octave 0 int octave = (int)(pitch_class_mult / 12) - 1; int space; int accid = 0; // for only sharp representation // first special end cases: if ( mapped_midi == 47) { space = 13; accid = 1; } else if ( mapped_midi == 0) { space = -14; accid = -1; } else if (pitch_class == 0) { space = 0; } else if (pitch_class == 1) { if (maybe() || maybe()) { space = 0; accid = 1; } else { space = 1; accid = -1; } } else if (pitch_class == 2) { space = 1; } else if (pitch_class == 3) { if (maybe() && maybe()) { space = 1; accid = 1; } else { space = 2; accid = -1; } } else if (pitch_class == 4) { space = 2; } else if (pitch_class == 5) { space = 3; } else if (pitch_class == 6) { if (maybe() || maybe() || maybe()) { space = 3; accid = 1; } else { space = 4; accid = -1; } } else if (pitch_class == 7) { space = 4; } else if (pitch_class == 8) { if (maybe()) { space = 4; accid = 1; } else { space = 5; accid = -1; } } else if (pitch_class == 9) { space = 5; } else if (pitch_class == 10) { if (maybe() && maybe() && maybe()) { space = 5; accid = 1; } else { space = 6; accid = -1; } } else { space = 6; } Spelling note_spelling; printf("octave is %d, space is %d, accidental is %d\n", octave, space, accid); note_spelling.staff_offset = (7 * octave) + space - 6; note_spelling.accidental = accid; return note_spelling; }
bool LineParser::maybeWhitespace() { return maybe([&]() { whitespace(); } ); }