Skip to content
Open
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
63 changes: 63 additions & 0 deletions .agents/docs/2026-10-18-add-safeint-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Add SafeInt 3.0.28a as compat.safeint

## Source and shape

- Upstream: <https://github.com/dcleblanc/SafeInt>.
- Version: `3.0.28a`, the newest upstream tag. The local checkout is 25
commits past that tag, so the package pins the release rather than an
untagged development snapshot.
- License: MIT.
- Shape: Form B, header-only. `SafeInt.hpp`, `safe_math.h`, and
`safe_math_impl.h` are at the archive root, so `include_dirs = { "*" }`
matches upstream's CMake INTERFACE include directory. A trivial anchor C TU
supplies mcpp's buildable `safeint` lib target.
- No features: the archive has no optional compilable library component.
Upstream's tests are intentionally excluded.

Upstream provides two public APIs. `SafeInt.hpp` implements the C++
`SafeInt<T>` class. `safe_math.h` provides checked C arithmetic for 32- and
64-bit integers. `safe_math.h` raises `#error` when `SafeInt.hpp` has already
been included, so the test project exercises them from separate translation
units.

## Download and mirror

- GLOBAL:
`https://github.com/dcleblanc/SafeInt/archive/refs/tags/3.0.28a.tar.gz`
- SHA-256:
`9e652d065a3cef80623287d5dc61edcf6a95ddab38a9dfeb34f155261fc9cef7`
- The archive was downloaded twice and the bytes compared; both downloads had
the same digest.
- No verified `mcpp-res/safeint` release is available, so the descriptor uses
the repository's plain-string upstream URL fallback. It does not invent a CN
mirror. A maintainer can add a byte-identical GitCode release later.

## Test member

Two members are registered in the root workspace because mcpp builds every
`tests/*.cpp` as an independent executable (they are tests, not object files
linked into one binary):

- `tests/examples/safeint/`: SafeInt addition, subtraction, multiplication and
division; successful and rejected `SafeCast`; and overflow and
divide-by-zero exception codes.
- `tests/examples/safeint-c-api/`: checked add/subtract/multiply/divide through
`safe_math.h`, including rejection of `INT32_MAX * 2`. The header is wrapped
in `extern "C"` here because upstream's own C++ linkage spelling is
inconsistent, while the intended consumer is C.

Every assertion affects the process exit status (`return ok ? 0 : 1`).

## Verification

