From cf96ccd6fb7805751b05e4db7bc8394c702aa463 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sat, 23 May 2026 17:13:52 +0100 Subject: [PATCH 01/19] Refactor ConvertGenericCallExpr --- cpp2rust/converter/converter.cpp | 203 ++++++++++++++++++------------- cpp2rust/converter/converter.h | 33 +++++ 2 files changed, 150 insertions(+), 86 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 63734c9e..5be7bd60 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1517,125 +1517,156 @@ void Converter::ConvertFunctionToFunctionPointer( StrCat(std::format("Some({})", Mapper::MapFunctionName(fn_decl))); } -void Converter::ConvertGenericCallExpr(clang::CallExpr *expr) { - clang::Expr *callee = expr->getCallee(); - auto convert_param_ty = [&](clang::QualType param_type, clang::Expr *expr) { - if (param_type->isLValueReferenceType()) { - PushExprKind push(*this, ExprKind::AddrOf); - ConvertVarInit(param_type, expr); - } else { - ConvertVarInit(param_type, expr); - } - }; +Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { + using Kind = CallArg::Kind; - unsigned arg_begin = 0; // skip count for operator()'s implicit object arg + CallInfo info{}; + info.callee = expr->getCallee(); + unsigned arg_begin = 0; if (auto op_call = llvm::dyn_cast(expr)) { if (op_call->getOperator() == clang::OO_Call) { - callee = op_call->getArg(0); + info.callee = op_call->getArg(0); arg_begin = 1; } } - PushParen outer(*this); - StrCat(keyword_unsafe_); - PushBrace unsafe_brace(*this); const auto *function = expr->getCalleeDecl() ? expr->getCalleeDecl()->getAsFunction() : nullptr; const clang::FunctionProtoType *proto = nullptr; - if (!function) { - auto callee_ty = callee->getType().getDesugaredType(ctx_); + auto callee_ty = info.callee->getType().getDesugaredType(ctx_); if (auto ptr_ty = callee_ty->getAs()) { proto = ptr_ty->getPointeeType()->getAs(); } } - assert((function || proto) && "Either function decl or function prototype should be known"); - auto num_args = expr->getNumArgs() - arg_begin; - bool is_variadic = - function ? function->isVariadic() : (proto && proto->isVariadic()); - unsigned num_named_params = function - ? function->getNumParams() - : (proto ? proto->getNumParams() : num_args); - - // Track which args are materialized temps bound to reference params - std::vector temp_refs(num_args); + unsigned num_args = expr->getNumArgs() - arg_begin; + unsigned num_named_params = + function ? function->getNumParams() : proto->getNumParams(); + info.is_variadic = function ? function->isVariadic() : proto->isVariadic(); + info.is_fn_ptr_call = !function; for (unsigned i = 0; i < num_named_params && i < num_args; ++i) { auto *arg = expr->getArg(i + arg_begin); - std::string param_name = function - ? function->getParamDecl(i)->getNameAsString() - : ("arg" + std::to_string(i)); - clang::QualType param_type = function ? function->getParamDecl(i)->getType() - : proto->getParamType(i); - - bool is_materialize_to_ref = - clang::isa(arg) && - param_type->isLValueReferenceType(); - - if (is_materialize_to_ref) { - auto [binding, ref] = - MaterializeTemp(std::format("_{}", param_name), param_type, arg); - StrCat(binding); - temp_refs[i] = std::move(ref); - } else if (!clang::isa(arg)) { - StrCat("let", std::format("_{}: {}", param_name, ToString(param_type)), - "="); - convert_param_ty(param_type, arg); - StrCat(";"); + CallArg ca{ + .expr = arg, + .kind = Kind::Hoisted, + .param_name = function ? function->getParamDecl(i)->getNameAsString() + : ("arg" + std::to_string(i)), + .param_type = function ? function->getParamDecl(i)->getType() + : proto->getParamType(i), + .has_default = function && function->getParamDecl(i)->hasDefaultArg(), + }; + bool is_materialize = clang::isa(arg); + if (is_materialize && ca.param_type->isLValueReferenceType()) { + ca.kind = Kind::Materialized; + } else if (is_materialize) { + ca.kind = Kind::Inline; + } + info.args.push_back(std::move(ca)); + } + + if (info.is_variadic) { + for (unsigned i = num_named_params; i < num_args; ++i) { + info.variadic_args.push_back(expr->getArg(i + arg_begin)); } } - if (proto && !function) { - EmitFnPtrCall(callee); + return info; +} + +void Converter::ConvertParamTy(clang::QualType param_type, clang::Expr *expr) { + if (param_type->isLValueReferenceType()) { + PushExprKind push(*this, ExprKind::AddrOf); + ConvertVarInit(param_type, expr); } else { - PushExprKind push(*this, ExprKind::Callee); - Convert(callee); + ConvertVarInit(param_type, expr); } - { - PushParen call_args(*this); - for (unsigned i = 0; i < num_named_params && i < num_args; ++i) { - auto *arg = expr->getArg(i + arg_begin); - std::string param_name = - function ? function->getParamDecl(i)->getNameAsString() - : ("arg" + std::to_string(i)); - clang::QualType param_type = function - ? function->getParamDecl(i)->getType() - : proto->getParamType(i); - bool is_parm_with_default_value = - function && function->getParamDecl(i)->hasDefaultArg(); - - if (is_parm_with_default_value) { - StrCat("Some("); - } - if (!temp_refs[i].empty()) { - StrCat(temp_refs[i]); - } else if (clang::isa(arg)) { - convert_param_ty(param_type, arg); - } else { - StrCat(std::format("_{}", param_name)); - } - if (is_parm_with_default_value) { - StrCat(')'); - } - StrCat(token::kComma); +} + +void Converter::EmitArgBindings(CallInfo &info) { + using Kind = CallArg::Kind; + for (auto &ca : info.args) { + switch (ca.kind) { + case Kind::Hoisted: + StrCat("let", + std::format("_{}: {}", ca.param_name, ToString(ca.param_type)), + "="); + ConvertParamTy(ca.param_type, ca.expr); + StrCat(";"); + break; + case Kind::Materialized: { + auto [binding, ref] = MaterializeTemp(std::format("_{}", ca.param_name), + ca.param_type, ca.expr); + StrCat(binding); + ca.ref_temp_name = std::move(ref); + break; } + case Kind::Inline: + break; + } + } +} + +void Converter::EmitArgList(const CallInfo &info) { + using Kind = CallArg::Kind; + PushParen call_args(*this); - // Variadic args: wrap in &[arg.into(), ...] - if (is_variadic) { - StrCat("& ["); - for (unsigned i = num_named_params; i < num_args; ++i) { - auto *arg = expr->getArg(i + arg_begin); - ConvertVariadicArg(arg); - StrCat(".into()", token::kComma); + for (const auto &ca : info.args) { + if (ca.has_default) { + StrCat("Some"); + } + + { + PushParen push(*this, ca.has_default); + switch (ca.kind) { + case Kind::Hoisted: + StrCat(std::format("_{}", ca.param_name)); + break; + case Kind::Materialized: + StrCat(ca.ref_temp_name); + break; + case Kind::Inline: + ConvertParamTy(ca.param_type, ca.expr); + break; } - StrCat(']'); + } + + StrCat(token::kComma); + } + + if (info.is_variadic) { + StrCat(token::kRef); + PushBracket push(*this); + for (auto *arg : info.variadic_args) { + ConvertVariadicArg(arg); + StrCat(".into()", token::kComma); } } } +void Converter::EmitCall(CallInfo info) { + EmitArgBindings(info); + + if (info.is_fn_ptr_call) { + EmitFnPtrCall(info.callee); + } else { + PushExprKind push(*this, ExprKind::Callee); + Convert(info.callee); + } + + EmitArgList(info); +} + +void Converter::ConvertGenericCallExpr(clang::CallExpr *expr) { + PushParen outer(*this); + StrCat(keyword_unsafe_); + PushBrace unsafe_brace(*this); + EmitCall(CollectCallInfo(expr)); +} + std::optional Converter::ConvertCallExpr(clang::CallExpr *expr) { auto *callee = expr->getCallee(); diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 8d192aa7..9aa361fd 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -219,6 +219,39 @@ class Converter : public clang::RecursiveASTVisitor { std::optional ConvertCallExpr(clang::CallExpr *expr); + struct CallArg { + enum class Kind { + Hoisted, + Inline, + Materialized, + }; + + clang::Expr *expr; + Kind kind; + std::string param_name; + clang::QualType param_type; + bool has_default; + std::string ref_temp_name; + }; + + struct CallInfo { + clang::Expr *callee; + bool is_variadic; + bool is_fn_ptr_call; + std::vector args; + std::vector variadic_args; + }; + + CallInfo CollectCallInfo(clang::CallExpr *expr); + + void ConvertParamTy(clang::QualType param_type, clang::Expr *expr); + + void EmitArgBindings(CallInfo &info); + + void EmitArgList(const CallInfo &info); + + void EmitCall(CallInfo info); + void ConvertGenericCallExpr(clang::CallExpr *expr); virtual void EmitFnPtrCall(clang::Expr *callee); From 1c0fb3a3b5bdbdd354fb117680eb30c0d572978d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sat, 23 May 2026 18:44:57 +0100 Subject: [PATCH 02/19] EmitArgBindings -> EmitHoistedArgs --- cpp2rust/converter/converter.cpp | 4 ++-- cpp2rust/converter/converter.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 5be7bd60..563d9953 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1586,7 +1586,7 @@ void Converter::ConvertParamTy(clang::QualType param_type, clang::Expr *expr) { } } -void Converter::EmitArgBindings(CallInfo &info) { +void Converter::EmitHoistedArgs(CallInfo &info) { using Kind = CallArg::Kind; for (auto &ca : info.args) { switch (ca.kind) { @@ -1648,7 +1648,7 @@ void Converter::EmitArgList(const CallInfo &info) { } void Converter::EmitCall(CallInfo info) { - EmitArgBindings(info); + EmitHoistedArgs(info); if (info.is_fn_ptr_call) { EmitFnPtrCall(info.callee); diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 9aa361fd..8fe7687f 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -246,7 +246,7 @@ class Converter : public clang::RecursiveASTVisitor { void ConvertParamTy(clang::QualType param_type, clang::Expr *expr); - void EmitArgBindings(CallInfo &info); + void EmitHoistedArgs(CallInfo &info); void EmitArgList(const CallInfo &info); From e98df0c08a21740d2a10338c7e4a7aa5d449621e Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 24 May 2026 12:14:19 +0100 Subject: [PATCH 03/19] Drop default initialization fom CallInfo --- cpp2rust/converter/converter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 563d9953..6d7d74d2 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1520,7 +1520,7 @@ void Converter::ConvertFunctionToFunctionPointer( Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { using Kind = CallArg::Kind; - CallInfo info{}; + CallInfo info; info.callee = expr->getCallee(); unsigned arg_begin = 0; if (auto op_call = llvm::dyn_cast(expr)) { From 01765d8afeb498facbc24f1c056b29e0a1b8c3f8 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 24 May 2026 12:14:50 +0100 Subject: [PATCH 04/19] Add prefix on param_name from construction --- cpp2rust/converter/converter.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 6d7d74d2..e1a65bd1 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1553,8 +1553,9 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { CallArg ca{ .expr = arg, .kind = Kind::Hoisted, - .param_name = function ? function->getParamDecl(i)->getNameAsString() - : ("arg" + std::to_string(i)), + .param_name = function + ? ("_" + function->getParamDecl(i)->getNameAsString()) + : ("_arg" + std::to_string(i)), .param_type = function ? function->getParamDecl(i)->getType() : proto->getParamType(i), .has_default = function && function->getParamDecl(i)->hasDefaultArg(), @@ -1591,15 +1592,14 @@ void Converter::EmitHoistedArgs(CallInfo &info) { for (auto &ca : info.args) { switch (ca.kind) { case Kind::Hoisted: - StrCat("let", - std::format("_{}: {}", ca.param_name, ToString(ca.param_type)), - "="); + StrCat( + std::format("let {}: {} =", ca.param_name, ToString(ca.param_type))); ConvertParamTy(ca.param_type, ca.expr); StrCat(";"); break; case Kind::Materialized: { - auto [binding, ref] = MaterializeTemp(std::format("_{}", ca.param_name), - ca.param_type, ca.expr); + auto [binding, ref] = + MaterializeTemp(ca.param_name, ca.param_type, ca.expr); StrCat(binding); ca.ref_temp_name = std::move(ref); break; @@ -1623,7 +1623,7 @@ void Converter::EmitArgList(const CallInfo &info) { PushParen push(*this, ca.has_default); switch (ca.kind) { case Kind::Hoisted: - StrCat(std::format("_{}", ca.param_name)); + StrCat(ca.param_name); break; case Kind::Materialized: StrCat(ca.ref_temp_name); From dba4b3b6fca8a3ca05044078c25aa6725e459135 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 24 May 2026 12:15:11 +0100 Subject: [PATCH 05/19] Pass CallInfo by rvalue reference --- cpp2rust/converter/converter.cpp | 2 +- cpp2rust/converter/converter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index e1a65bd1..986252cf 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1647,7 +1647,7 @@ void Converter::EmitArgList(const CallInfo &info) { } } -void Converter::EmitCall(CallInfo info) { +void Converter::EmitCall(CallInfo &&info) { EmitHoistedArgs(info); if (info.is_fn_ptr_call) { diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 8fe7687f..29540a6b 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -250,7 +250,7 @@ class Converter : public clang::RecursiveASTVisitor { void EmitArgList(const CallInfo &info); - void EmitCall(CallInfo info); + void EmitCall(CallInfo &&info); void ConvertGenericCallExpr(clang::CallExpr *expr); From 5c1425d1b181084c95a14cadf4b7ce9b99450625 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 24 May 2026 12:15:27 +0100 Subject: [PATCH 06/19] Reorder CallArg fields by size --- cpp2rust/converter/converter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 29540a6b..0117b4f8 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -220,18 +220,18 @@ class Converter : public clang::RecursiveASTVisitor { std::optional ConvertCallExpr(clang::CallExpr *expr); struct CallArg { - enum class Kind { + enum class Kind : int8_t { Hoisted, Inline, Materialized, }; - clang::Expr *expr; - Kind kind; std::string param_name; + std::string ref_temp_name; clang::QualType param_type; + clang::Expr *expr; bool has_default; - std::string ref_temp_name; + Kind kind; }; struct CallInfo { From 06097f8aef0db0d3dbe2cf87c1506cd68a939634 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 24 May 2026 12:18:54 +0100 Subject: [PATCH 07/19] Reorder initialization --- cpp2rust/converter/converter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 986252cf..6a5e3dc1 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1551,14 +1551,14 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { for (unsigned i = 0; i < num_named_params && i < num_args; ++i) { auto *arg = expr->getArg(i + arg_begin); CallArg ca{ - .expr = arg, - .kind = Kind::Hoisted, .param_name = function ? ("_" + function->getParamDecl(i)->getNameAsString()) : ("_arg" + std::to_string(i)), .param_type = function ? function->getParamDecl(i)->getType() : proto->getParamType(i), + .expr = arg, .has_default = function && function->getParamDecl(i)->hasDefaultArg(), + .kind = Kind::Hoisted, }; bool is_materialize = clang::isa(arg); if (is_materialize && ca.param_type->isLValueReferenceType()) { From 27fd2af67db0c0f76872f0b61e932b97fda027ed Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 21:44:32 +0100 Subject: [PATCH 08/19] Reorder CallInfo fields --- cpp2rust/converter/converter.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 0117b4f8..7b463c1c 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -235,11 +235,11 @@ class Converter : public clang::RecursiveASTVisitor { }; struct CallInfo { + std::vector args; + std::vector variadic_args; clang::Expr *callee; bool is_variadic; bool is_fn_ptr_call; - std::vector args; - std::vector variadic_args; }; CallInfo CollectCallInfo(clang::CallExpr *expr); From 768b308a1a8690378331cda371c8cefda593c8e7 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Mon, 25 May 2026 15:32:24 +0100 Subject: [PATCH 09/19] Use template syntax to declare rules for variadic functions This allows writing ```cpp template int f1(int a0, int a1, Args... args) { return fcntl(a0, a1, args...); } ``` Which becomes `f1: int fcntl(int, int, ...)` --- cpp2rust/converter/mapper.cpp | 6 ++++++ cpp2rust/cpp_rule_preprocessor.cpp | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index fc8779fa..506c1430 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -792,6 +792,12 @@ std::string ToString(const clang::NamedDecl *decl) { } os << ToString(func_decl->getParamDecl(i)->getType()); } + if (func_decl->isVariadic()) { + if (func_decl->getNumParams()) { + os << ", "; + } + os << "..."; + } os << ')'; if (const auto *method_decl = diff --git a/cpp2rust/cpp_rule_preprocessor.cpp b/cpp2rust/cpp_rule_preprocessor.cpp index e7d1ae2f..3a9c9502 100644 --- a/cpp2rust/cpp_rule_preprocessor.cpp +++ b/cpp2rust/cpp_rule_preprocessor.cpp @@ -349,7 +349,10 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback { createTemplateArguments(clang::TemplateDecl *decl, llvm::SmallVectorImpl &out) { for (clang::NamedDecl *param : *decl->getTemplateParameters()) { - if (llvm::isa(param)) { + if (param->isTemplateParameterPack()) { + out.emplace_back( + clang::TemplateArgument::CreatePackCopy(sema_->Context, {})); + } else if (llvm::isa(param)) { clang::RecordDecl *rdecl = createRecordDecl(param->getName()); clang::QualType type = sema_->Context.getTagType(clang::ElaboratedTypeKeyword::None, @@ -409,7 +412,14 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback { clang::OverloadCandidateSet &candidates) { clang::LookupResult decls(*sema_, name, loc_, clang::Sema::LookupOrdinaryName); - sema_->LookupQualifiedName(decls, sema_->getStdNamespace()); + if (clang::NamespaceDecl *std_ns = sema_->getStdNamespace()) { + sema_->LookupQualifiedName(decls, std_ns); + } + if (decls.empty()) { + decls.clear(); + sema_->LookupQualifiedName(decls, + sema_->Context.getTranslationUnitDecl()); + } for (auto *ndecl : decls) { if (auto *candidate = createCandidate(ndecl, callArgs, explicitTArgs)) { sema_->AddOverloadCandidate( @@ -573,6 +583,7 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback { cxxConstructorNameLookup(rule->getReturnType(), callArgs, candidates); break; case LookupKind::ADL: + regularNameLookup(callArgs, &explicitTArgs, name, candidates); adlLookup(callArgs, name, candidates); break; } From 60fc170545130e9ea47c7d0fdca96b3c85d01394 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 14:52:55 +0100 Subject: [PATCH 10/19] Add src rules for variadic functions --- rules/fcntl/src.cpp | 14 ++++++++++++++ rules/ioctl/src.cpp | 9 +++++++++ rules/stdio/src.cpp | 5 +++++ 3 files changed, 28 insertions(+) create mode 100644 rules/fcntl/src.cpp create mode 100644 rules/ioctl/src.cpp diff --git a/rules/fcntl/src.cpp b/rules/fcntl/src.cpp new file mode 100644 index 00000000..657b35cc --- /dev/null +++ b/rules/fcntl/src.cpp @@ -0,0 +1,14 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +template +int f1(int a0, int a1, Args... args) { + return fcntl(a0, a1, args...); +} + +template +int f2(const char *a0, int a1, Args... args) { + return open(a0, a1, args...); +} diff --git a/rules/ioctl/src.cpp b/rules/ioctl/src.cpp new file mode 100644 index 00000000..02587ce4 --- /dev/null +++ b/rules/ioctl/src.cpp @@ -0,0 +1,9 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +template +int f1(int a0, unsigned long a1, Args... args) { + return ioctl(a0, a1, args...); +} diff --git a/rules/stdio/src.cpp b/rules/stdio/src.cpp index b4a62ea4..4c7b9071 100644 --- a/rules/stdio/src.cpp +++ b/rules/stdio/src.cpp @@ -56,3 +56,8 @@ int f19(FILE *stream, off_t offset, int whence) { } FILE *f20(int fd, const char *mode) { return fdopen(fd, mode); } + +template +int f21(char *a0, size_t a1, const char *a2, Args... args) { + return snprintf(a0, a1, a2, args...); +} From 08d2cc1316b5f6d93a712be29d29c9e87f1c29fd Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 15:20:32 +0100 Subject: [PATCH 11/19] Add Rust rule counterpart --- rule-preprocessor/src/ir.rs | 7 ++++++- rule-preprocessor/src/syntactic.rs | 9 +++++++++ rules/fcntl/tgt_unsafe.rs | 7 +++++++ rules/ioctl/tgt_unsafe.rs | 6 ++++++ rules/src/modules.rs | 4 ++++ rules/stdio/tgt_unsafe.rs | 4 ++++ 6 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 rules/fcntl/tgt_unsafe.rs create mode 100644 rules/ioctl/tgt_unsafe.rs diff --git a/rule-preprocessor/src/ir.rs b/rule-preprocessor/src/ir.rs index 4697724d..cacb3101 100644 --- a/rule-preprocessor/src/ir.rs +++ b/rule-preprocessor/src/ir.rs @@ -57,6 +57,8 @@ pub struct FnIr { pub params: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub return_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_variadic: Option, } impl FnIr { @@ -104,7 +106,10 @@ impl FnIr { 1, &format!("Rule {name} generics"), ); - assert!(!self.body.is_empty(), "Rule {name}: body must not be empty"); + assert!( + self.is_variadic == Some(true) || !self.body.is_empty(), + "Rule {name}: body must not be empty" + ); } } diff --git a/rule-preprocessor/src/syntactic.rs b/rule-preprocessor/src/syntactic.rs index 191dac3c..c5b9884f 100644 --- a/rule-preprocessor/src/syntactic.rs +++ b/rule-preprocessor/src/syntactic.rs @@ -415,6 +415,14 @@ impl<'a> FnIrBuilder<'a> { .unwrap_or(Access::Read) } + fn is_variadic(&self) -> bool { + self.fn_item + .param_list() + .into_iter() + .flat_map(|pl| pl.params()) + .any(|p| p.dotdotdot_token().is_some()) + } + fn returns_mut_ref(&self) -> bool { self.fn_item .ret_type() @@ -516,6 +524,7 @@ impl<'a> FnIrBuilder<'a> { }, multi_statement, body, + is_variadic: self.is_variadic().then_some(true), }; ir.validate(&format!("{}:{}", path.display(), fn_name)); ir diff --git a/rules/fcntl/tgt_unsafe.rs b/rules/fcntl/tgt_unsafe.rs new file mode 100644 index 00000000..82efd8e2 --- /dev/null +++ b/rules/fcntl/tgt_unsafe.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +unsafe extern "C" { + fn f1(a0: i32, a1: i32, ...) -> i32; + fn f2(a0: *const u8, a1: i32, ...) -> i32; +} diff --git a/rules/ioctl/tgt_unsafe.rs b/rules/ioctl/tgt_unsafe.rs new file mode 100644 index 00000000..00d6cbea --- /dev/null +++ b/rules/ioctl/tgt_unsafe.rs @@ -0,0 +1,6 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +unsafe extern "C" { + fn f1(a0: i32, a1: u64, ...) -> i32; +} diff --git a/rules/src/modules.rs b/rules/src/modules.rs index 2b5d751c..7f943d30 100644 --- a/rules/src/modules.rs +++ b/rules/src/modules.rs @@ -38,12 +38,16 @@ pub mod deque_tgt_refcount; pub mod deque_tgt_unsafe; #[path = r#"../errno/tgt_unsafe.rs"#] pub mod errno_tgt_unsafe; +#[path = r#"../fcntl/tgt_unsafe.rs"#] +pub mod fcntl_tgt_unsafe; #[path = r#"../fstream/tgt_refcount.rs"#] pub mod fstream_tgt_refcount; #[path = r#"../fstream/tgt_unsafe.rs"#] pub mod fstream_tgt_unsafe; #[path = r#"../initializer_list/tgt_unsafe.rs"#] pub mod initializer_list_tgt_unsafe; +#[path = r#"../ioctl/tgt_unsafe.rs"#] +pub mod ioctl_tgt_unsafe; #[path = r#"../iomanip/tgt_unsafe.rs"#] pub mod iomanip_tgt_unsafe; #[path = r#"../iostream/tgt_refcount.rs"#] diff --git a/rules/stdio/tgt_unsafe.rs b/rules/stdio/tgt_unsafe.rs index ce96445f..6378f318 100644 --- a/rules/stdio/tgt_unsafe.rs +++ b/rules/stdio/tgt_unsafe.rs @@ -85,3 +85,7 @@ unsafe fn f19(a0: *mut ::libc::FILE, a1: i64, a2: i32) -> i32 { unsafe fn f20(a0: i32, a1: *const u8) -> *mut ::libc::FILE { libc::fdopen(a0, a1 as *const i8) } + +unsafe extern "C" { + fn f21(a0: *mut u8, a1: u64, a2: *const u8, ...) -> i32; +} From c85429df5caa6474c2a9f1f9f2f5afc632e38ef0 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 15:56:49 +0100 Subject: [PATCH 12/19] Add C rules for builtin mul overflow In C++, builtin mul overflow is a variadic function: `bool (...)`. In C is an unprototyped function: `int ()`. --- rules/builtin/tgt_refcount.rs | 10 ++++++++++ rules/builtin/tgt_unsafe.rs | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/rules/builtin/tgt_refcount.rs b/rules/builtin/tgt_refcount.rs index 0f076e1c..4e1afbbc 100644 --- a/rules/builtin/tgt_refcount.rs +++ b/rules/builtin/tgt_refcount.rs @@ -13,3 +13,13 @@ fn f10(a0: i64, a1: i64, a2: Ptr) -> bool { a2.write(val); ovf } +fn f12(a0: i64, a1: i64, a2: Ptr) -> bool { + let (val, ovf) = a0.overflowing_mul(a1); + a2.write(val); + ovf +} +fn f13(a0: i64, a1: i64, a2: Ptr) -> bool { + let (val, ovf) = a0.overflowing_mul(a1); + a2.write(val); + ovf +} diff --git a/rules/builtin/tgt_unsafe.rs b/rules/builtin/tgt_unsafe.rs index 16638e3f..1d0e7cb8 100644 --- a/rules/builtin/tgt_unsafe.rs +++ b/rules/builtin/tgt_unsafe.rs @@ -39,3 +39,14 @@ unsafe fn f10(a0: i64, a1: i64, a2: *mut i64) -> bool { unsafe fn f11() { std::hint::spin_loop(); } + +unsafe fn f12(a0: i64, a1: i64, a2: *mut i64) -> bool { + let (val, ovf) = a0.overflowing_mul(a1); + *a2 = val; + ovf +} +unsafe fn f13(a0: i64, a1: i64, a2: *mut i64) -> bool { + let (val, ovf) = a0.overflowing_mul(a1); + *a2 = val; + ovf +} From d2ac8488b3a65fc6f4c6d4a9a5bc7307e4cb5eea Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 20:50:33 +0100 Subject: [PATCH 13/19] Read the is_variadic attribute on the other side --- cpp2rust/converter/converter.cpp | 7 +++++++ cpp2rust/converter/translation_rule.cpp | 3 +++ cpp2rust/converter/translation_rule.h | 1 + 3 files changed, 11 insertions(+) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 6a5e3dc1..4024916a 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1447,6 +1447,13 @@ bool Converter::VisitCallExpr(clang::CallExpr *expr) { } if (Mapper::Contains(expr->getCallee())) { + if (auto tgt_ir = Mapper::GetExprRule(GetCalleeOrExpr(expr))) { + if (tgt_ir->body.empty() && tgt_ir->is_variadic) { + ConvertGenericCallExpr(expr); + return false; + } + } + auto **args = expr->getArgs(); auto num_args = expr->getNumArgs(); auto ctx = CollectPrvalueToLRefArgs(expr); diff --git a/cpp2rust/converter/translation_rule.cpp b/cpp2rust/converter/translation_rule.cpp index 3f24e59a..02a7d948 100644 --- a/cpp2rust/converter/translation_rule.cpp +++ b/cpp2rust/converter/translation_rule.cpp @@ -104,6 +104,9 @@ ExprRule ParseExprRuleJSON(const llvm::json::Object &obj) { if (auto ms = obj.getBoolean("multi_statement")) ir.multi_statement = *ms; + if (auto v = obj.getBoolean("is_variadic")) + ir.is_variadic = *v; + if (auto *generics = obj.getObject("generics")) { for (auto &[key, val] : *generics) { if (auto *arr = val.getAsArray()) { diff --git a/cpp2rust/converter/translation_rule.h b/cpp2rust/converter/translation_rule.h index a2962cec..e48242d1 100644 --- a/cpp2rust/converter/translation_rule.h +++ b/cpp2rust/converter/translation_rule.h @@ -70,6 +70,7 @@ struct ExprRule { std::vector> generics; // "T1" -> ["Ord", "Clone"] std::vector body; bool multi_statement = false; + bool is_variadic = false; void dump() const; void validate(const std::string &name) const; From 8b98f291c5b789e8e145543e471611724c0819c8 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 20:51:11 +0100 Subject: [PATCH 14/19] Save CallExpr instead of callee in CallInfo --- cpp2rust/converter/converter.cpp | 18 +++++++++++------- cpp2rust/converter/converter.h | 3 ++- cpp2rust/converter/converter_lib.cpp | 9 +++++++++ cpp2rust/converter/converter_lib.h | 2 ++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 4024916a..393e603d 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1528,20 +1528,20 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { using Kind = CallArg::Kind; CallInfo info; - info.callee = expr->getCallee(); + info.expr = expr; + auto callee = GetCallee(expr); unsigned arg_begin = 0; if (auto op_call = llvm::dyn_cast(expr)) { if (op_call->getOperator() == clang::OO_Call) { - info.callee = op_call->getArg(0); arg_begin = 1; } } - const auto *function = - expr->getCalleeDecl() ? expr->getCalleeDecl()->getAsFunction() : nullptr; + auto decl = expr->getCalleeDecl(); + const auto *function = decl ? decl->getAsFunction() : nullptr; const clang::FunctionProtoType *proto = nullptr; if (!function) { - auto callee_ty = info.callee->getType().getDesugaredType(ctx_); + auto callee_ty = callee->getType().getDesugaredType(ctx_); if (auto ptr_ty = callee_ty->getAs()) { proto = ptr_ty->getPointeeType()->getAs(); } @@ -1658,10 +1658,14 @@ void Converter::EmitCall(CallInfo &&info) { EmitHoistedArgs(info); if (info.is_fn_ptr_call) { - EmitFnPtrCall(info.callee); + EmitFnPtrCall(GetCallee(info.expr)); + } else if (info.is_libc_passthrough) { + auto *direct_callee = info.expr->getDirectCallee(); + assert(direct_callee); + StrCat("libc::", direct_callee->getName()); } else { PushExprKind push(*this, ExprKind::Callee); - Convert(info.callee); + Convert(GetCallee(info.expr)); } EmitArgList(info); diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 7b463c1c..c8877a4f 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -237,9 +237,10 @@ class Converter : public clang::RecursiveASTVisitor { struct CallInfo { std::vector args; std::vector variadic_args; - clang::Expr *callee; + clang::CallExpr *expr; bool is_variadic; bool is_fn_ptr_call; + bool is_libc_passthrough; }; CallInfo CollectCallInfo(clang::CallExpr *expr); diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 16576371..dcda0905 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -588,6 +588,15 @@ BuildUnifiedArgs(clang::Expr *expr, clang::Expr **args, unsigned num_args) { return all_args; } +clang::Expr *GetCallee(clang::CallExpr *expr) { + if (auto op_call = clang::dyn_cast(expr)) { + if (op_call->getOperator() == clang::OO_Call) { + return op_call->getArg(0); + } + } + return expr->getCallee(); +} + clang::Expr *GetCalleeOrExpr(clang::Expr *expr) { if (auto *call = clang::dyn_cast(expr)) { return call->getCallee(); diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index 6a6a70ec..d67c74b9 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -129,6 +129,8 @@ void ForEachTemplateArgument( clang::Expr *GetCallObject(clang::CallExpr *expr); +clang::Expr *GetCallee(clang::CallExpr *expr); + clang::Expr *GetCalleeOrExpr(clang::Expr *expr); bool HasReceiver(clang::Expr *expr); From 91b69ead6c4e9d6f690ecfc035e569687c84f716 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 20:51:28 +0100 Subject: [PATCH 15/19] Set is_libc_passthrough --- cpp2rust/converter/converter.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 393e603d..111da267 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1554,6 +1554,8 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { function ? function->getNumParams() : proto->getNumParams(); info.is_variadic = function ? function->isVariadic() : proto->isVariadic(); info.is_fn_ptr_call = !function; + info.is_libc_passthrough = + decl && ctx_.getSourceManager().isInSystemHeader(decl->getLocation()); for (unsigned i = 0; i < num_named_params && i < num_args; ++i) { auto *arg = expr->getArg(i + arg_begin); @@ -1565,7 +1567,7 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) { : proto->getParamType(i), .expr = arg, .has_default = function && function->getParamDecl(i)->hasDefaultArg(), - .kind = Kind::Hoisted, + .kind = info.is_libc_passthrough ? Kind::Inline : Kind::Hoisted, }; bool is_materialize = clang::isa(arg); if (is_materialize && ca.param_type->isLValueReferenceType()) { @@ -1645,11 +1647,16 @@ void Converter::EmitArgList(const CallInfo &info) { } if (info.is_variadic) { - StrCat(token::kRef); - PushBracket push(*this); + if (!info.is_libc_passthrough) { + StrCat(token::kRef); + } + PushBracket push(*this, !info.is_libc_passthrough); for (auto *arg : info.variadic_args) { ConvertVariadicArg(arg); - StrCat(".into()", token::kComma); + if (!info.is_libc_passthrough) { + StrCat(".into()"); + } + StrCat(token::kComma); } } } From ba997cda867f2194392772fdb54dd126fb8c88bb Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 21:40:47 +0100 Subject: [PATCH 16/19] Use correct types for arguments --- rules/fcntl/tgt_unsafe.rs | 2 +- rules/stdio/tgt_unsafe.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/fcntl/tgt_unsafe.rs b/rules/fcntl/tgt_unsafe.rs index 82efd8e2..be3a31ab 100644 --- a/rules/fcntl/tgt_unsafe.rs +++ b/rules/fcntl/tgt_unsafe.rs @@ -3,5 +3,5 @@ unsafe extern "C" { fn f1(a0: i32, a1: i32, ...) -> i32; - fn f2(a0: *const u8, a1: i32, ...) -> i32; + fn f2(a0: *const i8, a1: i32, ...) -> i32; } diff --git a/rules/stdio/tgt_unsafe.rs b/rules/stdio/tgt_unsafe.rs index 6378f318..d63969ff 100644 --- a/rules/stdio/tgt_unsafe.rs +++ b/rules/stdio/tgt_unsafe.rs @@ -87,5 +87,5 @@ unsafe fn f20(a0: i32, a1: *const u8) -> *mut ::libc::FILE { } unsafe extern "C" { - fn f21(a0: *mut u8, a1: u64, a2: *const u8, ...) -> i32; + fn f21(a0: *mut i8, a1: usize, a2: *const i8, ...) -> i32; } From 108cdb72eb1ba544dfe04d65cc84dce10aedd8fe Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 21:43:07 +0100 Subject: [PATCH 17/19] Use declared type for each argument in passthrough mode --- cpp2rust/converter/converter.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 111da267..c3947a23 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1623,7 +1623,9 @@ void Converter::EmitArgList(const CallInfo &info) { using Kind = CallArg::Kind; PushParen call_args(*this); - for (const auto &ca : info.args) { + for (unsigned i = 0; i < info.args.size(); i++) { + const auto &ca = info.args[i]; + if (ca.has_default) { StrCat("Some"); } @@ -1639,6 +1641,9 @@ void Converter::EmitArgList(const CallInfo &info) { break; case Kind::Inline: ConvertParamTy(ca.param_type, ca.expr); + if (info.is_libc_passthrough) { + StrCat(std::format("as {}", Mapper::GetParamType(callee, i))); + } break; } } From f000eb560217cfdc4ffbc04f633f48ba6619b6de Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 21:49:52 +0100 Subject: [PATCH 18/19] Fix code --- cpp2rust/converter/converter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index c3947a23..8d2102ed 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1642,7 +1642,8 @@ void Converter::EmitArgList(const CallInfo &info) { case Kind::Inline: ConvertParamTy(ca.param_type, ca.expr); if (info.is_libc_passthrough) { - StrCat(std::format("as {}", Mapper::GetParamType(callee, i))); + StrCat(std::format( + "as {}", Mapper::GetParamType(GetCalleeOrExpr(info.expr), i))); } break; } From 70f5880524c0f48581b1d9fa2e60383b46b31cf9 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 21:59:02 +0100 Subject: [PATCH 19/19] Missing part of c85429df5caa6474c2a9f1f9f2f5afc632e38ef0 --- rules/builtin/src.c | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 rules/builtin/src.c diff --git a/rules/builtin/src.c b/rules/builtin/src.c new file mode 100644 index 00000000..805fbc1c --- /dev/null +++ b/rules/builtin/src.c @@ -0,0 +1,2 @@ +int f12(long a, long b, long *r) { return __builtin_mul_overflow(a, b, r); } +int f13(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }