//===------- 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 #include #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()) { PC += Offset; } return true; } static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) { if (!S.Stk.pop()) { 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(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(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(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(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(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(); CodePtr OpPC = PC; switch (Op) { #define GET_INTERP #include "Opcodes.inc" #undef GET_INTERP } } } } // namespace interp } // namespace clang