| //===------- Interp.cpp - 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Interp.h" |
| #include <limits> |
| #include <vector> |
| #include "Function.h" |
| #include "InterpFrame.h" |
| #include "InterpStack.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 "clang/AST/ExprCXX.h" |
| #include "llvm/ADT/APSInt.h" |
| |
| using namespace clang; |
| using namespace clang::interp; |
| |
| static bool RetValue(InterpState &S, CodePtr &Pt, APValue &Result) { |
| llvm::report_fatal_error("Interpreter cannot return values"); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Jmp, Jt, Jf |
| //===----------------------------------------------------------------------===// |
| |
| static bool Jmp(InterpState &S, CodePtr &PC, int32_t Offset) { |
| PC += Offset; |
| return true; |
| } |
| |
| static bool Jt(InterpState &S, CodePtr &PC, int32_t Offset) { |
| if (S.Stk.pop<bool>()) { |
| PC += Offset; |
| } |
| return true; |
| } |
| |
| static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) { |
| if (!S.Stk.pop<bool>()) { |
| PC += Offset; |
| } |
| return true; |
| } |
| |
| static bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| AccessKinds AK) { |
| if (Ptr.isActive()) |
| return true; |
| |
| // Get the inactive field descriptor. |
| const FieldDecl *InactiveField = Ptr.getField(); |
| |
| // Walk up the pointer chain to find the union which is not active. |
| Pointer U = Ptr.getBase(); |
| while (!U.isActive()) { |
| U = U.getBase(); |
| } |
| |
| // Find the active field of the union. |
| const Record *R = U.getRecord(); |
| assert(R && R->isUnion() && "Not a union"); |
| const FieldDecl *ActiveField = nullptr; |
| for (unsigned I = 0, N = R->getNumFields(); I < N; ++I) { |
| const Pointer &Field = U.atField(R->getField(I)->Offset); |
| if (Field.isActive()) { |
| ActiveField = Field.getField(); |
| break; |
| } |
| } |
| |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member) |
| << AK << InactiveField << !ActiveField << ActiveField; |
| return false; |
| } |
| |
| static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| AccessKinds AK) { |
| if (auto ID = Ptr.getDeclID()) { |
| if (!Ptr.isStaticTemporary()) |
| return true; |
| |
| if (Ptr.getDeclDesc()->getType().isConstQualified()) |
| return true; |
| |
| if (S.P.getCurrentDecl() == ID) |
| return true; |
| |
| const SourceInfo &E = S.Current->getSource(OpPC); |
| S.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK; |
| S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (auto ID = Ptr.getDeclID()) { |
| if (!Ptr.isStatic()) |
| return true; |
| |
| if (S.P.getCurrentDecl() == ID) |
| return true; |
| |
| S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_modify_global); |
| return false; |
| } |
| return true; |
| } |
| |
| namespace clang { |
| namespace interp { |
| |
| bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!Ptr.isExtern()) |
| return true; |
| |
| if (!S.checkingPotentialConstantExpression()) { |
| const auto *VD = Ptr.getDeclDesc()->asValueDecl(); |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_ltor_non_constexpr, 1) << VD; |
| S.Note(VD->getLocation(), diag::note_declared_at); |
| } |
| return false; |
| } |
| |
| bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!Ptr.isUnknownSizeArray()) |
| return true; |
| const SourceInfo &E = S.Current->getSource(OpPC); |
| S.FFDiag(E, diag::note_constexpr_unsized_array_indexed); |
| return false; |
| } |
| |
| bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| AccessKinds AK) { |
| if (Ptr.isZero()) { |
| const auto &Src = S.Current->getSource(OpPC); |
| |
| if (Ptr.isField()) |
| S.FFDiag(Src, diag::note_constexpr_null_subobject) << CSK_Field; |
| else |
| S.FFDiag(Src, diag::note_constexpr_access_null) << AK; |
| |
| return false; |
| } |
| |
| if (!Ptr.isLive()) { |
| const auto &Src = S.Current->getSource(OpPC); |
| bool IsTemp = Ptr.isTemporary(); |
| |
| S.FFDiag(Src, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp; |
| |
| if (IsTemp) |
| S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); |
| else |
| S.Note(Ptr.getDeclLoc(), diag::note_declared_at); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| CheckSubobjectKind CSK) { |
| if (!Ptr.isZero()) |
| return true; |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_null_subobject) << CSK; |
| return false; |
| } |
| |
| bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| AccessKinds AK) { |
| if (!Ptr.isOnePastEnd()) |
| return true; |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_access_past_end) << AK; |
| return false; |
| } |
| |
| bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| CheckSubobjectKind CSK) { |
| if (!Ptr.isElementPastEnd()) |
| return true; |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_past_end_subobject) << CSK; |
| return false; |
| } |
| |
| bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| assert(Ptr.isLive() && "Pointer is not live"); |
| if (!Ptr.isConst()) |
| return true; |
| |
| // The This pointer is writable in constructors and destructors, |
| // even if isConst() returns true. |
| if (const Function *Func = S.Current->getFunction(); |
| Func && (Func->isConstructor() || Func->isDestructor()) && |
| Ptr.block() == S.Current->getThis().block()) { |
| return true; |
| } |
| |
| const QualType Ty = Ptr.getType(); |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty; |
| return false; |
| } |
| |
| bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| assert(Ptr.isLive() && "Pointer is not live"); |
| if (!Ptr.isMutable()) { |
| return true; |
| } |
| |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| const FieldDecl *Field = Ptr.getField(); |
| S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field; |
| S.Note(Field->getLocation(), diag::note_declared_at); |
| return false; |
| } |
| |
| bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, |
| AccessKinds AK) { |
| if (Ptr.isInitialized()) |
| return true; |
| |
| if (!S.checkingPotentialConstantExpression()) { |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| S.FFDiag(Loc, diag::note_constexpr_access_uninit) |
| << AK << /*uninitialized=*/true; |
| } |
| return false; |
| } |
| |
| bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!CheckLive(S, OpPC, Ptr, AK_Read)) |
| return false; |
| if (!CheckExtern(S, OpPC, Ptr)) |
| return false; |
| if (!CheckRange(S, OpPC, Ptr, AK_Read)) |
| return false; |
| if (!CheckInitialized(S, OpPC, Ptr, AK_Read)) |
| return false; |
| if (!CheckActive(S, OpPC, Ptr, AK_Read)) |
| return false; |
| if (!CheckTemporary(S, OpPC, Ptr, AK_Read)) |
| return false; |
| if (!CheckMutable(S, OpPC, Ptr)) |
| return false; |
| return true; |
| } |
| |
| bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!CheckLive(S, OpPC, Ptr, AK_Assign)) |
| return false; |
| if (!CheckExtern(S, OpPC, Ptr)) |
| return false; |
| if (!CheckRange(S, OpPC, Ptr, AK_Assign)) |
| return false; |
| if (!CheckGlobal(S, OpPC, Ptr)) |
| return false; |
| if (!CheckConst(S, OpPC, Ptr)) |
| return false; |
| return true; |
| } |
| |
| bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!CheckLive(S, OpPC, Ptr, AK_MemberCall)) |
| return false; |
| if (!CheckExtern(S, OpPC, Ptr)) |
| return false; |
| if (!CheckRange(S, OpPC, Ptr, AK_MemberCall)) |
| return false; |
| return true; |
| } |
| |
| bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { |
| if (!CheckLive(S, OpPC, Ptr, AK_Assign)) |
| return false; |
| if (!CheckRange(S, OpPC, Ptr, AK_Assign)) |
| return false; |
| return true; |
| } |
| |
| bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) { |
| |
| if (F->isVirtual() && !S.getLangOpts().CPlusPlus20) { |
| const SourceLocation &Loc = S.Current->getLocation(OpPC); |
| S.CCEDiag(Loc, diag::note_constexpr_virtual_call); |
| return false; |
| } |
| |
| if (!F->isConstexpr()) { |
| // Don't emit anything if we're checking for a potential constant |
| // expression. That will happen later when actually executing. |
| if (S.checkingPotentialConstantExpression()) |
| return false; |
| |
| const SourceLocation &Loc = S.Current->getLocation(OpPC); |
| if (S.getLangOpts().CPlusPlus11) { |
| const FunctionDecl *DiagDecl = F->getDecl(); |
| |
| // If this function is not constexpr because it is an inherited |
| // non-constexpr constructor, diagnose that directly. |
| const auto *CD = dyn_cast<CXXConstructorDecl>(DiagDecl); |
| if (CD && CD->isInheritingConstructor()) { |
| const auto *Inherited = CD->getInheritedConstructor().getConstructor(); |
| if (!Inherited->isConstexpr()) |
| DiagDecl = CD = Inherited; |
| } |
| |
| // FIXME: If DiagDecl is an implicitly-declared special member function |
| // or an inheriting constructor, we should be much more explicit about why |
| // it's not constexpr. |
| if (CD && CD->isInheritingConstructor()) |
| S.FFDiag(Loc, diag::note_constexpr_invalid_inhctor, 1) |
| << CD->getInheritedConstructor().getConstructor()->getParent(); |
| else |
| S.FFDiag(Loc, diag::note_constexpr_invalid_function, 1) |
| << DiagDecl->isConstexpr() << (bool)CD << DiagDecl; |
| S.Note(DiagDecl->getLocation(), diag::note_declared_at); |
| } else { |
| S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckCallDepth(InterpState &S, CodePtr OpPC) { |
| if ((S.Current->getDepth() + 1) > S.getLangOpts().ConstexprCallDepth) { |
| S.FFDiag(S.Current->getSource(OpPC), |
| diag::note_constexpr_depth_limit_exceeded) |
| << S.getLangOpts().ConstexprCallDepth; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This) { |
| if (!This.isZero()) |
| return true; |
| |
| const SourceInfo &Loc = S.Current->getSource(OpPC); |
| |
| bool IsImplicit = false; |
| if (const auto *E = dyn_cast_if_present<CXXThisExpr>(Loc.asExpr())) |
| IsImplicit = E->isImplicit(); |
| |
| if (S.getLangOpts().CPlusPlus11) |
| S.FFDiag(Loc, diag::note_constexpr_this) << IsImplicit; |
| else |
| S.FFDiag(Loc); |
| |
| return false; |
| } |
| |
| bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) { |
| if (!MD->isPure()) |
| return true; |
| const SourceInfo &E = S.Current->getSource(OpPC); |
| S.FFDiag(E, diag::note_constexpr_pure_virtual_call, 1) << MD; |
| S.Note(MD->getLocation(), diag::note_declared_at); |
| return false; |
| } |
| |
| static void DiagnoseUninitializedSubobject(InterpState &S, const SourceInfo &SI, |
| const FieldDecl *SubObjDecl) { |
| assert(SubObjDecl && "Subobject declaration does not exist"); |
| S.FFDiag(SI, diag::note_constexpr_uninitialized) |
| << /*(name)*/ 1 << SubObjDecl; |
| S.Note(SubObjDecl->getLocation(), |
| diag::note_constexpr_subobject_declared_here); |
| } |
| |
| static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, |
| const Pointer &BasePtr, const Record *R); |
| |
| static bool CheckArrayInitialized(InterpState &S, CodePtr OpPC, |
| const Pointer &BasePtr, |
| const ConstantArrayType *CAT) { |
| bool Result = true; |
| size_t NumElems = CAT->getSize().getZExtValue(); |
| QualType ElemType = CAT->getElementType(); |
| |
| if (ElemType->isRecordType()) { |
| const Record *R = BasePtr.getElemRecord(); |
| for (size_t I = 0; I != NumElems; ++I) { |
| Pointer ElemPtr = BasePtr.atIndex(I).narrow(); |
| Result &= CheckFieldsInitialized(S, OpPC, ElemPtr, R); |
| } |
| } else if (const auto *ElemCAT = dyn_cast<ConstantArrayType>(ElemType)) { |
| for (size_t I = 0; I != NumElems; ++I) { |
| Pointer ElemPtr = BasePtr.atIndex(I).narrow(); |
| Result &= CheckArrayInitialized(S, OpPC, ElemPtr, ElemCAT); |
| } |
| } else { |
| for (size_t I = 0; I != NumElems; ++I) { |
| if (!BasePtr.atIndex(I).isInitialized()) { |
| DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), |
| BasePtr.getField()); |
| Result = false; |
| } |
| } |
| } |
| |
| return Result; |
| } |
| |
| static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, |
| const Pointer &BasePtr, const Record *R) { |
| assert(R); |
| bool Result = true; |
| // Check all fields of this record are initialized. |
| for (const Record::Field &F : R->fields()) { |
| Pointer FieldPtr = BasePtr.atField(F.Offset); |
| QualType FieldType = F.Decl->getType(); |
| |
| if (FieldType->isRecordType()) { |
| Result &= CheckFieldsInitialized(S, OpPC, FieldPtr, FieldPtr.getRecord()); |
| } else if (FieldType->isArrayType()) { |
| const auto *CAT = |
| cast<ConstantArrayType>(FieldType->getAsArrayTypeUnsafe()); |
| Result &= CheckArrayInitialized(S, OpPC, FieldPtr, CAT); |
| } else if (!FieldPtr.isInitialized()) { |
| DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), F.Decl); |
| Result = false; |
| } |
| } |
| |
| // Check Fields in all bases |
| for (const Record::Base &B : R->bases()) { |
| Pointer P = BasePtr.atField(B.Offset); |
| Result &= CheckFieldsInitialized(S, OpPC, P, B.R); |
| } |
| |
| // TODO: Virtual bases |
| |
| return Result; |
| } |
| |
| bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This) { |
| assert(!This.isZero()); |
| if (const Record *R = This.getRecord()) |
| return CheckFieldsInitialized(S, OpPC, This, R); |
| const auto *CAT = |
| cast<ConstantArrayType>(This.getType()->getAsArrayTypeUnsafe()); |
| return CheckArrayInitialized(S, OpPC, This, CAT); |
| } |
| |
| bool CheckFloatResult(InterpState &S, CodePtr OpPC, APFloat::opStatus Status) { |
| // In a constant context, assume that any dynamic rounding mode or FP |
| // exception state matches the default floating-point environment. |
| if (S.inConstantContext()) |
| return true; |
| |
| const SourceInfo &E = S.Current->getSource(OpPC); |
| FPOptions FPO = E.asExpr()->getFPFeaturesInEffect(S.Ctx.getLangOpts()); |
| |
| if ((Status & APFloat::opInexact) && |
| FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { |
| // Inexact result means that it depends on rounding mode. If the requested |
| // mode is dynamic, the evaluation cannot be made in compile time. |
| S.FFDiag(E, diag::note_constexpr_dynamic_rounding); |
| return false; |
| } |
| |
| if ((Status != APFloat::opOK) && |
| (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic || |
| FPO.getExceptionMode() != LangOptions::FPE_Ignore || |
| FPO.getAllowFEnvAccess())) { |
| S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); |
| return false; |
| } |
| |
| if ((Status & APFloat::opStatus::opInvalidOp) && |
| FPO.getExceptionMode() != LangOptions::FPE_Ignore) { |
| // There is no usefully definable result. |
| S.FFDiag(E); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Interpret(InterpState &S, APValue &Result) { |
| // The current stack frame when we started Interpret(). |
| // This is being used by the ops to determine wheter |
| // to return from this function and thus terminate |
| // interpretation. |
| const InterpFrame *StartFrame = S.Current; |
| assert(!S.Current->isRoot()); |
| CodePtr PC = S.Current->getPC(); |
| |
| // Empty program. |
| if (!PC) |
| return true; |
| |
| for (;;) { |
| auto Op = PC.read<Opcode>(); |
| CodePtr OpPC = PC; |
| |
| switch (Op) { |
| #define GET_INTERP |
| #include "Opcodes.inc" |
| #undef GET_INTERP |
| } |
| } |
| } |
| |
| } // namespace interp |
| } // namespace clang |