//===--- Interp.h - Interpreter for the constexpr VM ------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Definition of the interpreter state and entry point. // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_AST_INTERP_INTERP_H #define LLVM_CLANG_AST_INTERP_INTERP_H #include "Boolean.h" #include "Function.h" #include "InterpFrame.h" #include "InterpStack.h" #include "InterpState.h" #include "Opcode.h" #include "PrimType.h" #include "Program.h" #include "State.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Expr.h" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/Endian.h" #include #include namespace clang { namespace interp { using APInt = llvm::APInt; using APSInt = llvm::APSInt; /// Convert a value to an APValue. template bool ReturnValue(const T &V, APValue &R) { R = V.toAPValue(); return true; } /// Checks if the variable has externally defined storage. bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if the array is offsetable. bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a pointer is live and accessible. bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Checks if a pointer is null. bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); /// Checks if a pointer is in range. bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); /// Checks if a field from which a pointer is going to be derived is valid. bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, CheckSubobjectKind CSK); /// Checks if a pointer points to const storage. bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a pointer points to a mutable field. bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be loaded from a block. bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be stored in a block. bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a method can be invoked on an object. bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a value can be initialized. bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Checks if a method can be called. bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F); /// Checks the 'this' pointer. bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if a method is pure virtual. bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD); /// Checks that all fields are initialized after a constructor call. bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if the shift operation is legal. template bool CheckShift(InterpState &S, CodePtr OpPC, const RT &RHS, unsigned Bits) { if (RHS.isNegative()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); return false; } // C++11 [expr.shift]p1: Shift width must be less than the bit width of // the shifted type. if (Bits > 1 && RHS >= RT::from(Bits, RHS.bitWidth())) { const Expr *E = S.Current->getExpr(OpPC); const APSInt Val = RHS.toAPSInt(); QualType Ty = E->getType(); S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits; return false; } return true; } /// Checks if Div/Rem operation on LHS and RHS is valid. template bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { if (RHS.isZero()) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_expr_divide_by_zero); return false; } if (LHS.isSigned() && LHS.isMin() && RHS.isNegative() && RHS.isMinusOne()) { APSInt LHSInt = LHS.toAPSInt(); SmallString<32> Trunc; (-LHSInt.extend(LHSInt.getBitWidth() + 1)).toString(Trunc, 10); const SourceInfo &Loc = S.Current->getSource(OpPC); const Expr *E = S.Current->getExpr(OpPC); S.CCEDiag(Loc, diag::note_constexpr_overflow) << Trunc << E->getType(); return false; } return true; } /// Interpreter entry point. bool Interpret(InterpState &S, APValue &Result); //===----------------------------------------------------------------------===// // Add, Sub, Mul //===----------------------------------------------------------------------===// template class OpAP> bool AddSubMulHelper(InterpState &S, CodePtr OpPC, unsigned Bits, const T &LHS, const T &RHS) { // Fast path - add the numbers with fixed width. T Result; if (!OpFW(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } // If for some reason evaluation continues, use the truncated results. S.Stk.push(Result); // Slow path - compute the result using another bit of precision. APSInt Value = OpAP()(LHS.toAPSInt(Bits), RHS.toAPSInt(Bits)); // Report undefined behaviour, stopping if required. const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); if (S.checkingForUndefinedBehavior()) { SmallString<32> Trunc; Value.trunc(Result.bitWidth()).toString(Trunc, 10); auto Loc = E->getExprLoc(); S.report(Loc, diag::warn_integer_constant_overflow) << Trunc << Type; return true; } else { S.CCEDiag(E, diag::note_constexpr_overflow) << Value << Type; return S.noteUndefinedBehavior(); } } template ::T> bool Add(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() + 1; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } template ::T> bool Sub(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() + 1; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } template ::T> bool Mul(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); const unsigned Bits = RHS.bitWidth() * 2; return AddSubMulHelper(S, OpPC, Bits, LHS, RHS); } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS & RHS' on the stack template ::T> bool BitAnd(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if (!T::bitAnd(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS | RHS' on the stack template ::T> bool BitOr(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if (!T::bitOr(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS ^ RHS' on the stack template ::T> bool BitXor(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); unsigned Bits = RHS.bitWidth(); T Result; if (!T::bitXor(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS % RHS' on the stack (the remainder of dividing LHS by RHS). template ::T> bool Rem(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); if (!CheckDivRem(S, OpPC, LHS, RHS)) return false; const unsigned Bits = RHS.bitWidth() * 2; T Result; if (!T::rem(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } /// 1) Pops the RHS from the stack. /// 2) Pops the LHS from the stack. /// 3) Pushes 'LHS / RHS' on the stack template ::T> bool Div(InterpState &S, CodePtr OpPC) { const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); if (!CheckDivRem(S, OpPC, LHS, RHS)) return false; const unsigned Bits = RHS.bitWidth() * 2; T Result; if (!T::div(LHS, RHS, Bits, &Result)) { S.Stk.push(Result); return true; } return false; } //===----------------------------------------------------------------------===// // Inv //===----------------------------------------------------------------------===// template ::T> bool Inv(InterpState &S, CodePtr OpPC) { using BoolT = PrimConv::T; const T &Val = S.Stk.pop(); const unsigned Bits = Val.bitWidth(); Boolean R; Boolean::inv(BoolT::from(Val, Bits), &R); S.Stk.push(R); return true; } //===----------------------------------------------------------------------===// // Neg //===----------------------------------------------------------------------===// template ::T> bool Neg(InterpState &S, CodePtr OpPC) { const T &Val = S.Stk.pop(); T Result; T::neg(Val, &Result); S.Stk.push(Result); return true; } enum class PushVal : bool { No, Yes, }; enum class IncDecOp { Inc, Dec, }; template bool IncDecHelper(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { T Value = Ptr.deref(); T Result; if constexpr (DoPush == PushVal::Yes) S.Stk.push(Result); if constexpr (Op == IncDecOp::Inc) { if (!T::increment(Value, &Result)) { Ptr.deref() = Result; return true; } } else { if (!T::decrement(Value, &Result)) { Ptr.deref() = Result; return true; } } // Something went wrong with the previous operation. Compute the // result with another bit of precision. unsigned Bits = Value.bitWidth() + 1; APSInt APResult; if constexpr (Op == IncDecOp::Inc) APResult = ++Value.toAPSInt(Bits); else APResult = --Value.toAPSInt(Bits); // Report undefined behaviour, stopping if required. const Expr *E = S.Current->getExpr(OpPC); QualType Type = E->getType(); if (S.checkingForUndefinedBehavior()) { SmallString<32> Trunc; APResult.trunc(Result.bitWidth()).toString(Trunc, 10); auto Loc = E->getExprLoc(); S.report(Loc, diag::warn_integer_constant_overflow) << Trunc << Type; return true; } S.CCEDiag(E, diag::note_constexpr_overflow) << APResult << Type; return S.noteUndefinedBehavior(); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value increased by one back to the pointer /// 4) Pushes the original (pre-inc) value on the stack. template ::T> bool Inc(InterpState &S, CodePtr OpPC) { // FIXME: Check initialization of Ptr const Pointer &Ptr = S.Stk.pop(); return IncDecHelper(S, OpPC, Ptr); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value increased by one back to the pointer template ::T> bool IncPop(InterpState &S, CodePtr OpPC) { // FIXME: Check initialization of Ptr const Pointer &Ptr = S.Stk.pop(); return IncDecHelper(S, OpPC, Ptr); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value decreased by one back to the pointer /// 4) Pushes the original (pre-dec) value on the stack. template ::T> bool Dec(InterpState &S, CodePtr OpPC) { // FIXME: Check initialization of Ptr const Pointer &Ptr = S.Stk.pop(); return IncDecHelper(S, OpPC, Ptr); } /// 1) Pops a pointer from the stack /// 2) Load the value from the pointer /// 3) Writes the value decreased by one back to the pointer template ::T> bool DecPop(InterpState &S, CodePtr OpPC) { // FIXME: Check initialization of Ptr const Pointer &Ptr = S.Stk.pop(); return IncDecHelper(S, OpPC, Ptr); } /// 1) Pops the value from the stack. /// 2) Pushes the bitwise complemented value on the stack (~V). template ::T> bool Comp(InterpState &S, CodePtr OpPC) { const T &Val = S.Stk.pop(); T Result; if (!T::comp(Val, &Result)) { S.Stk.push(Result); return true; } return false; } //===----------------------------------------------------------------------===// // EQ, NE, GT, GE, LT, LE //===----------------------------------------------------------------------===// using CompareFn = llvm::function_ref; template bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv::T; const T &RHS = S.Stk.pop(); const T &LHS = S.Stk.pop(); S.Stk.push(BoolT::from(Fn(LHS.compare(RHS)))); return true; } template bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { return CmpHelper(S, OpPC, Fn); } template <> inline bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv::T; const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); if (!Pointer::hasSameBase(LHS, RHS)) { const SourceInfo &Loc = S.Current->getSource(OpPC); S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); return false; } else { unsigned VL = LHS.getByteOffset(); unsigned VR = RHS.getByteOffset(); S.Stk.push(BoolT::from(Fn(Compare(VL, VR)))); return true; } } template <> inline bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { using BoolT = PrimConv::T; const Pointer &RHS = S.Stk.pop(); const Pointer &LHS = S.Stk.pop(); if (LHS.isZero() && RHS.isZero()) { S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Equal))); return true; } if (!Pointer::hasSameBase(LHS, RHS)) { S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Unordered))); return true; } else { unsigned VL = LHS.getByteOffset(); unsigned VR = RHS.getByteOffset(); // In our Pointer class, a pointer to an array and a pointer to the first // element in the same array are NOT equal. They have the same Base value, // but a different Offset. This is a pretty rare case, so we fix this here // by comparing pointers to the first elements. if (LHS.inArray() && LHS.isRoot()) VL = LHS.atIndex(0).getByteOffset(); if (RHS.inArray() && RHS.isRoot()) VR = RHS.atIndex(0).getByteOffset(); S.Stk.push(BoolT::from(Fn(Compare(VL, VR)))); return true; } } template ::T> bool EQ(InterpState &S, CodePtr OpPC) { return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Equal; }); } template ::T> bool NE(InterpState &S, CodePtr OpPC) { return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { return R != ComparisonCategoryResult::Equal; }); } template ::T> bool LT(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Less; }); } template ::T> bool LE(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Less || R == ComparisonCategoryResult::Equal; }); } template ::T> bool GT(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Greater; }); } template ::T> bool GE(InterpState &S, CodePtr OpPC) { return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { return R == ComparisonCategoryResult::Greater || R == ComparisonCategoryResult::Equal; }); } //===----------------------------------------------------------------------===// // InRange //===----------------------------------------------------------------------===// template ::T> bool InRange(InterpState &S, CodePtr OpPC) { const T RHS = S.Stk.pop(); const T LHS = S.Stk.pop(); const T Value = S.Stk.pop(); S.Stk.push(LHS <= Value && Value <= RHS); return true; } //===----------------------------------------------------------------------===// // Dup, Pop, Test //===----------------------------------------------------------------------===// template ::T> bool Dup(InterpState &S, CodePtr OpPC) { S.Stk.push(S.Stk.peek()); return true; } template ::T> bool Pop(InterpState &S, CodePtr OpPC) { S.Stk.pop(); return true; } //===----------------------------------------------------------------------===// // Const //===----------------------------------------------------------------------===// template ::T> bool Const(InterpState &S, CodePtr OpPC, const T &Arg) { S.Stk.push(Arg); return true; } //===----------------------------------------------------------------------===// // Get/Set Local/Param/Global/This //===----------------------------------------------------------------------===// template ::T> bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Ptr = S.Current->getLocalPointer(I); if (!CheckLoad(S, OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool SetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->setLocal(I, S.Stk.pop()); return true; } template ::T> bool GetParam(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) { return false; } S.Stk.push(S.Current->getParam(I)); return true; } template ::T> bool SetParam(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->setParam(I, S.Stk.pop()); return true; } /// 1) Peeks a pointer on the stack /// 2) Pushes the value of the pointer's field on the stack template ::T> bool GetField(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.peek(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool SetField(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Obj = S.Stk.peek(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckStore(S, OpPC, Field)) return false; Field.deref() = Value; return true; } /// 1) Pops a pointer from the stack /// 2) Pushes the value of the pointer's field on the stack template ::T> bool GetFieldPop(InterpState &S, CodePtr OpPC, uint32_t I) { const Pointer &Obj = S.Stk.pop(); if (!CheckNull(S, OpPC, Obj, CSK_Field)) return false; if (!CheckRange(S, OpPC, Obj, CSK_Field)) return false; const Pointer &Field = Obj.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool GetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); if (!CheckLoad(S, OpPC, Field)) return false; S.Stk.push(Field.deref()); return true; } template ::T> bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const T &Value = S.Stk.pop(); const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); if (!CheckStore(S, OpPC, Field)) return false; Field.deref() = Value; return true; } template ::T> bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { auto *B = S.P.getGlobal(I); if (B->isExtern()) return false; S.Stk.push(B->deref()); return true; } template ::T> bool SetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { // TODO: emit warning. return false; } template ::T> bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { S.P.getGlobal(I)->deref() = S.Stk.pop(); return true; } template ::T> bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); Field.deref() = S.Stk.pop(); Field.initialize(); return true; } template ::T> bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(F->Offset); const auto &Value = S.Stk.pop(); Field.deref() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); Field.initialize(); return true; } template ::T> bool InitThisFieldActive(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); Field.deref() = S.Stk.pop(); Field.activate(); Field.initialize(); return true; } /// 1) Pops the value from the stack /// 2) Peeks a pointer from the stack /// 3) Pushes the value to field I of the pointer on the stack template ::T> bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Field = S.Stk.peek().atField(I); Field.deref() = Value; Field.activate(); Field.initialize(); return true; } template ::T> bool InitBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { const T &Value = S.Stk.pop(); const Pointer &Field = S.Stk.pop().atField(F->Offset); Field.deref() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); Field.activate(); Field.initialize(); return true; } template ::T> bool InitFieldActive(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); const Pointer &Field = Ptr.atField(I); Field.deref() = Value; Field.activate(); Field.initialize(); return true; } //===----------------------------------------------------------------------===// // GetPtr Local/Param/Global/Field/This //===----------------------------------------------------------------------===// inline bool GetPtrLocal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Stk.push(S.Current->getLocalPointer(I)); return true; } inline bool GetPtrParam(InterpState &S, CodePtr OpPC, uint32_t I) { if (S.checkingPotentialConstantExpression()) { return false; } S.Stk.push(S.Current->getParamPointer(I)); return true; } inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { S.Stk.push(S.P.getPtrGlobal(I)); return true; } /// 1) Pops a Pointer from the stack /// 2) Pushes Pointer.atField(Off) on the stack inline bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.pop(); if (!CheckNull(S, OpPC, Ptr, CSK_Field)) return false; if (!CheckExtern(S, OpPC, Ptr)) return false; if (!CheckRange(S, OpPC, Ptr, CSK_Field)) return false; S.Stk.push(Ptr.atField(Off)); return true; } inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; S.Stk.push(This.atField(Off)); return true; } inline bool GetPtrActiveField(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.pop(); if (!CheckNull(S, OpPC, Ptr, CSK_Field)) return false; if (!CheckRange(S, OpPC, Ptr, CSK_Field)) return false; Pointer Field = Ptr.atField(Off); Ptr.deactivate(); Field.activate(); S.Stk.push(std::move(Field)); return true; } inline bool GetPtrActiveThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; Pointer Field = This.atField(Off); This.deactivate(); Field.activate(); S.Stk.push(std::move(Field)); return true; } inline bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) { const Pointer &Ptr = S.Stk.pop(); if (!CheckNull(S, OpPC, Ptr, CSK_Base)) return false; S.Stk.push(Ptr.atField(Off)); return true; } inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; S.Stk.push(This.atField(Off)); return true; } inline bool VirtBaseHelper(InterpState &S, CodePtr OpPC, const RecordDecl *Decl, const Pointer &Ptr) { Pointer Base = Ptr; while (Base.isBaseClass()) Base = Base.getBase(); auto *Field = Base.getRecord()->getVirtualBase(Decl); S.Stk.push(Base.atField(Field->Offset)); return true; } inline bool GetPtrVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) { const Pointer &Ptr = S.Stk.pop(); if (!CheckNull(S, OpPC, Ptr, CSK_Base)) return false; return VirtBaseHelper(S, OpPC, D, Ptr); } inline bool GetPtrThisVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) { if (S.checkingPotentialConstantExpression()) return false; const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; return VirtBaseHelper(S, OpPC, D, S.Current->getThis()); } //===----------------------------------------------------------------------===// // Load, Store, Init //===----------------------------------------------------------------------===// template ::T> bool Load(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (!CheckLoad(S, OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool LoadPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckLoad(S, OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; } template ::T> bool Store(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; if (!Ptr.isRoot()) Ptr.initialize(); Ptr.deref() = Value; return true; } template ::T> bool StorePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; if (!Ptr.isRoot()) Ptr.initialize(); Ptr.deref() = Value; return true; } template ::T> bool StoreBitField(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; if (!Ptr.isRoot()) Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { Ptr.deref() = Value; } return true; } template ::T> bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; if (!Ptr.isRoot()) Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { Ptr.deref() = Value; } return true; } template ::T> bool InitPop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckInit(S, OpPC, Ptr)) return false; Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } /// 1) Pops the value from the stack /// 2) Peeks a pointer and gets its index \Idx /// 3) Sets the value on the pointer, leaving the pointer on the stack. template ::T> bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek().atIndex(Idx); if (!CheckInit(S, OpPC, Ptr)) return false; Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } /// The same as InitElem, but pops the pointer as well. template ::T> bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop().atIndex(Idx); if (!CheckInit(S, OpPC, Ptr)) return false; Ptr.initialize(); new (&Ptr.deref()) T(Value); return true; } //===----------------------------------------------------------------------===// // AddOffset, SubOffset //===----------------------------------------------------------------------===// template bool OffsetHelper(InterpState &S, CodePtr OpPC) { // Fetch the pointer and the offset. const T &Offset = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); if (!CheckRange(S, OpPC, Ptr, CSK_ArrayToPointer)) return false; // A zero offset does not change the pointer. if (Offset.isZero()) { S.Stk.push(Ptr); return true; } if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) return false; // Arrays of unknown bounds cannot have pointers into them. if (!CheckArray(S, OpPC, Ptr)) return false; // Get a version of the index comparable to the type. T Index = T::from(Ptr.getIndex(), Offset.bitWidth()); // Compute the largest index into the array. unsigned MaxIndex = Ptr.getNumElems(); // Helper to report an invalid offset, computed as APSInt. auto InvalidOffset = [&]() { const unsigned Bits = Offset.bitWidth(); APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), false); APSInt APIndex(Index.toAPSInt().extend(Bits + 2), false); APSInt NewIndex = Add ? (APIndex + APOffset) : (APIndex - APOffset); S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) << NewIndex << /*array*/ static_cast(!Ptr.inArray()) << static_cast(MaxIndex); return false; }; unsigned MaxOffset = MaxIndex - Ptr.getIndex(); if constexpr (Add) { // If the new offset would be negative, bail out. if (Offset.isNegative() && (Offset.isMin() || -Offset > Index)) return InvalidOffset(); // If the new offset would be out of bounds, bail out. if (Offset.isPositive() && Offset > MaxOffset) return InvalidOffset(); } else { // If the new offset would be negative, bail out. if (Offset.isPositive() && Index < Offset) return InvalidOffset(); // If the new offset would be out of bounds, bail out. if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset)) return InvalidOffset(); } // Offset is valid - compute it on unsigned. int64_t WideIndex = static_cast(Index); int64_t WideOffset = static_cast(Offset); int64_t Result; if constexpr (Add) Result = WideIndex + WideOffset; else Result = WideIndex - WideOffset; S.Stk.push(Ptr.atIndex(static_cast(Result))); return true; } template ::T> bool AddOffset(InterpState &S, CodePtr OpPC) { return OffsetHelper(S, OpPC); } template ::T> bool SubOffset(InterpState &S, CodePtr OpPC) { return OffsetHelper(S, OpPC); } /// 1) Pops a Pointer from the stack. /// 2) Pops another Pointer from the stack. /// 3) Pushes the different of the indices of the two pointers on the stack. template ::T> inline bool SubPtr(InterpState &S, CodePtr OpPC) { const Pointer &LHS = S.Stk.pop(); const Pointer &RHS = S.Stk.pop(); if (!Pointer::hasSameArray(LHS, RHS)) { // TODO: Diagnose. return false; } T A = T::from(LHS.getIndex()); T B = T::from(RHS.getIndex()); return AddSubMulHelper(S, OpPC, A.bitWidth(), A, B); } //===----------------------------------------------------------------------===// // Destroy //===----------------------------------------------------------------------===// inline bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->destroy(I); return true; } //===----------------------------------------------------------------------===// // Cast, CastFP //===----------------------------------------------------------------------===// template bool Cast(InterpState &S, CodePtr OpPC) { using T = typename PrimConv::T; using U = typename PrimConv::T; S.Stk.push(U::from(S.Stk.pop())); return true; } //===----------------------------------------------------------------------===// // Zero, Nullptr //===----------------------------------------------------------------------===// template ::T> bool Zero(InterpState &S, CodePtr OpPC) { S.Stk.push(T::zero()); return true; } template ::T> inline bool Null(InterpState &S, CodePtr OpPC) { S.Stk.push(); return true; } //===----------------------------------------------------------------------===// // This, ImplicitThis //===----------------------------------------------------------------------===// inline bool This(InterpState &S, CodePtr OpPC) { // Cannot read 'this' in this mode. if (S.checkingPotentialConstantExpression()) { return false; } const Pointer &This = S.Current->getThis(); if (!CheckThis(S, OpPC, This)) return false; S.Stk.push(This); return true; } inline bool RVOPtr(InterpState &S, CodePtr OpPC) { assert(S.Current->getFunction()->hasRVO()); S.Stk.push(S.Current->getRVOPtr()); return true; } //===----------------------------------------------------------------------===// // Shr, Shl //===----------------------------------------------------------------------===// template inline bool Shr(InterpState &S, CodePtr OpPC) { using LT = typename PrimConv::T; using RT = typename PrimConv::T; const auto &RHS = S.Stk.pop(); const auto &LHS = S.Stk.pop(); const unsigned Bits = LHS.bitWidth(); if (!CheckShift(S, OpPC, RHS, Bits)) return false; unsigned URHS = static_cast(RHS); S.Stk.push(LT::from(static_cast(LHS) >> URHS, LHS.bitWidth())); return true; } template inline bool Shl(InterpState &S, CodePtr OpPC) { using LT = typename PrimConv::T; using RT = typename PrimConv::T; const auto &RHS = S.Stk.pop(); const auto &LHS = S.Stk.pop(); const unsigned Bits = LHS.bitWidth(); if (!CheckShift(S, OpPC, RHS, Bits)) return false; unsigned URHS = static_cast(RHS); S.Stk.push(LT::from(static_cast(LHS) << URHS, LHS.bitWidth())); return true; } //===----------------------------------------------------------------------===// // NoRet //===----------------------------------------------------------------------===// inline bool NoRet(InterpState &S, CodePtr OpPC) { SourceLocation EndLoc = S.Current->getCallee()->getEndLoc(); S.FFDiag(EndLoc, diag::note_constexpr_no_return); return false; } //===----------------------------------------------------------------------===// // NarrowPtr, ExpandPtr //===----------------------------------------------------------------------===// inline bool NarrowPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); S.Stk.push(Ptr.narrow()); return true; } inline bool ExpandPtr(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); S.Stk.push(Ptr.expand()); return true; } inline bool Call(InterpState &S, CodePtr &PC, const Function *Func) { auto NewFrame = std::make_unique(S, Func, PC); Pointer ThisPtr; if (Func->hasThisPointer()) { ThisPtr = NewFrame->getThis(); if (!CheckInvoke(S, PC, ThisPtr)) { return false; } } if (!CheckCallable(S, PC, Func)) return false; InterpFrame *FrameBefore = S.Current; S.Current = NewFrame.get(); APValue CallResult; // Note that we cannot assert(CallResult.hasValue()) here since // Ret() above only sets the APValue if the curent frame doesn't // have a caller set. if (Interpret(S, CallResult)) { NewFrame.release(); // Frame was delete'd already. assert(S.Current == FrameBefore); // For constructors, check that all fields have been initialized. if (Func->isConstructor() && !CheckCtorCall(S, PC, ThisPtr)) return false; return true; } // Interpreting the function failed somehow. Reset to // previous state. S.Current = FrameBefore; return false; } //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// template inline T ReadArg(InterpState &S, CodePtr &OpPC) { if constexpr (std::is_pointer::value) { uint32_t ID = OpPC.read(); return reinterpret_cast(S.P.getNativePointer(ID)); } else { return OpPC.read(); } } } // namespace interp } // namespace clang #endif