Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cmake/ftxui_fuzzer.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ function(fuzz source)
)
target_include_directories(${name} PRIVATE src)
target_link_libraries(${name} PRIVATE component)
target_compile_options(${name} PRIVATE -fsanitize=fuzzer,address)
target_link_libraries(${name} PRIVATE -fsanitize=fuzzer,address)
target_compile_options(${name} PRIVATE -fsanitize=fuzzer,address,undefined)
target_link_libraries(${name} PRIVATE -fsanitize=fuzzer,address,undefined)
target_compile_features(${name} PRIVATE cxx_std_17)
endfunction(fuzz)

fuzz(terminal_input_parser_test_fuzzer)
fuzz(component_fuzzer)
fuzz(dom_layout_fuzzer)
fuzz(canvas_fuzzer)
fuzz(utf8_fuzzer)
fuzz(color_fuzzer)
3 changes: 3 additions & 0 deletions include/ftxui/screen/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ template <typename T>
std::wstring to_wstring(T s) {
return to_wstring(std::string_view(std::to_string(s)));
}
inline std::wstring to_wstring(const std::string& s) {
return to_wstring(std::string_view(s));
}
template <>
inline std::wstring to_wstring(const char* s) {
return to_wstring(std::string_view(s));
Expand Down
2 changes: 1 addition & 1 deletion include/ftxui/util/export.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
// |FTXUI_MACRO_CONDITIONAL_COMMA_()| above to implement conditional macro
// expansion.
#define FTXUI_MACRO_SELECT_THIRD_ARGUMENT_(...) \
FTXUI_MACRO_EXPAND( \
FTXUI_MACRO_EXPAND( \
FTXUI_MACRO_SELECT_THIRD_ARGUMENT_IMPL_(__VA_ARGS__, dummy))
#define FTXUI_MACRO_SELECT_THIRD_ARGUMENT_IMPL_(a, b, c, ...) c

Expand Down
12 changes: 5 additions & 7 deletions src/ftxui/component/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <algorithm> // for any_of, copy, max, min
#include <array> // for array
#include <atomic>
#include <map>
#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
#include <cstdint>
Expand All @@ -15,6 +14,7 @@
#include <functional> // for function
#include <initializer_list> // for initializer_list
#include <iostream> // for cout, ostream, operator<<, basic_ostream, endl, flush
#include <map>
#include <memory>
#include <stack> // for stack
#include <string>
Expand Down Expand Up @@ -404,8 +404,7 @@ int g_tty_fd = -1;
void RestoreSignalHandlerAndRaise(int signal) {
#if defined(_WIN32)
auto it = g_old_signal_handlers.find(signal);
auto old_handler =
(it != g_old_signal_handlers.end()) ? it->second : SIG_DFL;
auto old_handler = (it != g_old_signal_handlers.end()) ? it->second : SIG_DFL;
std::signal(signal, old_handler);
#else
auto it = g_old_sigactions.find(signal);
Expand Down Expand Up @@ -543,8 +542,7 @@ void InstallSignalHandler(int sig) {
struct sigaction old_sa;
sigaction(sig, &sa, &old_sa);
g_old_sigactions[sig] = old_sa;
on_exit_functions.emplace(
[=] { sigaction(sig, &old_sa, nullptr); });
on_exit_functions.emplace([=] { sigaction(sig, &old_sa, nullptr); });
#endif
}

Expand Down Expand Up @@ -1054,8 +1052,8 @@ void App::Internal::Draw(Component component) {
if (resized) {
public_->dimx_ = dimx;
public_->dimy_ = dimy;
public_->cells_ =
std::vector<Cell>(static_cast<size_t>(dimx) * static_cast<size_t>(dimy));
public_->cells_ = std::vector<Cell>(static_cast<size_t>(dimx) *
static_cast<size_t>(dimy));
Cursor cursor = public_->cursor_;
cursor.x = dimx - 1;
cursor.y = dimy - 1;
Expand Down
174 changes: 174 additions & 0 deletions src/ftxui/component/canvas_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <cstddef>
#include <string>
#include <vector>
#include "ftxui/dom/canvas.hpp"

using namespace ftxui;

namespace {

int GeneratorInt(const char*& data, size_t& size) {
if (size == 0) {
return 0;
}
auto out = int(data[0]);
data++;
size--;
return out;
}

bool GeneratorBool(const char*& data, size_t& size) {
if (size == 0) {
return false;
}
auto out = bool(data[0] % 2);
data++;
size--;
return out;
}

std::string GeneratorString(const char*& data, size_t& size) {
int index = 0;
while (index < size && data[index]) {
++index;
}
auto out = std::string(data, data + index);
data += index;
size -= index;
return out;
}

} // namespace

extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
if (size < 5) {
return 0;
}

// Read canvas width and height
int width = GeneratorInt(data, size);
int height = GeneratorInt(data, size);

// Keep dimensions bounded but allow some small, zero, negative or moderately
// large values
width = (width % 100) - 10;
height = (height % 100) - 10;

Canvas canvas(width, height);

// Perform multiple drawing actions while data is available
while (size > 2) {
int action = GeneratorInt(data, size) % 15;
switch (action) {
case 0: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
canvas.DrawPointOn(x, y);
break;
}
case 1: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
canvas.DrawPointOff(x, y);
break;
}
case 2: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
canvas.DrawPointToggle(x, y);
break;
}
case 3: {
int x1 = GeneratorInt(data, size);
int y1 = GeneratorInt(data, size);
int x2 = GeneratorInt(data, size);
int y2 = GeneratorInt(data, size);
canvas.DrawPointLine(x1, y1, x2, y2);
break;
}
case 4: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r = GeneratorInt(data, size) % 30;
canvas.DrawPointCircle(x, y, r);
break;
}
case 5: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r = GeneratorInt(data, size) % 30;
canvas.DrawPointCircleFilled(x, y, r);
break;
}
case 6: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r1 = GeneratorInt(data, size) % 30;
int r2 = GeneratorInt(data, size) % 30;
canvas.DrawPointEllipse(x, y, r1, r2);
break;
}
case 7: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r1 = GeneratorInt(data, size) % 30;
int r2 = GeneratorInt(data, size) % 30;
canvas.DrawPointEllipseFilled(x, y, r1, r2);
break;
}
case 8: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
canvas.DrawBlockOn(x, y);
break;
}
case 9: {
int x1 = GeneratorInt(data, size);
int y1 = GeneratorInt(data, size);
int x2 = GeneratorInt(data, size);
int y2 = GeneratorInt(data, size);
canvas.DrawBlockLine(x1, y1, x2, y2);
break;
}
case 10: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r = GeneratorInt(data, size) % 30;
canvas.DrawBlockCircle(x, y, r);
break;
}
case 11: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
int r = GeneratorInt(data, size) % 30;
canvas.DrawBlockCircleFilled(x, y, r);
break;
}
case 12: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
std::string s = GeneratorString(data, size);
canvas.DrawText(x, y, s);
break;
}
case 13: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
canvas.GetCell(x, y);
break;
}
case 14: {
int x = GeneratorInt(data, size);
int y = GeneratorInt(data, size);
bool val = GeneratorBool(data, size);
canvas.DrawPoint(x, y, val);
break;
}
}
}

