blob: 6f8aa55e7cddf5b6560efce5673eb8724be307e0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/expr/vm_exec.h"
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/common/test_with_loop.h"
#include "src/developer/debug/zxdb/expr/mock_eval_context.h"
#include "src/developer/debug/zxdb/expr/vm_stream.h"
#include "src/developer/debug/zxdb/symbols/type_test_support.h"
namespace zxdb {
namespace {
class VmExecTest : public TestWithLoop {
public:
void CallVmExec(VmStream stream, bool expected_sync, ErrOrValue expected) {
fxl::RefPtr<EvalContext> call_context(eval_context_);
bool called = false;
VmExec(call_context, std::move(stream), [&](ErrOrValue result) mutable {
called = true;
EXPECT_EQ(expected, result);
});
EXPECT_EQ(expected_sync, called);
if (!called) {
debug::MessageLoop::Current()->RunUntilNoTasks();
EXPECT_TRUE(called) << "Callback lost";
}
}
// Expects success = to the given integer value (flexible on type).
void CallVmExec(VmStream stream, bool expected_sync, int64_t expected) {
fxl::RefPtr<EvalContext> call_context(eval_context_);
bool called = false;
VmExec(call_context, std::move(stream), [&](ErrOrValue result) mutable {
called = true;
ASSERT_TRUE(result.ok()) << result.err().msg();
int64_t result_value = 0;
result.value().PromoteTo64(&result_value);
EXPECT_EQ(expected, result_value);
});
EXPECT_EQ(expected_sync, called);
if (!called) {
debug::MessageLoop::Current()->RunUntilNoTasks();
EXPECT_TRUE(called) << "Callback lost";
}
}
protected:
fxl::RefPtr<MockEvalContext> eval_context_ = fxl::MakeRefCounted<MockEvalContext>();
};
} // namespace
TEST_F(VmExecTest, Empty) {
// An empty program is defined to produce an empty value.
CallVmExec(VmStream(), true, ExprValue());
}
TEST_F(VmExecTest, UnaryOp) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(27)));
stream.push_back(VmOp::MakeUnary(ExprToken(ExprTokenType::kBang, "!", 0)));
CallVmExec(std::move(stream), true, 0);
}
TEST_F(VmExecTest, BinaryOp) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeLiteral(ExprValue(27)));
stream.push_back(VmOp::MakeBinary(ExprToken(ExprTokenType::kPlus, "+", 0)));
CallVmExec(std::move(stream), true, 32);
}
TEST_F(VmExecTest, ExpandRef) {
// Inject the data to back the reference (32-bit little-endian value = 99).
uint64_t kDataAddress = 0x32745612;
eval_context_->data_provider()->AddMemory(kDataAddress, {99, 0, 0, 0});
// Make the reference.
auto int32_type = MakeInt32Type();
auto int32_ref_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kReferenceType, int32_type);
ExprValue ref_value(kDataAddress, int32_ref_type);
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ref_value));
stream.push_back(VmOp::MakeExpandRef());
CallVmExec(std::move(stream), false, 99);
}
TEST_F(VmExecTest, Drop) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeLiteral(ExprValue(27)));
stream.push_back(VmOp::MakeDrop());
CallVmExec(std::move(stream), true, 5); // Should be left with the first value only.
}
TEST_F(VmExecTest, Dup) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeDup());
stream.push_back(VmOp::MakeBinary(ExprToken(ExprTokenType::kPlus, "+", 0)));
CallVmExec(std::move(stream), true, 10);
}
TEST_F(VmExecTest, Jump) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeJump(3)); // Skip to last instruction (one past the end).
stream.push_back(VmOp::MakeLiteral(ExprValue(6))); // Should be skipped.
CallVmExec(std::move(stream), true, 5);
}
TEST_F(VmExecTest, JumpIfFalse) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeJumpIfFalse(3)); // Skip to last instruction.
stream.push_back(VmOp::MakeLiteral(ExprValue(6))); // Should be run.
CallVmExec(std::move(stream), true, 6);
VmStream stream2;
stream2.push_back(VmOp::MakeLiteral(ExprValue(1)));
stream2.push_back(VmOp::MakeLiteral(ExprValue(0)));
stream2.push_back(VmOp::MakeJumpIfFalse(4)); // Skip to pushing "7".
stream2.push_back(VmOp::MakeLiteral(ExprValue(6))); // Should not be run.
stream2.push_back(VmOp::MakeLiteral(ExprValue(7))); // Should be run.
stream2.push_back(VmOp::MakeBinary(ExprToken(ExprTokenType::kPlus, "+", 0)));
CallVmExec(std::move(stream2), true, 8); // 7 + 1
}
TEST_F(VmExecTest, LocalVar) {
// Makes two local variables and adds them.
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(1)));
stream.push_back(VmOp::MakeSetLocal(2));
stream.push_back(VmOp::MakeLiteral(ExprValue(9)));
stream.push_back(VmOp::MakeSetLocal(4));
stream.push_back(VmOp::MakeLiteral(ExprValue(5))); // Overwrite slot 2.
stream.push_back(VmOp::MakeSetLocal(2));
stream.push_back(VmOp::MakeGetLocal(2));
stream.push_back(VmOp::MakeGetLocal(4));
stream.push_back(VmOp::MakeBinary(ExprToken(ExprTokenType::kPlus, "+", 0)));
CallVmExec(std::move(stream), true, 14); // 5 + 9 = 14.
// Create local variables and shrink the stack.
ExprToken var0(ExprTokenType::kName, "i", 0);
ExprToken var1(ExprTokenType::kName, "j", 2);
stream = VmStream();
stream.push_back(VmOp::MakeLiteral(ExprValue(1)));
stream.push_back(VmOp::MakeSetLocal(0, var0));
stream.push_back(VmOp::MakeLiteral(ExprValue(2)));
stream.push_back(VmOp::MakeSetLocal(1, var1));
stream.push_back(VmOp::MakePopLocals(1)); // Shrinks the local variable stack down to 1.
stream.push_back(VmOp::MakeGetLocal(0, var0)); // This should still succeed.
stream.push_back(VmOp::MakeGetLocal(1, var1)); // This should now fail.
CallVmExec(std::move(stream), true, Err("Bad local variable index 1 when reading 'j'."));
// Create a local variable, set it, then dereference the variable for the result. This tests the
// end-to-end update of these loacals.
stream = VmStream();
stream.push_back(VmOp::MakeLiteral(ExprValue(1)));
stream.push_back(VmOp::MakeSetLocal(0, var0));
stream.push_back(VmOp::MakeGetLocal(0, var0)); // Left side of "operator=".
stream.push_back(VmOp::MakeLiteral(ExprValue(99))); // Right side of "operator=".
stream.push_back(VmOp::MakeBinary(ExprToken(ExprTokenType::kEquals, "=", 0)));
stream.push_back(VmOp::MakeDrop()); // Drop unneeded return value of "operator=".
stream.push_back(VmOp::MakeGetLocal(0, var0)); // Read local to see if it got updated.
CallVmExec(std::move(stream), true, 99); // Result should be the updated value.
}
TEST_F(VmExecTest, Callback1) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeCallback1(
[&](const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& param) -> ErrOrValue {
EXPECT_EQ(param, ExprValue(5));
return ExprValue(1234);
}));
CallVmExec(std::move(stream), true, 1234);
}
TEST_F(VmExecTest, Callback2) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeLiteral(ExprValue(6)));
stream.push_back(
VmOp::MakeCallback2([&](const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& param1,
const ExprValue& param2) -> ErrOrValue {
EXPECT_EQ(param1, ExprValue(5));
EXPECT_EQ(param2, ExprValue(6));
return ExprValue(1234);
}));
CallVmExec(std::move(stream), true, 1234);
}
TEST_F(VmExecTest, AsyncCallback1) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeAsyncCallback1(
[&](const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& param, EvalCallback cb) {
EXPECT_EQ(param, ExprValue(5));
// Evaluate the completion asynchonosly.
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [cb = std::move(cb)]() mutable { cb(ExprValue(1234)); });
}));
CallVmExec(std::move(stream), false, 1234);
}
TEST_F(VmExecTest, AsyncCallback2) {
VmStream stream;
stream.push_back(VmOp::MakeLiteral(ExprValue(5)));
stream.push_back(VmOp::MakeLiteral(ExprValue(6)));
stream.push_back(VmOp::MakeAsyncCallback2([&](const fxl::RefPtr<EvalContext>& eval_context,
const ExprValue& param1, const ExprValue& param2,
EvalCallback cb) {
EXPECT_EQ(param1, ExprValue(5));
EXPECT_EQ(param2, ExprValue(6));
// Evaluate the completion asynchonosly.
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [cb = std::move(cb)]() mutable { cb(ExprValue(1234)); });
}));
CallVmExec(std::move(stream), false, 1234);
}
} // namespace zxdb