Skip to content

Commit fbb6592

Browse files
authored
Add support for stdarg.h (#2)
This PR adds support for `va_list, va_start, va_arg, va_end, va_copy`. `va_list` is implemented as `libcc2rs::VaList`, a cursor over a slice containing `VaArgs`. ```rs pub enum VaArg { Int(i32), UInt(u32), Long(i64), ULong(u64), Double(f64), RawPtr(*mut c_void), Ptr(AnyPtr), } pub struct VaList<'a> { args: &'a [VaArg], pos: usize, } impl<'a> VaList<'a> { pub fn arg<T: VaArgGet>(&mut self) -> T { ... } } ``` Callers of variadic functions build an array of `VaArgs`, for example: ```cpp int sum_ints(int first, ...); sum_ints(1, 2, 3, 4, 0) ``` becomes in unsafe ```rs pub unsafe fn sum_ints_0(mut first: i32, args: &[VaArg]) -> i32 sum_ints_0(1, &[2.into(), 3.into(), 4.into(), 0.into()]) ``` Then, a `va_start` call creates a new `VaList`: ```cpp va_list ap; va_start(ap, n); ``` ```rs let mut ap: VaList = <VaList>::default(); ap = VaList::new(args); ``` `va_end` is a no-op, `va_arg` translates to `VaList::arg`, and `va_copy` translates to cloning the `VaList`.
1 parent 647e59e commit fbb6592

49 files changed

Lines changed: 1886 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cpp2rust/compat/assert.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
#undef assert
77

8+
#ifndef __cplusplus
9+
#include <stdbool.h>
10+
#endif
11+
812
void __cpp2rust_assert_fail(bool condition) __attribute__((noreturn));
913

1014
#define assert(expr) __cpp2rust_assert_fail(expr)

cpp2rust/converter/converter.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,16 @@ bool Converter::VisitPointerType(clang::PointerType *type) {
243243
return false;
244244
}
245245

246+
if (IsVaListType(ctx_, clang::QualType(type, 0))) {
247+
StrCat("VaList");
248+
return false;
249+
}
250+
246251
StrCat(token::kStar);
247252
auto pointee_type = type->getPointeeType();
248253
StrCat(pointee_type.isConstQualified() ? keyword::kConst : keyword_mut_);
249254
if (pointee_type->isRecordType() &&
250-
abstract_structs_.contains(GetID(pointee_type->getAsCXXRecordDecl()))) {
255+
abstract_structs_.contains(GetID(pointee_type->getAsRecordDecl()))) {
251256
StrCat(keyword::kDyn);
252257
}
253258
return Convert(pointee_type);
@@ -352,9 +357,25 @@ bool Converter::VisitFunctionTemplateDecl(clang::FunctionTemplateDecl *decl) {
352357
return false;
353358
}
354359

360+
void Converter::ConvertVaListVarDecl(clang::VarDecl *decl) {
361+
if (clang::isa<clang::ParmVarDecl>(decl)) {
362+
// va_list parameter (decayed to __va_list_tag *)
363+
} else {
364+
// va_list local variable
365+
StrCat(keyword::kLet);
366+
}
367+
StrCat(keyword_mut_, GetNamedDeclAsString(decl), token::kColon, "VaList");
368+
}
369+
355370
bool Converter::ConvertVarDeclSkipInit(clang::VarDecl *decl) {
356371
auto qual_type = decl->getType();
357372
auto name = GetNamedDeclAsString(decl);
373+
374+
if (IsVaListType(ctx_, qual_type) && decl->isLocalVarDecl()) {
375+
ConvertVaListVarDecl(decl);
376+
return true;
377+
}
378+
358379
if (decl->isFileVarDecl()) {
359380
name = std::regex_replace(Mapper::ToString(decl), std::regex("::"), "_");
360381
if ((decl->isExternallyDeclarable() && !decl->hasInit()) ||
@@ -1259,7 +1280,28 @@ std::optional<std::string> Converter::TryPluginConvert(clang::CallExpr *call) {
12591280
return std::nullopt;
12601281
}
12611282

1283+
void Converter::ConvertVAArgCall(clang::CallExpr *expr) {
1284+
if (IsBuiltinVaStart(expr)) {
1285+
StrCat(ToString(expr->getArg(0)->IgnoreImpCasts()), "= VaList::new(args)");
1286+
return;
1287+
}
1288+
if (IsBuiltinVaEnd(expr)) {
1289+
// va_end is a no-op
1290+
return;
1291+
}
1292+
if (IsBuiltinVaCopy(expr)) {
1293+
StrCat(ToString(expr->getArg(0)->IgnoreImpCasts()), "=",
1294+
ToString(expr->getArg(1)->IgnoreImpCasts()), ".clone()");
1295+
return;
1296+
}
1297+
}
1298+
12621299
bool Converter::VisitCallExpr(clang::CallExpr *expr) {
1300+
if (IsBuiltinVaStart(expr) || IsBuiltinVaEnd(expr) || IsBuiltinVaCopy(expr)) {
1301+
ConvertVAArgCall(expr);
1302+
return false;
1303+
}
1304+
12631305
if (auto plugin_str = TryPluginConvert(expr)) {
12641306
StrCat(*plugin_str);
12651307
return false;
@@ -1356,11 +1398,16 @@ void Converter::ConvertGenericCallExpr(clang::CallExpr *expr) {
13561398
"Either function decl or function prototype should be known");
13571399

13581400
auto num_args = expr->getNumArgs() - arg_begin;
1401+
bool is_variadic =
1402+
function ? function->isVariadic() : (proto && proto->isVariadic());
1403+
unsigned num_named_params = function
1404+
? function->getNumParams()
1405+
: (proto ? proto->getNumParams() : num_args);
13591406

13601407
// Track which args are materialized temps bound to reference params
13611408
std::vector<std::string> temp_refs(num_args);
13621409

1363-
for (unsigned i = 0; i < num_args; ++i) {
1410+
for (unsigned i = 0; i < num_named_params && i < num_args; ++i) {
13641411
auto *arg = expr->getArg(i + arg_begin);
13651412
std::string param_name = function
13661413
? function->getParamDecl(i)->getNameAsString()
@@ -1387,7 +1434,7 @@ void Converter::ConvertGenericCallExpr(clang::CallExpr *expr) {
13871434

13881435
Convert(callee);
13891436
StrCat(token::kOpenParen);
1390-
for (unsigned i = 0; i < num_args; ++i) {
1437+
for (unsigned i = 0; i < num_named_params && i < num_args; ++i) {
13911438
auto *arg = expr->getArg(i + arg_begin);
13921439
std::string param_name = function
13931440
? function->getParamDecl(i)->getNameAsString()
@@ -1412,6 +1459,18 @@ void Converter::ConvertGenericCallExpr(clang::CallExpr *expr) {
14121459
}
14131460
StrCat(token::kComma);
14141461
}
1462+
1463+
// Variadic args: wrap in &[arg.into(), ...]
1464+
if (is_variadic) {
1465+
StrCat("& [");
1466+
for (unsigned i = num_named_params; i < num_args; ++i) {
1467+
auto *arg = expr->getArg(i + arg_begin);
1468+
Convert(arg);
1469+
StrCat(".into()", token::kComma);
1470+
}
1471+
StrCat("]");
1472+
}
1473+
14151474
StrCat(token::kCloseParen);
14161475
StrCat(token::kCloseCurlyBracket);
14171476
StrCat(token::kCloseParen);
@@ -1561,6 +1620,11 @@ bool Converter::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) {
15611620
clang::isa<clang::PredefinedExpr>(sub_expr)) {
15621621
return Convert(sub_expr);
15631622
}
1623+
// __va_list_tag [1] decays to __va_list_tag *. Just pass through by value
1624+
if (IsVaListType(ctx_, sub_expr->getType())) {
1625+
Convert(sub_expr);
1626+
break;
1627+
}
15641628
Convert(sub_expr);
15651629
if (sub_expr->getType().isConstQualified()) {
15661630
StrCat(keyword_ptr_decay_const_);
@@ -2209,6 +2273,18 @@ bool Converter::VisitCXXNullPtrLiteralExpr(clang::CXXNullPtrLiteralExpr *expr) {
22092273
return false;
22102274
}
22112275

2276+
bool Converter::VisitVAArgExpr(clang::VAArgExpr *expr) {
2277+
auto va_list_expr = expr->getSubExpr();
2278+
if (auto *cast = clang::dyn_cast<clang::ImplicitCastExpr>(va_list_expr)) {
2279+
va_list_expr = cast->getSubExpr();
2280+
}
2281+
Convert(va_list_expr);
2282+
StrCat(".arg::<");
2283+
Convert(expr->getType());
2284+
StrCat(">()");
2285+
return false;
2286+
}
2287+
22122288
bool Converter::VisitGNUNullExpr(clang::GNUNullExpr *expr) {
22132289
StrCat(keyword_default_);
22142290
computed_expr_type_ = ComputedExprType::FreshPointer;
@@ -2807,6 +2883,9 @@ void Converter::ConvertFunctionParameters(clang::FunctionDecl *decl) {
28072883
ConvertVarDeclSkipInit(parameter);
28082884
StrCat(token::kComma);
28092885
}
2886+
if (decl->isVariadic()) {
2887+
StrCat("args: &[VaArg]", token::kComma);
2888+
}
28102889
in_function_formals_ = false;
28112890
}
28122891

cpp2rust/converter/converter.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
8585

8686
virtual void ConvertGlobalVarDecl(clang::VarDecl *decl);
8787

88-
bool ConvertVarDeclSkipInit(clang::VarDecl *decl);
88+
virtual void ConvertVaListVarDecl(clang::VarDecl *decl);
89+
90+
virtual bool ConvertVarDeclSkipInit(clang::VarDecl *decl);
8991

9092
virtual bool ConvertLambdaVarDecl(clang::VarDecl *decl);
9193

@@ -199,6 +201,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
199201

200202
virtual void ConvertPrintf(clang::CallExpr *expr);
201203

204+
void ConvertVAArgCall(clang::CallExpr *expr);
205+
202206
virtual bool VisitCallExpr(clang::CallExpr *expr);
203207

204208
virtual bool VisitIntegerLiteral(clang::IntegerLiteral *expr);
@@ -273,6 +277,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
273277

274278
virtual bool VisitSwitchCase(clang::SwitchCase *stmt);
275279

280+
virtual bool VisitVAArgExpr(clang::VAArgExpr *expr);
281+
276282
virtual bool VisitCXXDefaultInitExpr(clang::CXXDefaultInitExpr *expr);
277283

278284
virtual bool VisitPredefinedExpr(clang::PredefinedExpr *expr);

cpp2rust/converter/converter_lib.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ unsigned GetArraySize(clang::QualType array_type) {
253253
}
254254

255255
std::string GetID(const clang::Decl *decl) {
256+
assert(decl);
256257
const auto file_name = GetFileName(decl);
257258
const auto line_num = GetLineNumber(decl);
258259
const auto column_num = GetColumnNumber(decl);
@@ -557,4 +558,58 @@ bool IsRedundantCopyInConversion(clang::ASTContext &ctx,
557558
return parent && parent->getConstructor()->isConvertingConstructor(false);
558559
}
559560

561+
// va_list is implemented as __va_list_tag[1] and decays to __va_list_tag *.
562+
// That's because va_list must have pointer semantics, but still be passed as
563+
// value by user code.
564+
bool IsVaListType(clang::ASTContext &ctx, clang::QualType type) {
565+
auto canonical = type.getCanonicalType();
566+
auto va_list = ctx.getBuiltinVaListType().getCanonicalType();
567+
568+
// Direct match: va_list itself
569+
if (canonical == va_list) {
570+
return true;
571+
}
572+
573+
// Decayed match: __va_list_tag[1] decays to __va_list_tag *
574+
if (auto *arr = clang::dyn_cast<clang::ConstantArrayType>(va_list)) {
575+
return canonical ==
576+
ctx.getPointerType(arr->getElementType()).getCanonicalType();
577+
}
578+
579+
return false;
580+
}
581+
582+
bool IsBuiltinVaStart(const clang::CallExpr *expr) {
583+
if (auto *fn = expr->getDirectCallee()) {
584+
return fn->getBuiltinID() == clang::Builtin::BI__builtin_va_start;
585+
}
586+
return false;
587+
}
588+
589+
bool IsBuiltinVaEnd(const clang::CallExpr *expr) {
590+
if (auto *fn = expr->getDirectCallee()) {
591+
return fn->getBuiltinID() == clang::Builtin::BI__builtin_va_end;
592+
}
593+
return false;
594+
}
595+
596+
bool IsBuiltinVaCopy(const clang::CallExpr *expr) {
597+
if (auto *fn = expr->getDirectCallee()) {
598+
return fn->getBuiltinID() == clang::Builtin::BI__builtin_va_copy;
599+
}
600+
return false;
601+
}
602+
603+
bool ContainsVAArgExpr(const clang::Stmt *stmt) {
604+
if (clang::isa<clang::VAArgExpr>(stmt)) {
605+
return true;
606+
}
607+
for (auto *child : stmt->children()) {
608+
if (ContainsVAArgExpr(child)) {
609+
return true;
610+
}
611+
}
612+
return false;
613+
}
614+
560615
} // namespace cpp2rust

cpp2rust/converter/converter_lib.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,14 @@ std::string GetClassName(clang::QualType type);
142142
bool IsRedundantCopyInConversion(clang::ASTContext &ctx,
143143
const clang::CXXConstructExpr *expr);
144144

145+
bool IsVaListType(clang::ASTContext &ctx, clang::QualType type);
146+
147+
bool IsBuiltinVaStart(const clang::CallExpr *expr);
148+
149+
bool IsBuiltinVaEnd(const clang::CallExpr *expr);
150+
151+
bool IsBuiltinVaCopy(const clang::CallExpr *expr);
152+
153+
bool ContainsVAArgExpr(const clang::Stmt *stmt);
154+
145155
} // namespace cpp2rust

cpp2rust/converter/models/converter_refcount.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ bool ConverterRefCount::VisitPointerType(clang::PointerType *type) {
172172
return false;
173173
}
174174

175+
if (IsVaListType(ctx_, clang::QualType(type, 0))) {
176+
StrCat("VaList");
177+
return false;
178+
}
179+
175180
if (type->isVoidPointerType()) {
176181
StrCat("AnyPtr");
177182
return false;
@@ -472,6 +477,17 @@ void ConverterRefCount::EmitFunctionPreamble(clang::FunctionDecl *decl) {
472477
}
473478
}
474479

480+
void ConverterRefCount::ConvertVaListVarDecl(clang::VarDecl *decl) {
481+
if (clang::isa<clang::ParmVarDecl>(decl)) {
482+
// va_list parameter (decayed to __va_list_tag *)
483+
} else {
484+
// va_list local variable
485+
StrCat(keyword::kLet);
486+
}
487+
488+
StrCat(GetNamedDeclAsString(decl), token::kColon, "Value<VaList>");
489+
}
490+
475491
bool ConverterRefCount::ConvertLambdaVarDecl(clang::VarDecl *decl) {
476492
return false;
477493
}
@@ -775,6 +791,11 @@ void ConverterRefCount::ConvertPrintf(clang::CallExpr *expr) {
775791
}
776792

777793
bool ConverterRefCount::VisitCallExpr(clang::CallExpr *expr) {
794+
if (IsBuiltinVaStart(expr) || IsBuiltinVaEnd(expr) || IsBuiltinVaCopy(expr)) {
795+
ConvertVAArgCall(expr);
796+
return false;
797+
}
798+
778799
if (expr->isCallToStdMove()) {
779800
if (IsUniquePtr(expr->getArg(0)->getType())) {
780801
StrCat(std::format("{}.take()", ConvertLValue(expr->getArg(0))));
@@ -912,6 +933,10 @@ bool ConverterRefCount::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) {
912933
}
913934

914935
if (expr->getCastKind() == clang::CastKind::CK_ArrayToPointerDecay) {
936+
if (IsVaListType(ctx_, sub_expr->getType())) {
937+
Convert(sub_expr);
938+
return false;
939+
}
915940
if (clang::isa<clang::StringLiteral>(sub_expr) ||
916941
clang::isa<clang::PredefinedExpr>(sub_expr)) {
917942
StrCat(std::format("Ptr::from_string_literal({})", ToString(sub_expr)));
@@ -1366,6 +1391,18 @@ bool ConverterRefCount::VisitImplicitValueInitExpr(
13661391
return Converter::VisitImplicitValueInitExpr(expr);
13671392
}
13681393

1394+
bool ConverterRefCount::VisitVAArgExpr(clang::VAArgExpr *expr) {
1395+
auto va_list_expr = expr->getSubExpr();
1396+
if (auto *cast = clang::dyn_cast<clang::ImplicitCastExpr>(va_list_expr)) {
1397+
va_list_expr = cast->getSubExpr();
1398+
}
1399+
StrCat(ConvertLValue(va_list_expr));
1400+
StrCat(".arg::<");
1401+
StrCat(GetUnsafeTypeAsString(expr->getType()));
1402+
StrCat(">()");
1403+
return false;
1404+
}
1405+
13691406
bool ConverterRefCount::VisitCXXDefaultArgExpr(clang::CXXDefaultArgExpr *expr) {
13701407
return Converter::VisitCXXDefaultArgExpr(expr);
13711408
}
@@ -1569,9 +1606,14 @@ void ConverterRefCount::ConvertGenericBinaryOperator(
15691606
auto sides_contain_ptr_or_deref = std::ranges::any_of(rhs_vars, predicate) ||
15701607
std::ranges::any_of(lhs_vars, predicate);
15711608

1572-
auto may_cause_borrow_mut_err = !sides_contains_literal &&
1573-
!same_var_on_both_sides &&
1574-
sides_contain_ptr_or_deref;
1609+
auto both_sides_have_va_arg = same_var_on_both_sides &&
1610+
ContainsVAArgExpr(lhs) &&
1611+
ContainsVAArgExpr(rhs);
1612+
1613+
auto may_cause_borrow_mut_err =
1614+
both_sides_have_va_arg ||
1615+
(!sides_contains_literal && !same_var_on_both_sides &&
1616+
sides_contain_ptr_or_deref);
15751617

15761618
if (may_cause_borrow_mut_err) {
15771619
StrCat(std::format("{{ let _lhs = {}; _lhs {} {} }}",

cpp2rust/converter/models/converter_refcount.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class ConverterRefCount final : public Converter {
4747

4848
void ConvertGlobalVarDecl(clang::VarDecl *decl) override;
4949

50+
void ConvertVaListVarDecl(clang::VarDecl *decl) override;
51+
5052
bool ConvertLambdaVarDecl(clang::VarDecl *decl) override;
5153

5254
bool VisitDeclRefExpr(clang::DeclRefExpr *expr) override;
@@ -93,6 +95,8 @@ class ConverterRefCount final : public Converter {
9395

9496
bool VisitImplicitValueInitExpr(clang::ImplicitValueInitExpr *expr) override;
9597

98+
bool VisitVAArgExpr(clang::VAArgExpr *expr) override;
99+
96100
void ConvertArrayCXXConstructExpr(clang::CXXConstructExpr *expr) override;
97101

98102
bool VisitCXXDefaultArgExpr(clang::CXXDefaultArgExpr *expr) override;

libcc2rs/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ pub use iterators::*;
2424

2525
mod compat;
2626
pub use compat::*;
27+
28+
mod va_args;
29+
pub use va_args::*;

0 commit comments

Comments
 (0)