return 0;
}
132 changes: 132 additions & 0 deletions src/ftxui/component/color_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <cstddef>
#include <cstdint>
#include <string>
#include "ftxui/screen/color.hpp"
#include "ftxui/screen/terminal.hpp"

using namespace ftxui;

namespace {

uint8_t GeneratorByte(const char*& data, size_t& size) {
if (size == 0) {
return 0;
}
uint8_t out = static_cast<uint8_t>(data[0]);
data++;
size--;
return out;
}

float GeneratorFloat(const char*& data, size_t& size) {
if (size == 0) {
return 0.0f;
}
float out = float(static_cast<uint8_t>(data[0])) / 255.0f;
data++;
size--;
return out;
}

Color GeneratorColor(const char*& data, size_t& size) {
if (size < 4) {
return Color();
}
int type = GeneratorByte(data, size) % 5;
switch (type) {
case 0:
return Color(
static_cast<Color::Palette16>(GeneratorByte(data, size) % 16));
case 1:
return Color(static_cast<Color::Palette256>(GeneratorByte(data, size)));
case 2: {
uint8_t r = GeneratorByte(data, size);
uint8_t g = GeneratorByte(data, size);
uint8_t b = GeneratorByte(data, size);
return Color::RGB(r, g, b);
}
case 3: {
uint8_t h = GeneratorByte(data, size);
uint8_t s = GeneratorByte(data, size);
uint8_t v = GeneratorByte(data, size);
return Color::HSV(h, s, v);
}
case 4: {
uint8_t r = GeneratorByte(data, size);
uint8_t g = GeneratorByte(data, size);
uint8_t b = GeneratorByte(data, size);
uint8_t a = GeneratorByte(data, size);
return Color::RGBA(r, g, b, a);
}
default:
return Color();
}
}

} // namespace

extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
if (size < 5) {
return 0;
}

// Read terminal color support
int support = GeneratorByte(data, size) % 4;
switch (support) {
case 0:
Terminal::SetColorSupport(Terminal::Color::Palette1);
break;
case 1:
Terminal::SetColorSupport(Terminal::Color::Palette16);
break;
case 2:
Terminal::SetColorSupport(Terminal::Color::Palette256);
break;
case 3:
Terminal::SetColorSupport(Terminal::Color::TrueColor);
break;
}

while (size > 1) {
int op = GeneratorByte(data, size) % 4;
switch (op) {
case 0: {
Color c = GeneratorColor(data, size);
bool is_background = GeneratorByte(data, size) % 2;
c.Print(is_background);
break;
}
case 1: {
Color c1 = GeneratorColor(data, size);
Color c2 = GeneratorColor(data, size);
float t = GeneratorFloat(data, size);
Color result = Color::Interpolate(t, c1, c2);
result.Print(true);
result.Print(false);
break;
}
case 2: {
Color c1 = GeneratorColor(data, size);
Color c2 = GeneratorColor(data, size);
Color result = Color::Blend(c1, c2);
result.Print(true);
result.Print(false);
break;
}
case 3: {
uint8_t h = GeneratorByte(data, size);
uint8_t s = GeneratorByte(data, size);
uint8_t v = GeneratorByte(data, size);
uint8_t a = GeneratorByte(data, size);
Color result = Color::HSVA(h, s, v, a);
result.Print(true);
break;
}
}
}

return 0;
}
2 changes: 1 addition & 1 deletion src/ftxui/component/component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Element ComponentBase::OnRender() {
/// @return True when the event has been handled.
/// The default implementation called OnEvent on every child until one return
/// true. If none returns true, return false.
bool ComponentBase::OnEvent(Event event) { // NOLINT
bool ComponentBase::OnEvent(Event event) { // NOLINT
for (Component& child : impl_->children) { // NOLINT
if (child->OnEvent(event)) {
return true;
Expand Down
Loading
Loading