Intercept assert into runtime error.

This commit is contained in:
Alejandro Cabeza Romero 2026-05-20 16:15:25 +02:00
parent 173c610821
commit 297c7c59ee
No known key found for this signature in database
GPG Key ID: DA3D14AE478030FD
7 changed files with 58 additions and 8 deletions

View File

@ -83,6 +83,7 @@ runs:
cp "${SOURCES_ROOT}/circom_adapter.hpp" "${CIRCUIT_CPP_PATH}/circom_adapter.hpp"
cp "${SOURCES_ROOT}/circom_fwd.hpp" "${CIRCUIT_CPP_PATH}/circom_fwd.hpp"
cp "${SOURCES_ROOT}/types.hpp" "${CIRCUIT_CPP_PATH}/types.hpp"
cp "${SOURCES_ROOT}/assert.h" "${CIRCUIT_CPP_PATH}/assert.h"
# TODO: Instead of replace, make a fork that generates the appropriate Makefile
- name: Replace ${{ inputs.circuit-name-display }}'s Makefile

View File

@ -35,7 +35,7 @@ poq: check-circom
# circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop
{{sed_i}} ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' blend/poq_cpp/main.cpp
cp -r {{src}}/poq blend/poq_cpp/poq
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp blend/poq_cpp/
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h blend/poq_cpp/
cp {{ci_makefile}} blend/poq_cpp/Makefile
cp blend/test_ffi.cpp blend/poq_cpp/test_ffi.cpp
make -C blend/poq_cpp PROJECT=poq linux-lib
@ -51,7 +51,7 @@ pol: check-circom
# circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop
{{sed_i}} ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/pol_cpp/main.cpp
cp -r {{src}}/pol mantle/pol_cpp/pol
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/pol_cpp/
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h mantle/pol_cpp/
cp {{ci_makefile}} mantle/pol_cpp/Makefile
cp mantle/test_pol.cpp mantle/pol_cpp/test_pol.cpp
make -C mantle/pol_cpp PROJECT=pol linux-lib
@ -67,7 +67,7 @@ poc: check-circom
# circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop
{{sed_i}} ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/poc_cpp/main.cpp
cp -r {{src}}/poc mantle/poc_cpp/poc
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/poc_cpp/
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h mantle/poc_cpp/
cp {{ci_makefile}} mantle/poc_cpp/Makefile
cp mantle/test_poc.cpp mantle/poc_cpp/test_poc.cpp
make -C mantle/poc_cpp PROJECT=poc linux-lib
@ -83,7 +83,7 @@ signature: check-circom
# circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop
{{sed_i}} ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/signature_cpp/main.cpp
cp -r {{src}}/signature mantle/signature_cpp/signature
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/signature_cpp/
cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h mantle/signature_cpp/
cp {{ci_makefile}} mantle/signature_cpp/Makefile
cp mantle/test_signature.cpp mantle/signature_cpp/test_signature.cpp
make -C mantle/signature_cpp PROJECT=signature linux-lib

25
src/assert.h Normal file
View File

@ -0,0 +1,25 @@
// Assert-to-throw shim for circom-generated circuit code.
//
// Problem
// -------
// Circom generates C++ code that calls the standard assert() macro to enforce
// circuit constraints (e.g. `assert(Fr_isTrue(&expaux[0]))`). When compiled
// into a standalone binary, a failing assert aborts the subprocess and the
// caller receives a non-zero exit code; an error. When compiled into a static
// library and linked into the caller's process, the same abort kills the entire
// process. A library must never call abort() on its caller.
//
// Mechanism
// ---------
// This file is copied into each circuit's build directory as assert.h. The
// Makefile already passes -I. so the compiler finds this file before the
// system assert.h when the generated code does `#include <assert.h>`.
// #include_next <assert.h> pulls in the real system header (so all
// declarations are present), then we redefine the assert macro to throw a
// std::runtime_error instead of calling abort(). #pragma once prevents a
// second include from re-running and undoing the redefinition.
#pragma once
#include_next <assert.h>
#undef assert
#include <stdexcept>
#define assert(cond) ((cond) ? void(0) : throw std::runtime_error("Failed assert: " #cond))

View File

@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) {
Circom_Circuit* circuit = loadCircuit(circuit_bytes);
Circom_CalcWit* ctx = new Circom_CalcWit(circuit);
loadJson(ctx, input->inputs_json);
try {
loadJson(ctx, input->inputs_json);
} catch (...) {
delete ctx;
delete circuit;
throw;
}
if (ctx->getRemaingInputsToBeSet()!=0) {
const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + ".";
delete ctx;

View File

@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) {
Circom_Circuit* circuit = loadCircuit(circuit_bytes);
Circom_CalcWit* ctx = new Circom_CalcWit(circuit);
loadJson(ctx, input->inputs_json);
try {
loadJson(ctx, input->inputs_json);
} catch (...) {
delete ctx;
delete circuit;
throw;
}
if (ctx->getRemaingInputsToBeSet()!=0) {
const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + ".";
delete ctx;

View File

@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) {
Circom_Circuit* circuit = loadCircuit(circuit_bytes);
Circom_CalcWit* ctx = new Circom_CalcWit(circuit);
loadJson(ctx, input->inputs_json);
try {
loadJson(ctx, input->inputs_json);
} catch (...) {
delete ctx;
delete circuit;
throw;
}
if (ctx->getRemaingInputsToBeSet()!=0) {
const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + ".";
delete ctx;

View File

@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) {
Circom_Circuit* circuit = loadCircuit(circuit_bytes);
Circom_CalcWit* ctx = new Circom_CalcWit(circuit);
loadJson(ctx, input->inputs_json);
try {
loadJson(ctx, input->inputs_json);
} catch (...) {
delete ctx;
delete circuit;
throw;
}
if (ctx->getRemaingInputsToBeSet()!=0) {
const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + ".";
delete ctx;