| 1 | //===- ExecutorBase.cpp - Non-visitor methods of InstExecutor -------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements non-visitor methods of InstExecutor for code reuse. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "ExecutorBase.h" |
| 14 | |
| 15 | namespace llvm::ubi { |
| 16 | Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame, |
| 17 | ArrayRef<AnyValue> Args, AnyValue &RetVal, |
| 18 | const TargetLibraryInfoImpl &TLIImpl) |
| 19 | : Func(F), LastFrame(LastFrame), CallSite(CallSite), Args(Args), |
| 20 | RetVal(RetVal), TLI(TLIImpl, &F) { |
| 21 | assert((Args.size() == F.arg_size() || |
| 22 | (F.isVarArg() && Args.size() >= F.arg_size())) && |
| 23 | "Expected enough arguments to call the function." ); |
| 24 | BB = &Func.getEntryBlock(); |
| 25 | PC = BB->begin(); |
| 26 | for (Argument &Arg : F.args()) |
| 27 | ValueMap[&Arg] = Args[Arg.getArgNo()]; |
| 28 | } |
| 29 | |
| 30 | DiagnosticReporter ExecutorBase::reportImmediateUB() { |
| 31 | return DiagnosticReporter(*this, DiagnosticKind::ImmediateUB); |
| 32 | } |
| 33 | |
| 34 | DiagnosticReporter ExecutorBase::reportError() { |
| 35 | return DiagnosticReporter(*this, DiagnosticKind::Error); |
| 36 | } |
| 37 | |
| 38 | void ExecutorBase::reportImmediateUBString(StringRef Msg) { |
| 39 | // Check if we have already reported an immediate UB. |
| 40 | if (hasProgramExited()) |
| 41 | return; |
| 42 | dumpStackTrace(); |
| 43 | requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Failed); |
| 44 | Handler.onImmediateUB(Msg); |
| 45 | } |
| 46 | |
| 47 | void ExecutorBase::reportErrorString(StringRef Msg) { |
| 48 | // Check if we have already reported an error message. |
| 49 | if (hasProgramExited()) |
| 50 | return; |
| 51 | dumpStackTrace(); |
| 52 | requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Failed); |
| 53 | Handler.onError(Msg); |
| 54 | } |
| 55 | |
| 56 | std::pair<MemoryObject *, uint64_t> |
| 57 | ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize, |
| 58 | Align Alignment, bool IsStore) { |
| 59 | auto *MO = Ctx.checkProvenance(Ptr, Check: [](const Provenance &) { |
| 60 | // TODO: check provenance |
| 61 | // TODO: check inrange(S, E) |
| 62 | return true; |
| 63 | }); |
| 64 | if (!MO) { |
| 65 | reportImmediateUB() |
| 66 | << "Invalid memory access via a pointer with nullary provenance." ; |
| 67 | return {}; |
| 68 | } |
| 69 | const APInt &Address = Ptr.address(); |
| 70 | // Loading from a stack object outside its lifetime is not undefined |
| 71 | // behavior and returns a poison value instead. Storing to it is still |
| 72 | // undefined behavior. |
| 73 | if (IsStore ? MO->getState() != MemoryObjectState::Alive |
| 74 | : MO->getState() == MemoryObjectState::Freed) { |
| 75 | reportImmediateUB() << "Try to access a dead memory object at address 0x" |
| 76 | << Twine::utohexstr(Val: Address.getZExtValue()) << "." ; |
| 77 | return {}; |
| 78 | } |
| 79 | |
| 80 | if (IsStore && MO->isConstant()) { |
| 81 | reportImmediateUB() << "Try to write to a constant memory object: " << Ptr |
| 82 | << "." ; |
| 83 | return {}; |
| 84 | } |
| 85 | |
| 86 | if (Address.countr_zero() < Log2(A: Alignment)) { |
| 87 | reportImmediateUB() << "Misaligned memory access. Address: 0x" |
| 88 | << Twine::utohexstr(Val: Address.getZExtValue()) |
| 89 | << ", Required alignment: " << Alignment.value() << "." ; |
| 90 | return {}; |
| 91 | } |
| 92 | |
| 93 | if (AccessSize > MO->getSize() || Address.ult(RHS: MO->getAddress())) { |
| 94 | reportImmediateUB() << "Memory access is out of bounds. Accessed size: " |
| 95 | << AccessSize << ", Address: 0x" |
| 96 | << Twine::utohexstr(Val: Address.getZExtValue()) |
| 97 | << ", Object base: 0x" |
| 98 | << Twine::utohexstr(Val: MO->getAddress()) |
| 99 | << ", Object size: " << MO->getSize() << "." ; |
| 100 | return {}; |
| 101 | } |
| 102 | |
| 103 | APInt Offset = Address - MO->getAddress(); |
| 104 | |
| 105 | if (Offset.ugt(RHS: MO->getSize() - AccessSize)) { |
| 106 | reportImmediateUB() << "Memory access is out of bounds. Accessed size: " |
| 107 | << AccessSize << ", Address: 0x" |
| 108 | << Twine::utohexstr(Val: Address.getZExtValue()) |
| 109 | << ", Object base: 0x" |
| 110 | << Twine::utohexstr(Val: MO->getAddress()) |
| 111 | << ", Object size: " << MO->getSize() << "." ; |
| 112 | return {}; |
| 113 | } |
| 114 | |
| 115 | return {MO, Offset.getZExtValue()}; |
| 116 | } |
| 117 | |
| 118 | AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy, |
| 119 | bool NoUndef) { |
| 120 | if (Ptr.isPoison()) { |
| 121 | reportImmediateUB() << "Invalid memory access with a poison pointer." ; |
| 122 | return AnyValue::getPoisonValue(Ctx, Ty: ValTy); |
| 123 | } |
| 124 | auto &PtrVal = Ptr.asPointer(); |
| 125 | if (auto [MO, Offset] = verifyMemAccess( |
| 126 | Ptr: PtrVal, AccessSize: Ctx.getEffectiveTypeStoreSize(Ty: ValTy), Alignment, |
| 127 | /*IsStore=*/false); |
| 128 | MO) { |
| 129 | bool ContainsUndefinedBits = false; |
| 130 | AnyValue Res = Ctx.load(MO&: *MO, Offset, ValTy, |
| 131 | ContainsUndefinedBits: NoUndef ? &ContainsUndefinedBits : nullptr); |
| 132 | if (NoUndef && ContainsUndefinedBits) |
| 133 | reportImmediateUB() << "The value loaded contains undefined bits." ; |
| 134 | return Res; |
| 135 | } |
| 136 | return AnyValue::getPoisonValue(Ctx, Ty: ValTy); |
| 137 | } |
| 138 | |
| 139 | void ExecutorBase::store(const AnyValue &Ptr, Align Alignment, |
| 140 | const AnyValue &Val, Type *ValTy) { |
| 141 | if (Ptr.isPoison()) { |
| 142 | reportImmediateUB() << "Invalid memory access with a poison pointer." ; |
| 143 | return; |
| 144 | } |
| 145 | auto &PtrVal = Ptr.asPointer(); |
| 146 | if (auto [MO, Offset] = verifyMemAccess( |
| 147 | Ptr: PtrVal, AccessSize: Ctx.getEffectiveTypeStoreSize(Ty: ValTy), Alignment, |
| 148 | /*IsStore=*/true); |
| 149 | MO) |
| 150 | Ctx.store(MO&: *MO, Offset, Val, ValTy); |
| 151 | } |
| 152 | |
| 153 | void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind, |
| 154 | uint64_t ExitCode) { |
| 155 | ExitInfo.emplace(args&: Kind, args&: ExitCode); |
| 156 | Handler.onProgramExit(ExitInfo: *ExitInfo); |
| 157 | } |
| 158 | |
| 159 | void ExecutorBase::setFailed() { |
| 160 | requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Failed); |
| 161 | } |
| 162 | |
| 163 | bool ExecutorBase::hasProgramExited() const { return ExitInfo.has_value(); } |
| 164 | |
| 165 | std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const { |
| 166 | return ExitInfo; |
| 167 | } |
| 168 | |
| 169 | unsigned ExecutorBase::getIntSize() const { |
| 170 | return CurrentFrame->TLI.getIntSize(); |
| 171 | } |
| 172 | |
| 173 | void ExecutorBase::dumpStackTrace() const { |
| 174 | errs() << "Stacktrace:\n" ; |
| 175 | const Frame *TheFrame = CurrentFrame; |
| 176 | unsigned Index = 0; |
| 177 | const AsmParserContext *ParserContext = Ctx.getParserContext(); |
| 178 | StringRef ModuleFileName = Ctx.getModule().getModuleIdentifier(); |
| 179 | while (TheFrame != nullptr) { |
| 180 | if (TheFrame->BB) { |
| 181 | Instruction &Inst = *TheFrame->PC; |
| 182 | errs() << "#" << Index++ << " " << Inst << " at " ; |
| 183 | Inst.getFunction()->printAsOperand(O&: errs(), /*PrintType=*/false); |
| 184 | if (ParserContext) { |
| 185 | if (auto Loc = ParserContext->getInstructionOrArgumentLocation(&Inst)) |
| 186 | errs() << ' ' << ModuleFileName << ':' << Loc->Start.Line + 1; |
| 187 | } |
| 188 | errs() << "\n" ; |
| 189 | } |
| 190 | TheFrame = TheFrame->LastFrame; |
| 191 | } |
| 192 | } |
| 193 | } // namespace llvm::ubi |
| 194 | |