Skip to content

Commit

Permalink
feat: condition breakpoint support
Browse files Browse the repository at this point in the history
  • Loading branch information
sssooonnnggg committed Nov 27, 2024
1 parent 6af239f commit acda43e
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 14 deletions.
1 change: 1 addition & 0 deletions debugger/src/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ void Debugger::registerInitializeHandler() {
response.supportsExceptionOptions = false;
response.supportsDelayedStackTraceLoading = false;
response.supportsSetVariable = true;
response.supportsConditionalBreakpoints = true;
return response;
});
session_->registerSentHandler(
Expand Down
30 changes: 30 additions & 0 deletions debugger/src/internal/breakpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <internal/breakpoint.h>
#include <internal/log.h>
#include <internal/utils.h>
#include "internal/utils/lua_utils.h"

namespace luau::debugger {

Expand All @@ -16,6 +17,35 @@ int BreakPoint::line() const {
return line_;
}

void BreakPoint::setCondition(std::string condition) {
condition_ = std::move(condition);
}

const std::string& BreakPoint::condition() const {
return condition_;
}

BreakPoint::HitResult BreakPoint::hit(lua_State* L) const {
if (condition_.empty())
return HitResult::success(true);

lua_utils::StackGuard guard(L);
if (!lua_utils::pushBreakEnv(L, 0))
return HitResult::error("Invalid condition: environment not found");

auto result = lua_utils::eval(L, condition_, -1);
if (!result.has_value())
return HitResult::error("Invalid condition: syntax error");

if (result.value() != 1)
return HitResult::error("Invalid condition: must return a boolean value");

if (lua_type(L, -1) != LUA_TBOOLEAN)
return HitResult::error("Invalid condition: must return a boolean value");

return HitResult::success(lua_toboolean(L, -1) != 0);
}

bool BreakPoint::enable(lua_State* L, int func_index, bool enable) {
lua_checkstack(L, 1);
lua_getref(L, func_index);
Expand Down
9 changes: 9 additions & 0 deletions debugger/src/internal/breakpoint.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
#pragma once
#include <string>

#include <lua.h>

#include <internal/utils.h>

namespace luau::debugger {
class BreakPoint {
public:
static BreakPoint create(int line);

int line() const;
bool enable(lua_State* L, int func_index, bool enable);
void setCondition(std::string condition);
const std::string& condition() const;

using HitResult = utils::Result<bool>;
HitResult hit(lua_State* L) const;

private:
std::string condition_;
int line_ = 0;
};
} // namespace luau::debugger
40 changes: 36 additions & 4 deletions debugger/src/internal/debug_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ void DebugBridge::onDebugBreak(lua_State* L,
DEBUGGER_LOG_INFO("Session is lost, ignore breakpoints");
return;
}

if (reason == BreakReason::BreakPoint) {
if (auto* bp = findBreakPoint(L)) {
auto hit_result = bp->hit(L);
if (hit_result.isError()) {
// Encountered error when evaluating breakpoint condition
writeDebugConsole(
log::formatError("Failed to evaluate breakpoint condition: {}",
hit_result.error()),
L);
return;
} else if (!hit_result.value()) {
// Condition not met
return;
}
}
}

session_->send(event);

break_vm_ = L;
Expand Down Expand Up @@ -245,13 +263,17 @@ void DebugBridge::setBreakPoints(
if (it == files_.end())
return;
it->second.clearBreakPoints();
files_.erase(it);
return;
}

auto it = files_.find(normalized_path);
std::unordered_map<int, BreakPoint> bps;
for (const auto& bp : *breakpoints)
bps.emplace(static_cast<int>(bp.line), BreakPoint::create(bp.line));
for (const auto& breakpoint : *breakpoints) {
auto bp = BreakPoint::create(breakpoint.line);
if (breakpoint.condition.has_value())
bp.setCondition(breakpoint.condition.value());
bps.emplace(static_cast<int>(breakpoint.line), std::move(bp));
}

if (it == files_.end()) {
DEBUGGER_LOG_INFO("[setBreakPoint] create new file with breakpoints: {}",
Expand All @@ -264,7 +286,6 @@ void DebugBridge::setBreakPoints(
DEBUGGER_LOG_INFO("[setBreakPoint] file already loaded: {}",
normalized_path);

// Update existing file with breakpoints
auto& file = it->second;
file.setBreakPoints(bps);
}
Expand Down Expand Up @@ -487,4 +508,15 @@ void DebugBridge::writeDebugConsole(std::string_view output,
session_->send(std::move(event));
}

BreakPoint* DebugBridge::findBreakPoint(lua_State* L) {
lua_Debug ar;
lua_getinfo(L, 0, "sl", &ar);
auto it = files_.find(normalizePath(ar.source));
if (it == files_.end())
return nullptr;

auto& file = it->second;
return file.findBreakPoint(ar.currentline);
}

} // namespace luau::debugger
2 changes: 2 additions & 0 deletions debugger/src/internal/debug_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ class DebugBridge final {

ResponseOrError<EvaluateResponse> evalWithEnv(const EvaluateRequest& request);

BreakPoint* findBreakPoint(lua_State* L);

private:
friend class LuaStatics;

Expand Down
26 changes: 19 additions & 7 deletions debugger/src/internal/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void File::setBreakPoints(
const std::unordered_map<int, BreakPoint>& breakpoints) {
std::unordered_set<int> settled;
for (const auto& [_, bp] : breakpoints) {
addBreakPoint(bp.line());
addBreakPoint(bp);
settled.insert(bp.line());
}
removeBreakPointsIf([&settled](const BreakPoint& bp) {
Expand All @@ -65,14 +65,19 @@ void File::enableBreakPoint(BreakPoint& bp, bool enable) {
}

void File::addBreakPoint(int line) {
auto it = breakpoints_.find(line);
addBreakPoint(BreakPoint::create(line));
}

void File::addBreakPoint(const BreakPoint& bp) {
auto it = breakpoints_.find(bp.line());
if (it == breakpoints_.end()) {
DEBUGGER_LOG_INFO("Add breakpoint: {}:{}", path_, line);
auto bp = BreakPoint::create(line);
enableBreakPoint(bp, true);
breakpoints_.emplace(line, std::move(bp));
DEBUGGER_LOG_INFO("Add breakpoint: {}:{}", path_, bp.line());
auto inserted = breakpoints_.emplace(bp.line(), bp).first;
enableBreakPoint(inserted->second, true);
} else {
DEBUGGER_LOG_INFO("Breakpoint already exists, ignore: {}:{}", path_, line);
it->second = bp;
DEBUGGER_LOG_INFO("Breakpoint already exists, update it: {}:{}", path_,
bp.line());
}
}

Expand All @@ -83,4 +88,11 @@ void File::clearBreakPoints() {
breakpoints_.clear();
}

BreakPoint* File::findBreakPoint(int line) {
auto it = breakpoints_.find(line);
if (it == breakpoints_.end())
return nullptr;
return &it->second;
}

} // namespace luau::debugger
3 changes: 3 additions & 0 deletions debugger/src/internal/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ class File {
void setBreakPoints(const std::unordered_map<int, BreakPoint>& breakpoints);
void addRef(FileRef ref);

void addBreakPoint(const BreakPoint& bp);
void addBreakPoint(int line);
void clearBreakPoints();

template <class Predicate>
void removeBreakPointsIf(Predicate pred);

BreakPoint* findBreakPoint(int line);

private:
void enableBreakPoint(BreakPoint& bp, bool enable);

Expand Down
8 changes: 8 additions & 0 deletions debugger/src/internal/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ inline void debug_break() {
::DebugBreak();
#endif
}

template <class... Types>
decltype(auto) formatError(const std::format_string<Types...> format,
Types&&... args) {
return std::string("\x1B[31m") +
std::format(format, std::forward<Types>(args)...) + "\033[0m\n";
}

} // namespace luau::debugger::log

#if defined(_MSC_VER) && !defined(__clang__)
Expand Down
32 changes: 32 additions & 0 deletions debugger/src/internal/utils.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include <type_traits>
#include <variant>

#include <internal/utils/dap_utils.h>
#include <internal/utils/lua_utils.h>

namespace luau::debugger::utils {

template <class T, class U>
requires std::is_rvalue_reference_v<T&&> && std::is_rvalue_reference_v<T&&>
class RAII {
Expand All @@ -27,4 +29,34 @@ inline decltype(auto) makeRAII(T&& setup, U&& cleanup) {
return RAII(std::move(setup), std::move(cleanup));
}

template <class T>
requires(!std::is_reference_v<T>)
class Result {
public:
static Result error(std::string_view message) {
Result result;
result.data_ = std::string(message);
return result;
}
static Result success(T&& value) {
Result result;
result.data_ = std::move(value);
return result;
}
static Result success(const T& value) {
Result result;
result.data_ = value;
return result;
}

bool isOk() const { return std::holds_alternative<T>(data_); }
bool isError() const { return std::holds_alternative<std::string>(data_); }
T& value() { return std::get<T>(data_); }
const T& value() const { return std::get<T>(data_); }
std::string_view error() const { return std::get<std::string>(data_); }

private:
std::variant<T, std::string> data_;
};

} // namespace luau::debugger::utils
12 changes: 12 additions & 0 deletions debugger/src/internal/utils/lua_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,16 @@ bool setLocal(lua_State* L, int level, const std::string& name, int index);

bool setUpvalue(lua_State* L, int level, const std::string& name, int index);

class StackGuard {
public:
StackGuard(lua_State* L, int capacity = 5) : L_(L), top_(lua_gettop(L)) {
lua_checkstack(L, capacity);
}
~StackGuard() { lua_settop(L_, top_); }

private:
lua_State* L_;
int top_;
};

} // namespace luau::debugger::lua_utils
4 changes: 2 additions & 2 deletions extensions/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ A debugger for Luau with debug adapter protocol(DAP) support.
- [x] Stop on entry
- [x] Breakpoints
- [x] Add break points when running (Considering thread safety)
- [ ] Conditional breakpoints
- [x] Conditional breakpoints
- [ ] Data breakpoints
- [ ] Breakpoint hit count
- [x] Continue
Expand Down Expand Up @@ -102,4 +102,4 @@ A debugger for Luau with debug adapter protocol(DAP) support.
- [x] Disconnect and reconnect
- [x] Print to debug console
- [x] Coroutine
- [ ] Multiply lua vm
- [x] Multiple lua vm
2 changes: 1 addition & 1 deletion extensions/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "luau-debugger",
"displayName": "Luau Debugger Extension",
"version": "0.0.12",
"version": "0.0.13",
"publisher": "sssooonnnggg",
"description": "Luau debugger",
"author": {
Expand Down
12 changes: 12 additions & 0 deletions tests/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,25 @@ local function test_print()
print(number, string, vector)
end

local function test_condition()
for i = 1, 10 do
print(i)
end
local str = "hello, debugger"
for i = 1, #str do
local a = string.sub(str, 1, i)
print(a)
end
end

local function main()
test_variables()
test_step()
test_coroutine()
test_math()
test_print()
test_error()
test_condition()
print('=============================================')
end

Expand Down

0 comments on commit acda43e

Please sign in to comment.