blob: 0cbb38073f652ab43c4156122cd767ecb8ac6082 [file] [edit]
//===- ExecutorBase.cpp - Non-visitor methods of InstExecutor -------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements non-visitor methods of InstExecutor for code reuse.
//
//===----------------------------------------------------------------------===//
#include "ExecutorBase.h"
namespace llvm::ubi {
Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
ArrayRef<AnyValue> Args, AnyValue &RetVal,
const TargetLibraryInfoImpl &TLIImpl)
: Func(F), LastFrame(LastFrame), CallSite(CallSite), Args(Args),
RetVal(RetVal), TLI(TLIImpl, &F) {
assert((Args.size() == F.arg_size() ||
(F.isVarArg() && Args.size() >= F.arg_size())) &&
"Expected enough arguments to call the function.");
BB = &Func.getEntryBlock();
PC = BB->begin();
for (Argument &Arg : F.args())
ValueMap[&Arg] = Args[Arg.getArgNo()];
}
DiagnosticReporter ExecutorBase::reportImmediateUB() {
return DiagnosticReporter(*this, DiagnosticKind::ImmediateUB);
}
DiagnosticReporter ExecutorBase::reportError() {
return DiagnosticReporter(*this, DiagnosticKind::Error);
}
void ExecutorBase::reportImmediateUBString(StringRef Msg) {
// Check if we have already reported an immediate UB.
if (hasProgramExited())
return;
dumpStackTrace();
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
Handler.onImmediateUB(Msg);
}
void ExecutorBase::reportErrorString(StringRef Msg) {
// Check if we have already reported an error message.
if (hasProgramExited())
return;
dumpStackTrace();
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
Handler.onError(Msg);
}
std::pair<MemoryObject *, uint64_t>
ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
Align Alignment, bool IsStore) {
auto *MO = Ctx.checkProvenance(Ptr, [](const Provenance &) {
// TODO: check provenance
// TODO: check inrange(S, E)
return true;
});
if (!MO) {
reportImmediateUB()
<< "Invalid memory access via a pointer with nullary provenance.";
return {};
}
const APInt &Address = Ptr.address();
// Loading from a stack object outside its lifetime is not undefined
// behavior and returns a poison value instead. Storing to it is still
// undefined behavior.
if (IsStore ? MO->getState() != MemoryObjectState::Alive
: MO->getState() == MemoryObjectState::Freed) {
reportImmediateUB() << "Try to access a dead memory object at address 0x"
<< Twine::utohexstr(Address.getZExtValue()) << ".";
return {};
}
if (IsStore && MO->isConstant()) {
reportImmediateUB() << "Try to write to a constant memory object: " << Ptr
<< ".";
return {};
}
if (Address.countr_zero() < Log2(Alignment)) {
reportImmediateUB() << "Misaligned memory access. Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Required alignment: " << Alignment.value() << ".";
return {};
}
if (AccessSize > MO->getSize() || Address.ult(MO->getAddress())) {
reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
<< AccessSize << ", Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Object base: 0x"
<< Twine::utohexstr(MO->getAddress())
<< ", Object size: " << MO->getSize() << ".";
return {};
}
APInt Offset = Address - MO->getAddress();
if (Offset.ugt(MO->getSize() - AccessSize)) {
reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
<< AccessSize << ", Address: 0x"
<< Twine::utohexstr(Address.getZExtValue())
<< ", Object base: 0x"
<< Twine::utohexstr(MO->getAddress())
<< ", Object size: " << MO->getSize() << ".";
return {};
}
return {MO, Offset.getZExtValue()};
}
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
bool NoUndef) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
}
auto &PtrVal = Ptr.asPointer();
if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
/*IsStore=*/false);
MO) {
bool ContainsUndefinedBits = false;
AnyValue Res = Ctx.load(*MO, Offset, ValTy,
NoUndef ? &ContainsUndefinedBits : nullptr);
if (NoUndef && ContainsUndefinedBits)
reportImmediateUB() << "The value loaded contains undefined bits.";
return Res;
}
return AnyValue::getPoisonValue(Ctx, ValTy);
}
void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
const AnyValue &Val, Type *ValTy) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
}
auto &PtrVal = Ptr.asPointer();
if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
/*IsStore=*/true);
MO)
Ctx.store(*MO, Offset, Val, ValTy);
}
void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
uint64_t ExitCode) {
ExitInfo.emplace(Kind, ExitCode);
Handler.onProgramExit(*ExitInfo);
}
void ExecutorBase::setFailed() {
requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
}
bool ExecutorBase::hasProgramExited() const { return ExitInfo.has_value(); }
std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
return ExitInfo;
}
unsigned ExecutorBase::getIntSize() const {
return CurrentFrame->TLI.getIntSize();
}
void ExecutorBase::dumpStackTrace() const {
errs() << "Stacktrace:\n";
const Frame *TheFrame = CurrentFrame;
unsigned Index = 0;
const AsmParserContext *ParserContext = Ctx.getParserContext();
StringRef ModuleFileName = Ctx.getModule().getModuleIdentifier();
while (TheFrame != nullptr) {
if (TheFrame->BB) {
Instruction &Inst = *TheFrame->PC;
errs() << "#" << Index++ << " " << Inst << " at ";
Inst.getFunction()->printAsOperand(errs(), /*PrintType=*/false);
if (ParserContext) {
if (auto Loc = ParserContext->getInstructionOrArgumentLocation(&Inst))
errs() << ' ' << ModuleFileName << ':' << Loc->Start.Line + 1;
}
errs() << "\n";
}
TheFrame = TheFrame->LastFrame;
}
}
} // namespace llvm::ubi