- Pinned mcpp `2026.10.1.2`, the version in `validate.yml`:
`mcpp xpkg parse --all-os pkgs/c/compat.safeint.lua` reports `parse OK` for
linux, macosx, and windows.
- Pinned cold test:
`mcpp test -p safeint -p safeint-c-api --cache off` reports
`workspace result ok. 2 member(s); 2 passed; 0 failed`.
- Full relevant lint passed: Lua syntax, mirror URL policy, package identity,
reserved namespaces, cross-package references, platform parity, and
duplicate versions.
- A direct GCC 16.2.1 compile against the local checkout also ran both tests:
`safeint OK` and `safeint C API OK`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ One descriptor per common shape:
| Native module library (Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`gzj-creator.galay`](pkgs/g/gzj-creator.galay.lua) |
| C sources + `features` | [`compat.cjson`](pkgs/c/compat.cjson.lua) |
| C++ sources, several versions | [`compat.yaml-cpp`](pkgs/c/compat.yaml-cpp.lua) |
| Header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) |
| Header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) · [`compat.safeint`](pkgs/c/compat.safeint.lua) |
| Generated config header | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) |
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) |
| C++23 module shipped by upstream | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| 原生模块库(Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`gzj-creator.galay`](pkgs/g/gzj-creator.galay.lua) |
| C 源码 + `features` | [`compat.cjson`](pkgs/c/compat.cjson.lua) |
| C++ 源码,多版本 | [`compat.yaml-cpp`](pkgs/c/compat.yaml-cpp.lua) |
| header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) |
| header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) · [`compat.safeint`](pkgs/c/compat.safeint.lua) |
| 生成 config 头 | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) |
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) |
| 上游自带 C++23 module | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) |
Expand Down
2 changes: 2 additions & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ members = [
"tests/examples/redis-plus-plus",
"tests/examples/redis-plus-plus-v133",
"tests/examples/redis-plus-plus-async",
"tests/examples/safeint",
"tests/examples/safeint-c-api",
"tests/examples/sdl2",
"tests/examples/sdl3",
"tests/examples/spdlog",
Expand Down
63 changes: 63 additions & 0 deletions pkgs/c/compat.safeint.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
-- Form B inline descriptor for SafeInt — header-only checked integer arithmetic.
--
-- Upstream ships two independent public APIs:
-- * SafeInt.hpp — the C++ SafeInt<T> class;
-- * safe_math.h — checked C arithmetic over 32- and 64-bit integers.
--
-- Both headers, and safe_math.h's safe_math_impl.h, sit at the archive root,
-- exactly like upstream's INTERFACE include directory. A trivial anchor TU
-- gives mcpp a buildable lib target, following the compat.gtl / compat.eigen
-- shape. There are no optional compilable components to expose as features.
--
-- safe_math.h deliberately rejects a translation unit that has already
-- included SafeInt.hpp, so these APIs must be consumed from separate TUs.
-- A C++ TU using only the C API should wrap #include <safe_math.h> in
-- extern "C"; this keeps upstream's prototype and inline definitions on the
-- same linkage.
package = {
spec = "1",
namespace = "compat",
name = "safeint",
description = "Header-only checked integer arithmetic for C++ and C",
licenses = {"MIT"},
repo = "https://github.com/dcleblanc/SafeInt",
type = "package",

xpm = {
linux = {
["3.0.28a"] = {
-- No verified mcpp-res release is available: use the upstream
-- archive directly rather than fabricating a CN mirror table.
url = "https://github.com/dcleblanc/SafeInt/archive/refs/tags/3.0.28a.tar.gz",
sha256 = "9e652d065a3cef80623287d5dc61edcf6a95ddab38a9dfeb34f155261fc9cef7",
},
},
macosx = {
["3.0.28a"] = {
url = "https://github.com/dcleblanc/SafeInt/archive/refs/tags/3.0.28a.tar.gz",
sha256 = "9e652d065a3cef80623287d5dc61edcf6a95ddab38a9dfeb34f155261fc9cef7",
},
},
windows = {
["3.0.28a"] = {
url = "https://github.com/dcleblanc/SafeInt/archive/refs/tags/3.0.28a.tar.gz",
sha256 = "9e652d065a3cef80623287d5dc61edcf6a95ddab38a9dfeb34f155261fc9cef7",
},
},
},

mcpp = {
language = "c++23",
import_std = false,
c_standard = "c11",
include_dirs = { "*" },
generated_files = {
["mcpp_generated/safeint_anchor.c"] = [==[
int mcpp_compat_safeint_headers_anchor(void) { return 0; }
]==],
},
sources = { "mcpp_generated/safeint_anchor.c" },
targets = { ["safeint"] = { kind = "lib" } },
deps = { },
},
}
8 changes: 8 additions & 0 deletions tests/examples/safeint-c-api/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The C API cannot share a translation unit with SafeInt.hpp, and mcpp builds
# every tests/*.cpp as its own executable, so it gets a second member.
[package]
name = "safeint-c-api-tests"
version = "0.1.0"

[dependencies.compat]
safeint = "3.0.28a"
37 changes: 37 additions & 0 deletions tests/examples/safeint-c-api/tests/safe_math.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// safe_math.h is the C API. Include it inside extern "C" so its prototype and
// inline definitions share C linkage; it also rejects a translation unit that
// included SafeInt.hpp.
#include <cstdio>

extern "C" {
#include <safe_math.h>
}

int main() {
bool ok = true;
auto check = [&](bool condition, const char* what) {
if (!condition) {
std::printf("FAIL: %s\n", what);
ok = false;
}
};

int result = 0;

check(check_add_int_int(40, 2, &result) && result == 42,
"checked C addition");
check(check_sub_int_int(44, 2, &result) && result == 42,
"checked C subtraction");
check(check_mul_int_int(21, 2, &result) && result == 42,
"checked C multiplication");
check(check_div_int_int(84, 2, &result) && result == 42,
"checked C division");
check(!check_mul_int_int(INT32_MAX, 2, &result),
"checked C multiplication rejects overflow");

if (ok) {
std::puts("safeint C API OK");
}

return ok ? 0 : 1;
}
9 changes: 9 additions & 0 deletions tests/examples/safeint/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SafeInt test project: one TU covers SafeInt.hpp and another covers the
# independent safe_math.h API. Upstream forbids including both headers in the
# same translation unit.
[package]
name = "safeint-tests"
version = "0.1.0"

[dependencies.compat]
safeint = "3.0.28a"
56 changes: 56 additions & 0 deletions tests/examples/safeint/tests/safeint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// compat.safeint — assert the C++ class's normal arithmetic, non-throwing cast
// check, and both overflow and divide-by-zero exception paths.
#include <SafeInt.hpp>

#include <cstdint>
#include <cstdio>
#include <limits>

int main() {
bool ok = true;
auto check = [&](bool condition, const char* what) {
if (!condition) {
std::printf("FAIL: %s\n", what);
ok = false;
}
};

SafeInt<std::int32_t> lhs = 40;
SafeInt<std::int32_t> rhs = 2;
check(static_cast<std::int32_t>(lhs + rhs) == 42,
"SafeInt addition");
check(static_cast<std::int32_t>((lhs + rhs) * 2) == 84,
"SafeInt multiplication");
check(static_cast<std::int32_t>((lhs + rhs) / rhs) == 21,
"SafeInt division");

std::int32_t narrowed = 0;
check(SafeCast(std::uint64_t{42}, narrowed) && narrowed == 42,
"SafeCast accepts an in-range value");
check(!SafeCast(std::numeric_limits<std::uint64_t>::max(), narrowed),
"SafeCast rejects an out-of-range value");

bool overflow_reported = false;
try {
SafeInt<std::int32_t> maximum =
std::numeric_limits<std::int32_t>::max();
maximum += 1;
} catch (const SafeIntException& error) {
overflow_reported = error.m_code == SafeIntArithmeticOverflow;
}
check(overflow_reported, "overflow throws SafeIntArithmeticOverflow");

bool div_zero_reported = false;
try {
SafeInt<std::int32_t> value = 42;
value /= 0;
} catch (const SafeIntException& error) {
div_zero_reported = error.m_code == SafeIntDivideByZero;
}
check(div_zero_reported, "divide by zero throws SafeIntDivideByZero");

if (ok) {
std::puts("safeint OK");
}
return ok ? 0 : 1;
}
Loading