From 82599166844b5224ab99659850c8082a9a3c3e93 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 15:44:51 +0100 Subject: [PATCH 1/7] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit faa6d928c58b144648a61c1c3c2847397c45bfbc Merge: 943b49750 72bb583d7 Author: Lucian Popescu Date: Tue Oct 13 15:43:19 2026 +0100 Merge branch 'master' into issue-15 commit 943b49750111c5053cde8e92866386960c158654 Author: Lucian Popescu Date: Tue Oct 13 15:39:03 2026 +0100 Don't clone entire row on global access commit 434c7bbf506a7fa95f529c67ef1870556b3532dc Author: Lucian Popescu Date: Tue Oct 13 14:56:15 2026 +0100 Add UB tests commit 03d04950cd299eabd790fb7db25cc496bcb59e27 Author: Lucian Popescu Date: Tue Oct 13 14:50:20 2026 +0100 Update docs commit 1240976194f7676d7870e607492aab8c973997d3 Author: Lucian Popescu Date: Tue Oct 13 14:45:05 2026 +0100 Add reinterpret for nested pointers commit 97cfa97bbf59db6f73e7b424fc6bbe25344a12a2 Author: Lucian Popescu Date: Tue Oct 13 14:44:20 2026 +0100 Update tests commit b0f2819648b1583f42c216250acb9ac3a2b38e6f Author: Lucian Popescu Date: Tue Oct 13 14:36:14 2026 +0100 Fix pointer subscript commit 8d675d13945efee622952b052e02b27ae5d3877c Author: Lucian Popescu Date: Tue Oct 13 14:15:56 2026 +0100 Fix imbricated access for array commit ce937c1eb124111e194abd5f200a3cefc28de1ab Author: Lucian Popescu Date: Tue Oct 13 14:08:56 2026 +0100 Update tests commit 3a0358fa56d2db56c5579a95a0cfbb568ab9f8b3 Author: Lucian Popescu Date: Tue Oct 13 14:03:34 2026 +0100 Drop boxing for multi dimensional arrays commit 9118872d0ca88012d31204091d9b43e049474a79 Author: Lucian Popescu Date: Tue Oct 13 14:00:09 2026 +0100 Add library support for multi dimensional containers commit c157d41ba52103a960a018c1f340bac20528c05c Author: Lucian Popescu Date: Tue Oct 13 13:59:53 2026 +0100 Drop Value from imbricated containers commit 6b083f6235f153f2cd8eb0948b783d9f14a30239 Author: Lucian Popescu Date: Tue Oct 13 13:53:58 2026 +0100 Delete vector rules commit 78d6570714b4eacb601f613e98c89306d17447ab Author: Lucian Popescu Date: Mon Oct 12 21:35:16 2026 +0100 Fix list initializers for unions (#514) commit e7d20118c6ddf771c25a9a5b90c0c82e1138de02 Author: Lucian Popescu Date: Mon Oct 12 15:15:52 2026 +0100 Format rules (#528) commit ba095717c5fee13684d9e865095e892b15565361 Author: Lucian Popescu Date: Mon Oct 12 10:39:46 2026 +0100 Name anonymous records (#521) commit cd5b552d100b47110bdba4db8d8368a24b62dfd7 Author: Lucian Popescu Date: Mon Oct 12 10:21:23 2026 +0100 Delete abstract_structs_ (#526) We don't need extra state to translate abstract structs. commit eacf79e9fa09adcfaba29698aba496dc8fe9458a Author: Lucian Popescu Date: Mon Oct 12 10:20:27 2026 +0100 Escape Rust keywords (#519) commit 6627b2f75b2f6e9620130ba8fe6d0d29f4e87387 Author: Lucian Popescu Date: Mon Oct 12 10:19:57 2026 +0100 Call printf rule when the format string is not static (#508) commit d343b2777a8d0c56c398a533f9c8a3412bb2024c Author: Lucian Popescu Date: Sun Oct 11 21:11:14 2026 +0100 Skip elidable constructors (#525) commit 6e47d5b61b3a027b84f5c48926e4520ff12c1f22 Author: Lucian Popescu Date: Sun Oct 11 15:59:50 2026 +0100 Fix woff2 build after #523 (#524) I realized that #523 fully works only with the new AST matcher because of const types: string matcher matches strings which is broken for const pointers in general. Until the AST matcher lands I fixed the string matcher. commit 80c214f82b8620fece91c7842b76118cce30d0a8 Author: Lucian Popescu Date: Sun Oct 11 13:51:19 2026 +0100 Remove converter hack around std::map::iterator (#523) The old code special cased `std::map::iterator` (which is just a sugar for `std::pair`) to generate `i.first()` and `i.second()` instead of `i.0` and `i.1`. This PR removes the hack by saving a pair in `libcc2rs::MapIter` which can be accessed with `.0` and `.1`. It also fixes a translation bug: ```cpp for (auto i : m) { i.second = -1; assert(i.second == -1); } for (const auto &i : m) { assert(i.second != -1); } ``` commit a447c09c3d45100afa8ddeaeb38f2ac5ed144807 Author: Lucian Popescu Date: Sun Oct 11 08:15:51 2026 +0100 [NFC] Pass Converter to Mapper (#450) The current string matcher must save string rules for user defined types. For example `std::vector` works by first applying the string rule for vector then the string rule for UserDefinedRecord. The new AST matcher (#447) won't need string rules for user defined types. After applying the rule for vector it will ask Converter to print UserDefinedRecord. I also added a test that fails with a current matcher and will pass with the AST matcher: `std::vector`. There is no string rule for `S * const *` and the current matcher panics. commit 79fd877916655b1f46140a98aa85a2a31d1fb1e9 Author: Lucian Popescu Date: Sun Oct 11 08:14:21 2026 +0100 Translate va_list references and pointers (#522) commit 2411760b3e1e819b20d341b5eaf545129314594c Author: Lucian Popescu Date: Sun Oct 11 08:11:22 2026 +0100 Write mutable fields through const objects (#520) commit 86861510dfea6a3807cb0c5d86d8555c6fecbfb0 Author: Nuno Lopes Date: Sat Oct 10 12:41:58 2026 +0100 Fix clang 24 build commit 69fdca5d94c4b078d0d7f2e466baa3cc8ed04227 Author: Lucian Popescu Date: Sat Oct 10 12:24:22 2026 +0100 Selectively load rules in the same TU as the translated code (#451) Part of #447. This only loads the rules into cpp2rust, AST matching comes in a future PR: > To implement this, rules are included in the translated TU. Only the rules that parse are kept. If the TU never included then there are no rules for map. This is faster than the string matching approach. cpp_rule_indexer runs at build time to build an index of rules. This index is used by rule_loader to lazily load the rules into the converter for doing AST matching. There are 2 index files: one for C code and one for C++ code. Both live in build/rules_index. C translated code only loads the C index, C++ translated code loads both indexes. Example of index: ``` key: std/vector/push_back requires: std/vector dir: vector text: template void f14(std::vector &o, T1 &&value) { return o.push_back(std::move(value)); } key: std/basic_ifstream/rdbuf requires: std/basic_ifstream, std/basic_filebuf dir: ifstream text: std::filebuf *f4(const std::ifstream &o) { return o.rdbuf(); } ``` `key` is generated using the dependent name of the rules for templates and using the concrete name for non-templates. `text` is extracted from the file and appended into the translated TU if it needs the rule. `requires` only loads the rule in the TU if the translated code uses all the dependencies, basic_ifstream and basic_fielbuf in the example. This makes the translation fast. With 100K dummy rules, it takes on avergage 20ms per invocation to selectively load the rules. With full loading of all the 100K dummy rules, it takes 5 seconds on average. commit ebb7f176804416fd5996346e93e7e91e97b3d70d Author: Nuno Lopes Date: Sat Oct 10 11:54:35 2026 +0100 fix tests commit 4701d294776ee363faa76e9df50b394e16926ad3 Author: Lucian Popescu Date: Sat Oct 10 11:37:07 2026 +0100 Skip explicit specializations of template functions (#517) commit ce92121d0f63fe70f543474626fba7f9059b1d17 Author: Lucian Popescu Date: Sat Oct 10 11:35:34 2026 +0100 Set computed expression type for default args and init lists (#516) commit b0cddeb77da7b1d55619dace8006ec7488a2f15a Author: Lucian Popescu Date: Sat Oct 10 11:34:51 2026 +0100 Fix lambda calls, parameters, captures and namespace-scope lambdas (#501) In VisitCallExpr convert the arguments of the operator call in Unboxed context to avoid unnecessary Value boxes Use ConvertVarDeclSkipInit to convert the params of the operator call so that it can add mut where needed PushConstInitializer(false) before converting the body of lambda to fix translation of global lambdas Call the move constructor when the lambda is moved Fix generic lambdas with no specialisations in libcc2rs-macros Fix lambda in lambda syntax in libcc2rs-macros commit b7476ab50a67316c81eea3c0753166afb3597629 Author: Lucian Popescu Date: Sat Oct 10 08:16:03 2026 +0100 Fix signed printing of integers (#511) commit 99206c57f4947c6a3ba3646f8a7bac00fd4033e0 Author: Lucian Popescu Date: Sat Oct 10 08:12:55 2026 +0100 Translate unsafe Default in unsafe context (#509) commit 43d63624f52cb770996cc3c3e9062032416657a5 Author: Lucian Popescu Date: Sat Oct 10 08:09:33 2026 +0100 Fix delete for const pointers (#513) commit 5c0309a5b38288016ccd9cdc7cead0d11f13010f Author: Lucian Popescu Date: Sat Oct 10 08:09:16 2026 +0100 Push init type in materialized temporaries (#512) commit 32c5b1f830f258a3245e819e278340e663960601 Author: Lucian Popescu Date: Sat Oct 10 08:03:30 2026 +0100 Make const variables that need destruction mut in unsafe (#515) commit 2ae262e13dd8669ef90320279d5654ca54ffab42 Author: Lucian Popescu Date: Sat Oct 10 07:59:33 2026 +0100 Accept the j format specifier and escape braces in printf (#518) commit e6c335eadc1f30dd56a6970871d6030f31efa8f8 Author: Lucian Popescu Date: Fri Oct 9 19:05:28 2026 +0100 Fix variables named _ (#502) commit e986cc86236699bb95923a94f4e33a14484d84f5 Author: Lucian Popescu Date: Fri Oct 9 18:57:15 2026 +0100 Fix Default for floating types and floating to boolean casts (#505) commit 1757106b8a31eb0d60bca8ad340f7b7a8dda91d2 Author: Lucian Popescu Date: Fri Oct 9 18:56:24 2026 +0100 Fix converter crashes around freshness (#503) commit 29ff9cd90b3aaad2772cd0e11193254fa49bf9fc Author: Lucian Popescu Date: Fri Oct 9 18:55:43 2026 +0100 Fix Default for enums with bool underlying type (#504) commit c5fbc922ed8901a79c280acddd4e796366e2919e Author: Lucian Popescu Date: Fri Oct 9 18:55:00 2026 +0100 Extend arity of function pointer arguments (#507) commit ab5265de2b8f07e6867c2d9befb89c737ff07cd5 Author: Lucian Popescu Date: Fri Oct 9 18:54:42 2026 +0100 Fix const_cast for references (#506) commit 0834e8884eff3fd872620efc008d631424b560d0 Author: Lucian Popescu Date: Fri Oct 9 13:56:05 2026 +0100 Translate self referencing initialiser (#498) Fixes the original issue reported in #248. Self-refrencing in constructors is not handled here. It translates: ```cpp struct list_head list = { &list }; ``` to: ```rs let list: Value = >::default(); *list.borrow_mut() = list_head { next: (list.as_pointer()), }; ``` That is, first create a default value, then assign it the self-referencing initialiser. Types that have defaults with side-effects are not supported. commit c4276ba0126554ed86e08248a47bbf27a584cfaa Author: Lucian Popescu Date: Fri Oct 9 11:00:49 2026 +0100 Fix lambdas that receive pack captures (#500) commit fcee2ce0b70eef2eade0900e850ef8adfb3c0f6c Author: Lucian Popescu Date: Fri Oct 9 10:59:56 2026 +0100 Translate generic lambdas (#499) commit dd4b7eb7df84c4a1e59b7ce3ea6c1ceb0f489c99 Author: Nuno Lopes Date: Thu Oct 8 20:50:25 2026 +0100 Simplify lambda translation by using macros to infer copy/move/destruct automatically commit e8961cf10534a76a3b6fd14d5eb71268b3f28d83 Author: Lucian Popescu Date: Thu Oct 8 19:40:33 2026 +0100 Allow move-only types to implement FnPtrArg (#476) commit 5741a8d62b82990d6ce658277eaebaea01f54d33 Author: Lucian Popescu Date: Thu Oct 8 15:53:55 2026 +0100 Translate moveable/copyable/destroyable lambdas (#458) commit fd1f8c804a2384290e92468a143699e09ff1f09e Author: Nuno Lopes Date: Thu Oct 8 15:51:20 2026 +0100 Fix bug in printf %c commit f4f82ae467383dafd7eb1f8ec51d026609823736 Author: Lucian Popescu Date: Thu Oct 8 13:59:09 2026 +0100 Translate C99 flexible array member (#473) commit 478bd1823ff5e886104917e643fe138a76087641 Author: Lucian Popescu Date: Thu Oct 8 13:50:31 2026 +0100 Translate comparison operators that don't return bool (#496) commit cf6b6a01824f9fbb38c94a15b6c4f434619bf79e Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu Oct 8 12:16:57 2026 +0100 Remove unnecessary `unsafe` from time refcount rules (#495) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 8dfc4d2cd7d645e98e193e585d00be3046b8c122 Author: Lucian Popescu Date: Thu Oct 8 12:07:44 2026 +0100 Add standard-version ifdefs in rules (#448) Part of #447 commit abb9792bb41c863de58df7f63ec01c3743812855 Author: Lucian Popescu Date: Thu Oct 8 12:03:32 2026 +0100 Hoist static locals to module scope (#491) commit c700ce65458ceb0c8c928426567c549a67ca6355 Author: Lucian Popescu Date: Thu Oct 8 12:02:04 2026 +0100 Fix array pointer dereference and temporary array materialisation (#487) commit 5d79b189c7815c3614e92d0999fbb152e9743eb8 Author: Lucian Popescu Date: Thu Oct 8 11:59:19 2026 +0100 Fix mismatched usize/u64 array element type (#494) Closes #320 commit 5a49110f7e24f3de5fbf8ace789546b1fe4fb9bb Author: Lucian Popescu Date: Thu Oct 8 10:38:26 2026 +0100 Print float literals at full precision (#483) commit 0d92094f6e16a2347d6b195a066dc32bb3eb3565 Author: Lucian Popescu Date: Thu Oct 8 09:39:27 2026 +0100 Emit clang-computed sizeof values in refcount (#490) commit 5eb6fd10f26d5e76373f9fd52ecdb3ef634901b3 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu Oct 8 09:12:51 2026 +0100 Fix `std::find` refcount rule when the first iterator has a non-zero offset (#493) The refcount `std::find` rule compared a relative `enumerate` index against the absolute `a1.get_offset()`. If `a0` didn't start at offset 0, the bound was too large, so the search could run past `last`. - Fixes #456 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 6d6f83cd35c5405e810f87d9f6f57b42645145c5 Author: Lucian Popescu Date: Thu Oct 8 08:31:38 2026 +0100 Skip explicit template instantiation declarations (#481) commit 9e57ef73a63a12907ceb82dc3a43e269900f0ef5 Author: Lucian Popescu Date: Thu Oct 8 08:31:23 2026 +0100 Distinguish declarations from one macro expansion in decl IDs (#485) commit cb82dad9d013188ae6b804fbe002b10a236f0bcb Author: Lucian Popescu Date: Thu Oct 8 08:31:08 2026 +0100 Translate references to functions as function pointers (#486) commit 585798adfccdc40f5bf6b3e6f0bfb614935469ab Author: Lucian Popescu Date: Thu Oct 8 08:22:17 2026 +0100 Translate only the taken branch of constant if conditions (#488) ```cpp template unsigned long long widen(T v) { if (sizeof(T) > 4) { T hi = v; hi <<= 32; return hi; } return v; } ``` The code inside if is not compilable. `hi <<= 32` is a compilation error in rust when T = int. commit f68ce854f8ed9dda46a0a10426fd73f194be6411 Author: Lucian Popescu Date: Thu Oct 8 08:18:40 2026 +0100 Translate out-of-line copy constructors (#489) commit 2e92ab3ca0851db6665cffbade334cf21ce1e71a Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu Oct 8 08:17:10 2026 +0100 Preserve first-element semantics in max_element translations (#479) `std::max_element` returns the first iterator when multiple elements share the maximum, but the Rust translations selected the last. This affected vector rules f42/f94 and algorithm rule f8. - Fixes #454 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 9190c3c3ac9d3790bd29142b85776da1a9df8baa Author: Lucian Popescu Date: Thu Oct 8 08:16:06 2026 +0100 Treat u8 literals by their element type (#492) Pre C++-20 u8 literals were `const char*`, post C++-20 they are `const char8_t*` commit cca70e234b8521adcd8318155985ebe8bdd9a69d Author: Lucian Popescu Date: Thu Oct 8 08:13:28 2026 +0100 Translate transparent init lists (#484) commit 6661085aa791ee81f84a0ac61d2a1cf863918fde Author: Lucian Popescu Date: Thu Oct 8 08:05:41 2026 +0100 Return 0 when main falls off its end (#482) commit 4e953e4c75abd7d07e11d23aa11ed0e92c997199 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Oct 7 20:53:57 2026 +0100 Initialize empty `std::string` values with a null terminator (#478) The `std::string` rules model strings as null-terminated vectors, but default initialization produced an empty vector. Operations such as `length()` then incorrectly assume a terminator exists. - **Type rules:** Initialize default strings with `vec![0]` in both unsafe and refcount models. - **Regression coverage:** Add a default-constructed string case that checks `length()`. ```cpp std::string empty; assert(empty.length() == 0); ``` - Fixes #455 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 488bee6f7003caaebabe7991c79dcf521f68a079 Author: Lucian Popescu Date: Wed Oct 7 19:38:24 2026 +0100 Don't use uninstantiated copy constructor (#477) commit 5911673b6df4c1453e81b1515c40c699df93cffe Author: Lucian Popescu Date: Wed Oct 7 14:59:13 2026 +0100 Translate SourceLocExpr (#468) commit 9263f5d4b685fc1350123db700b985c6f876cbac Author: Lucian Popescu Date: Wed Oct 7 14:58:23 2026 +0100 Move reinterpret cast OOB tests in tests/ub (#475) commit 8c3cee8e90107ec713f1c4dc6dcb5e169a147552 Author: Lucian Popescu Date: Wed Oct 7 12:30:11 2026 +0100 Access unions through `Ptr` (#474) Fixes #311 commit d546c8cd3e8f0c815ca3f3032c52ff2b78d6645f Author: Lucian Popescu Date: Wed Oct 7 12:27:14 2026 +0100 Visit linkage spec decls and skip class template specializations (#467) commit 4efff884ff53c32c606f3c0bf3d564adfe44cd08 Author: Lucian Popescu Date: Wed Oct 7 12:26:15 2026 +0100 Add i128/u128 inc/dec and i64 Ptr add/sub (#466) commit 485d6bbf90187d202876d34e234ea90bbc41cdea Author: Lucian Popescu Date: Wed Oct 7 12:16:38 2026 +0100 Evaluate static_assert conditions in constant context (#469) commit 6f6d359e163f95473000f925c0fcc5c63ca09493 Author: Lucian Popescu Date: Wed Oct 7 11:09:23 2026 +0100 Emit bool values for enums with bool underlying type (#470) commit eb92b55fd0581f03f11ae226c4eef7292de6fac2 Author: Lucian Popescu Date: Wed Oct 7 10:54:40 2026 +0100 Update derives for unions and opaque records (#471) commit 8e6b297b2acc7e35415c8dc4f5a6e6d3960ba50f Author: Lucian Popescu Date: Wed Oct 7 10:51:56 2026 +0100 Fix lvalue and address of conditional operator (#472) commit 9f80cb2f9928940a190563563738403e1727cce8 Author: Lucian Popescu Date: Wed Oct 7 10:48:42 2026 +0100 Translate noexcept expressions (#462) Rust has no equivalent for noexcept, let clang do the computation. commit a16b44fed42761a06ab2737431f12932e3b8e085 Author: Lucian Popescu Date: Wed Oct 7 10:47:16 2026 +0100 Use fresh rvalues for comma operands and materialized temporaries (#461) Fixes miscompilations, rvalue comma operands should be cloned, not moved. commit 405fdc480adb337295e739c0b97b6728c933b8e1 Author: Lucian Popescu Date: Wed Oct 7 10:44:48 2026 +0100 Name user-defined literal operators (#463) commit 55e814ab60200a3a3668661210517159e20eb540 Author: Lucian Popescu Date: Wed Oct 7 10:44:00 2026 +0100 Skip methods with undeduced return type (#465) commit e3fd97157eb0012b7c7de209d475e372f647d2da Author: Lucian Popescu Date: Tue Oct 6 22:07:09 2026 +0100 Evaluate default arguments lazily and support reference defaults (#459) The old `arg.unwrap(default_value)` pattern unconditionally evaluated default_value. This is wrong if default_value has side effects and the default value is never used. Also fix default arguments bound to reference arguments. commit d8aa9c42b23f6e743cebad0c583eeaace2cad44b Author: Lucian Popescu Date: Tue Oct 6 21:15:12 2026 +0100 Translate delete nullptr as no-op (#457) delete nullptr is no-op in C++. It's wrong to translate it to `drop(Box::from_raw(null()))` because it triggers unsafe precondition violation. Skip the drop call when the deleted pointer is nullptr. commit 004a9050788593c01ef9158bf2978e9e100788d3 Author: Nuno Lopes Date: Tue Oct 6 11:09:59 2026 +0100 Unbox local vectors and issue straight [] accesses instead of going through Ptr commit d6ced87e1e5a2dae4501b43ab0e85793ee4e1081 Author: Nuno Lopes Date: Tue Oct 6 09:06:59 2026 +0100 Unbox holders of std::unique_ptr commit 8067fe934468f0aada39460d7c87e029149bc285 Author: Nuno Lopes Date: Mon Oct 5 22:28:08 2026 +0100 Move instead of clone for returned vars commit cfdccf1d04b04d564b2c4536d7d9290f96c3342e Author: Nuno Lopes Date: Mon Oct 5 21:58:38 2026 +0100 Address taken analysis: dig into initializer lists commit 373a36f4747f9276a4e0653efa799613a84fb59b Author: Nuno Lopes Date: Mon Oct 5 18:00:04 2026 +0100 Unbox struct locals and optimize compound ptr assignments to not copy the pointer Around 40% speedup on Brunsli commit f0f836e29ac3552eefa7c484fcd6f128f21caf8d Author: Nuno Lopes Date: Mon Oct 5 15:13:15 2026 +0100 Replace `ptr.offset(x).op` with `offset!(ptr, x).op` in the rules The latter is more efficient commit d7fec13190e6c322faa3bcc006960d456b18c2e8 Author: Nuno Lopes Date: Mon Oct 5 15:01:59 2026 +0100 Fail format CI if clippy issues a warning commit ccf088359ccfb06daecd65cf11ba49ea29b63b52 Author: Nuno Lopes Date: Mon Oct 5 14:59:14 2026 +0100 fix clippy warnings commit 3ba181cc374684c8d1cd3f1566843e63b4fdf4a1 Author: Nuno Lopes Date: Mon Oct 5 14:56:05 2026 +0100 Remove unneeded Value boxing from constructors commit 0753f651c0752d56b964b2a3117b08d89472d229 Author: Nuno Lopes Date: Mon Oct 5 14:33:37 2026 +0100 Add lightweight analysis that gathers non-address taken variables Remove Value boxing in those cases commit b41990ed6f5baca2570285e769faa444e5777f06 Author: Nuno Lopes Date: Mon Oct 5 13:39:33 2026 +0100 Switch char to i8 (#452) Fixes #246 commit 9e032ae9ca050c099ecd6b2ee154d24874102086 Author: Nuno Lopes Date: Sun Oct 4 19:48:49 2026 +0100 Fix testsuite compilation commit 67fbc74348849bf4d42df74016754b6549e393cd Author: Nuno Lopes Date: Sun Oct 4 18:28:25 2026 +0100 fix tests commit 89e83a88f7f2a08687790f5e48db4259a3b7f237 Author: Lucian Popescu Date: Sun Oct 4 18:26:41 2026 +0100 Translate lambdas using FnPtr (#439) Fixes #314. commit 8be9b75ef58d40f460b1d50080f7cdbb19f86de8 Author: Lucian Popescu Date: Sun Oct 4 18:09:09 2026 +0100 Dedup conversion operator names (#445) char8_t and char are both translated to u8. This breaks conversion operators: to_u8 (char8_t) vs to_u8 (char). Dedup the names to avoid name collisions. commit 91f76f55aa14af1c26b656acff0c71013d4e8966 Author: Lucian Popescu Date: Sun Oct 4 11:08:03 2026 +0100 Move C specific rules in src.c (#449) Part of #447. commit 42533cb9860779d8bbd2693a2214eec6f2a3e807 Author: Lucian Popescu Date: Fri Oct 2 11:59:37 2026 +0100 Respect signedness for integer literals (#446) commit 5e045731bd7ae9e8588e36899295e75c80a516ef Author: Lucian Popescu Date: Fri Oct 2 10:47:50 2026 +0100 Fix mismatched u64/usize pointers and references (#444) commit 4f7fe3b64e40d1d11f69efaf03920deacd215aa4 Author: Nuno Lopes Date: Fri Oct 2 10:11:46 2026 +0100 fix test commit 93ca13844e100e935bd1a73ea29b1348db3debd1 Author: Nuno Lopes Date: Fri Oct 2 09:55:31 2026 +0100 Use the `Default` default implementation more often commit f6154ccc7163a4c069d31742d4db7b420ee71f5e Author: Nuno Lopes Date: Fri Oct 2 08:56:03 2026 +0100 Add a DeepClone macro to remove manual Clone implementations FIxes #216 commit f3700559de071679c614dcad16404f5b3461eab1 Author: Nuno Lopes Date: Fri Oct 2 08:40:00 2026 +0100 Add `offset!(ptr, idx)` macro to create temporary pointers without ref counting overhead commit 251c0677fbc781e4ecd2e4aa69ad10ee2dc5fd7c Author: Lucian Popescu Date: Fri Oct 2 08:20:44 2026 +0100 Translate static member templates (#440) commit 6d59ab88f299a453d77f9f6e6630d9e8688d4659 Author: Lucian Popescu Date: Fri Oct 2 08:15:40 2026 +0100 Fix u64/usize mismatch in array init and ctor call (#442) commit 0383cd5b894c1545fdea048d01327d69c378ce5b Author: Nuno Lopes Date: Thu Oct 1 22:07:57 2026 +0100 Update to Rust 1.99 (#443) commit 63dd17e631ed9144c7755d01778ee3630acba83f Author: Nuno Lopes Date: Wed Sep 30 19:34:10 2026 +0100 Reduce the number of temporary variabkes created by using braces Rust 2024 treates the last expr as temporary, allowing us to write *x.borrow_mut() = { *x.borrow() +1 } without causing borrowing errors We must careful only with side effects and evaluation orders commit ce687c40e84bb232b13583066962e1b6cc55ab47 Author: Nuno Lopes Date: Wed Sep 30 18:59:47 2026 +0100 Add a macro to derive ByteRepr automatically Shrinks the translated code Part of #216 commit 619be59e0df1de36bcd316841f93403aacb7632f Author: Nuno Lopes Date: Wed Sep 30 18:06:48 2026 +0100 Remove Value<> boxing from struct fields except arrays/vectors (#437) commit aa9e5746c20cca6f0eabd481dd9adb38a4ecd908 Author: Lucian Popescu Date: Wed Sep 30 14:46:08 2026 +0100 Add extra tests for struct fields, PR #437 (#438) commit 6a1926705ab5146cea7050f5203b3bce33e7b5eb Author: Lucian Popescu Date: Wed Sep 30 09:13:52 2026 +0100 [NFC] Split responsibilities of Mapper (#434) Mapper grew too big. It has 4 responsibilities at the same time: 1. Map expressions or types to a rust string 2. Match expressions or types against translation rules 3. Answer queries about the translation rules (ParamIsPointer, IsLibcPassthrough, etc) 4. Print string representation of expressions or types Only the first should be in Mapper, the rest should be split. For (2) I plan to add a new tree matcher instead of the current string matcher. To make the transition smooth I extracted the matching logic form Mapper into `cpp2rust/converter/rules/matcher.h`. For (3) I added `cpp2rust/converter/rules/registry.h` which keeps the `exprs_` and `rules_` multimaps + handles queries about the translation rules. For (4) I added `cpp2rust/converter/printer.h` which should BTW disappear after we switch to tree matching instead of string matching. This is a building block for fixing #435 commit edd15e8cbf509beeb3c58ce23a26532137f3fe23 Author: Nuno Lopes Date: Wed Sep 30 09:02:15 2026 +0100 fix test output commit eaac2eb6fdeb726709461a85346e8a2179c40231 Author: Nuno Lopes Date: Wed Sep 30 08:55:59 2026 +0100 fix build with clang 24 commit 44deda0d72b763a030abe9fe2bfc109c9d6b6591 Author: Lucian Popescu Date: Tue Sep 29 22:30:20 2026 +0100 Avoid extremely long names in overloaded methods (#431) ```diff - pub unsafe fn apply_Optionunsafefnpmuti32_i32__i32_const( - &self, - mut f: Option, - mut x: i32, - ) -> i32 { + pub unsafe fn apply_11(&self, mut f: Option, mut x: i32) -> i32 { ``` This generates more readable names and avoid special-casing characters in the mangled name. Also it fixes a problem in the codegen where a template is visited before the type it's instantiated with. commit ee38fdab975a2b224c712d08c49476bc0728377e Author: Lucian Popescu Date: Tue Sep 29 15:21:54 2026 +0100 Derive VaArg and FnPtrArg for cloneable records (#432) commit 90ddea537e3265ad7c7a252b799bf95271b34471 Author: Lucian Popescu Date: Tue Sep 29 15:14:54 2026 +0100 Avoid collision between user-defined record and libcc2rs (#433) The other option is to use fully qualified names in the output, for example libcc2rs::Ptr, but that's too verbose. commit c8329a61aea05cc9d9ce96638f77f1fa856731ff Author: Lucian Popescu Date: Mon Sep 28 21:44:26 2026 +0100 Handle multi-dimensional arrays in codegen (#428) commit 3a9064ff4f7c63b826945d68aac1639a158298ea Author: Lucian Popescu Date: Mon Sep 28 21:34:59 2026 +0100 Translate access of static member through object (#430) ```cpp struct S { static int static_member = 0; } assert(S{}.static_member == 0); assert(S::static_member == 0); ``` Both are valid C++. Same for static methods. commit 9ba0d6974c5bff4952f69a7bd768ca3a02eb50ee Author: Lucian Popescu Date: Mon Sep 28 21:33:40 2026 +0100 Move builtin types in rules/ (#429) Removes the awkward handling of builtin types from addBuiltinTypes. Remove unneeded casts between builtin pointer types. Also useful for a future PR where I plan to replace string rule matching with structured tree matching. commit e3c65269c67a403f1a8f839f355027c41c28305e Author: Lucian Popescu Date: Mon Sep 28 21:30:58 2026 +0100 Drop helper structs in algorithm rules (#427) commit 047cb7c66d99149cb20e642b6214e52f6ddef176 Author: Nuno Lopes Date: Mon Sep 28 18:45:22 2026 +0100 Don't clone whole global arrays on a read commit 1f5e1f5d6cbaca09f82ef0982cbc21dc21d7bafc Author: Nuno Lopes Date: Mon Sep 28 08:51:09 2026 +0100 libcc2rs: various optimizations that yield about 10% speedup on Brunsli Inline a few hot functions Rearrange PtrKind to simplify the generated code commit 825b71c16665cdb4a199e102d2baaf4a328814a5 Author: João Gouveia Date: Mon Sep 28 08:34:44 2026 +0100 Use fully-qualified syntax for decay calls (#426) Addresses the second issue mentioned in #425 commit 83af9de94d1eb37bcd77775ffb66dcac6438cb49 Author: Nuno Lopes Date: Sun Sep 27 10:43:05 2026 +0100 Fix build of testsuite commit 2b2612f305810ec01de1cd4f2ba5bc52cef0193b Author: Nuno Lopes Date: Sun Sep 27 10:20:25 2026 +0100 Fix compilation after `ninja format` has been run commit 4a4238166a79a018ac6a778efe23ef0fd6d1b812 Author: Nuno Lopes Date: Sun Sep 27 09:59:32 2026 +0100 Remove a few spurious clones before Ptr::to_any commit 58a2a29b43ac13b747f031f392e12cdbe932a9f4 Author: João Gouveia Date: Sun Sep 27 09:45:47 2026 +0100 Fix void pointer comparisons (#424) `AnyPtr` comparisons rely on the equals method of the `ErasedPtr` trait. For `Ptr`, this method is as follows: ```rust fn equals(&self, other: &dyn ErasedPtr) -> bool { other.as_any().downcast_ref::>() == Some(self) } ``` This implementation causes `AnyPtr` equality checks to succeed only when the underlying `ErasedPtr`s have the same pointee type, since `.as_any().downcast_ref::>()` returns `None` otherwise. I do not believe this behavior is correct. Two pointers cast to `void *` compare equal if they point to the same address, regardless of their original types. This patch adds two unit tests covering this bug and addresses it by comparing the two `ErasedPtrs` through their byte representations. commit ba061bfe0afb8aeb214bd7387cd9ad889979340f Author: Nuno Lopes Date: Fri Sep 25 18:00:59 2026 +0100 [unsage] Fix missing transmute in function pointer cast Fixes test fn_ptr_typedef_cast.c commit 3086a02129a93771649fa720d12fae855a57435a Author: Nuno Lopes Date: Fri Sep 25 17:56:16 2026 +0100 Fix string.h translation with glibc >= 2.43 commit da79fe9efc8c77b094b76b54aa8d7abd07740ba4 Author: Nuno Lopes Date: Fri Sep 25 17:27:11 2026 +0100 Build all of Rust in the build directory and avoid double build on format commit bce8f54006312d4642382983fc0581e5738657ca Author: Lucian Popescu Date: Fri Sep 25 16:41:52 2026 +0100 Move shims from libcc2rs into rules (#421) We used `libcc2rs/src/libc_shims` to define various shims for interacting with libc from refcount: CFile, CFdSet, networking structs, etc. This will soon get very big with the addition of new rules. I moved each shim in the corresponding rule dir, for example `rules/stdio/shim.rs`, `rules/select/shim.rs`, etc. This way, shims stay small and separated. libcc2rs gathers all shim.rs files form rules/ at build time and compiles them. commit 2a8276e92a0392c1dd6583fd10c8fb3d5cb0efd7 Author: Lucian Popescu Date: Fri Sep 25 16:21:59 2026 +0100 Translate concepts and requires expressions (#347) They are evaluated by clang. Only the result of the evaluation is translated. Furthermore, static_assert is no-op because it's evaluated at compile time. Same for concept declaration. commit 483abe62b6ff4eaa9fc506d9fb4f8f19de146c00 Author: Lucian Popescu Date: Thu Sep 24 21:34:12 2026 +0100 Add lambda testsuite (#423) commit 1b519b63b8784121c0ff8697ea73e97b74029e67 Author: Lucian Popescu Date: Wed Sep 23 22:34:34 2026 +0100 Add support for rules that receive argument packs (#416) Add support for rules like `std::vector::emplace_back(Args&&....)` and `std::unique_ptr::make_unique(Args&&...)`. This also deletes the emplace back plugin. This sets the stage for adding other argument pack rules like `std::construct_at` or `std::map::try_emplace`. Rust does not support forwarding argument packs. So emplace_back is translated as `v.push(T::new(arg1, arg2, ...))`, i.e. it builds the object at the call site. For things that might not build the object using the provided arguments, like try_emplace, we will use the following translation that conditionally builds the object: `m.entry(key).or_insert_with(|| T::new(arg1, arg2, ...))`. I used a new node in Rules IR, called init, which rule-preprocessor creates when encountering a pack that builds an object. cpp2rust reads the init node and creates a construct expression which is then translated in order to create `T::new(arg1, arg2, ...)`. commit 6fbeb0cf6aa3ed1c26c691b45437d36f39d67741 Author: Lucian Popescu Date: Wed Sep 23 21:55:12 2026 +0100 Translate in-class initializers in Default trait (#332) commit 578694b8b2b09a30a71480d8b533ca2bd21fc150 Author: Lucian Popescu Date: Wed Sep 23 21:39:10 2026 +0100 Use copy_from/move_from to translate copy/move ctor (#418) If possible, use the `copy_from`/`move_from` idiomatic name instead of `S_pmutS_rv`. This is only possible when the copy/move constructor is unique (no other copy/move constructor exists) and when no other method in the class is called copy_from/move_from. Do the same for copy/move assignment: copy_assign and move_assign. commit aa7c10462a7d9623830933934499d0faa509bb88 Author: Lucian Popescu Date: Wed Sep 23 21:13:37 2026 +0100 Translate if constexpr (#345) commit 98cc1b796f9db0913c4b90fdfc1bf5f76710065e Author: Lucian Popescu Date: Wed Sep 23 21:04:04 2026 +0100 Use idiomatic S::new for ctor instead of S::S (#420) commit aa071d8f43fe7e7f42a1bff9662d1bb39262a925 Author: Lucian Popescu Date: Wed Sep 23 21:03:09 2026 +0100 Generate to_bool instead of operator__Bool (#419) commit 43df4b9738d30aaa9b79c9b395cfff54bf724660 Author: Lucian Popescu Date: Wed Sep 23 21:02:16 2026 +0100 Translate std::launder (#417) Initially I wanted to special case this in the converter like std::move and std::forward. However since this is just a passthrough rule that receives a pointer and returns a pointer + does not change the value category of the argument, I put this in rules. commit ed10067a1b04e549f299cd8483cb427c9e23c443 Author: Nuno Lopes Date: Tue Sep 22 17:22:34 2026 +0100 Refactor how function pointer casts work by creating a trampoline automatically Simplifies the generated code at the expense of more runtime library code commit 8173b259fc1fcf4c690374629aaf7179f8a5e870 Author: Lucian Popescu Date: Tue Sep 22 14:18:19 2026 +0100 Add rules for __builtin_abort/nanf/inff/clzl (#415) commit 511e5b752e2bb09d6dc2993e8fd2cee2d8580799 Author: Lucian Popescu Date: Tue Sep 22 10:27:27 2026 +0100 Handle transparent std calls (#414) Similar to move and forward, handle move_if_noexcept and as_const in codegen. They have no effect at runtime. commit d9b33e5eb95565ad3785594266126926778e0065 Author: Lucian Popescu Date: Tue Sep 22 08:31:23 2026 +0100 Translate std::forward (#413) Similar to `std::move`, `std::forward` performs a cast and helps the AST choose the right overload. It has no effect at runtime. commit aabc367196e0c46d80737bb2dc04c141675a2efa Author: Lucian Popescu Date: Mon Sep 21 21:10:47 2026 +0100 Translate friend operators (#412) commit b398a1b7d01cdb3242292cd9e497417a94d1319b Author: Lucian Popescu Date: Mon Sep 21 13:48:04 2026 +0100 Fix late instantiated virtual methods (#411) #394 translates records that contain declared but not defined methods. That did not cover virtual methods in derived records. This PR solves that. cpp2rust generated an empty `impl Base for Derived {}` when no virtual method had a definition. Then, when an out-of-line definition was encountered, cpp2rust refused it because multiple `impl Base for Derived {}` are not allowed by rustc. This resulted in usages of undefiend methods because neither the record visitor nor the out-of-line method visitor generated the methods. The fix is to defer the `impl Base for Derived {}` block at the end of the file, similar to how methods on Ptr are deferred. At the end of the file, where every TU was visited, we know all the methods that have definitions. commit 174ebb6f2a32c6ebccfa1924d19f8ce664b13dd9 Author: Nuno Lopes Date: Mon Sep 21 11:13:43 2026 +0100 optimize reinterpret casts to avoid heap allocation on dereferences Also add fast paths for common operations (aligned access, deref u8) commit 9934ac78bb2dc4ad2b6f6a57873d1b35ab583e50 Author: Nuno Lopes Date: Mon Sep 21 10:31:49 2026 +0100 optimize function pointers to avoid heap allocation commit 6a43cf98cc90fa7f95938ce7ef8595c896018bb9 Author: Nuno Lopes Date: Mon Sep 21 09:42:56 2026 +0100 optimize vaarg handling commit 8f54ac90cb04e550c22b9a86c16a4b84b82e196e Author: Nuno Lopes Date: Mon Sep 21 09:28:25 2026 +0100 fix test commit a40ebe7df024940b95f842f7b941a87b76c28dff Author: Nuno Lopes Date: Mon Sep 21 09:26:37 2026 +0100 optimizations to libcc2rs related with strings Reduces heap allocations in the common case commit 7a801325da28170bcf72dc69e10e4dabf36c9f84 Author: Nuno Lopes Date: Sun Sep 20 22:06:45 2026 +0100 Replace Ptr::delete_array with Ptr::delete There's no need to catch all those UB cases; we can refine into working code. commit b5aa8518d98a4f68607f191c0cc71c91be3acf20 Author: Nuno Lopes Date: Sun Sep 20 21:43:53 2026 +0100 Remove Ptr::to_strong() and add Ptr::decay() (#410) To decay a pointer, we would previously translate first to a strong reference and then call `as_pointer`. This patch avoids that by 1) decaying pointers whenever possible and by using `decay` as a more efficient way to decay pointers (does only a weak clone). commit 50d844d21bbcef8d98c09a0b866f670f4d83134f Author: Lucian Popescu Date: Sun Sep 20 18:59:25 2026 +0100 Translate operators that receive by value arguments (#408) commit 4edecf6d25d9c32c3a083503fc162e7a5422d793 Author: Lucian Popescu Date: Sun Sep 20 18:42:16 2026 +0100 Fix address of static method (#409) commit 325c11ff0744d7cc6027415e095967e6c5b11d8a Author: Nuno Lopes Date: Sun Sep 20 16:55:18 2026 +0100 fix compilation of assert with C++11 commit 352ac8a41af4c090bc542b3279ad4a459a33970c Author: Lucian Popescu Date: Sun Sep 20 15:50:54 2026 +0100 Don't add default impl for declared but not defined default ctor (#405) commit 4d5177b768b934f679d908145b51f908a9535794 Author: Lucian Popescu Date: Sun Sep 20 15:50:17 2026 +0100 Don't translate partial specializations (#406) Only the full specialization is translated. commit 2a8929229fdb8ac4dc06768cba72345a771a1fe3 Author: Lucian Popescu Date: Sun Sep 20 15:49:53 2026 +0100 Convert operand as lvalue in array-to-pointer decay (#404) commit 74c683000708a4c6480b93cb49a6830331c8d093 Author: Lucian Popescu Date: Sun Sep 20 15:43:51 2026 +0100 Define constexpr cpp2rust_assert_fail (#401) Calling assert in constexpr context requires cpp2rust_assert_fail to be constexpr defined. commit 444164c4982971f47c4bee31b94046e9586c31e8 Author: Lucian Popescu Date: Sun Sep 20 15:42:01 2026 +0100 Translate inner templates (#400) commit 6c870265d3794e8794ed0e76455688a4015cc572 Author: Lucian Popescu Date: Sun Sep 20 15:41:16 2026 +0100 Fix address of conditional operand (#403) commit 582e0f6d2ce5124757be2e58e110b0f42aab0dfa Author: Lucian Popescu Date: Sun Sep 20 15:40:03 2026 +0100 Translate empty unions in unsafe (#402) Unions cannot be empty in unsafe, add a dummy u8 member. commit d9a10be01b671557ef3cc1a38a774b0a184fb834 Author: Lucian Popescu Date: Sun Sep 20 15:39:04 2026 +0100 Translate unary plus (#399) commit 3b4667707b49c5c9fe19f4071a061498ae447c68 Author: Lucian Popescu Date: Sun Sep 20 13:59:24 2026 +0100 Don't use uninstantiated default argument (#398) commit e348a4fbe5b7c6ef0ca984c9b3bd43fa41fc496a Author: Lucian Popescu Date: Sun Sep 20 13:57:55 2026 +0100 Fix temporary receivers (#397) commit 28b0d414ac2b524e7a4756a6e96da2884a3f77b3 Author: Lucian Popescu Date: Sun Sep 20 13:56:51 2026 +0100 Translate nameless copy constructor agrument as _a0 instead of self (#396) `self` is used only on defaulted copy constructor, i.e. inside the Clone implementation. commit e345b15913b921aa8718a6ef8a08e2e823c79155 Author: Lucian Popescu Date: Sun Sep 20 13:00:36 2026 +0100 Translate functional casts (#395) commit 0fe86a0b58bcbfd2fa323db361d19e8fe54d9549 Author: Lucian Popescu Date: Sun Sep 20 12:58:00 2026 +0100 Translate records that contain declared but not defined methods (#394) ```cpp struct S { int a; void declared_but_never_defined(); ]; int main() { S s{1}; assert(s.a == 1); } ``` S is a valid record that can be translated to Rust. This PR translates it like this: ```rs struct S { a: i32 } trait SImpl { fn declared_but_never_defined(&self) { unimplemented!() } } ``` The function gets a default `unimplemented` implementation. Other TU might offer a definition for the declared-only method. commit 053a8306ef69e4d1f1018c7f3ea14b85056f91b8 Author: Lucian Popescu Date: Sun Sep 20 12:55:02 2026 +0100 Convert delegating constructors (#386) commit 06dc24e92b154a307fb692920ce314e70a90c24c Author: Lucian Popescu Date: Sun Sep 20 11:34:56 2026 +0100 Translate classes with mutable fields (#387) Classes with mutable fields can modify the mutable fields even in const methods. For this reason, the receiver of const methods in classes that contain mutable fields should be `&mut` instead of `&`. commit 3943e3ec711c30507ea088a2a52c7da3192fad78 Author: Lucian Popescu Date: Sun Sep 20 11:03:35 2026 +0100 Translate wide strings (#381) commit cadf6a850bc3a96c79e592930889045179e93eff Author: Lucian Popescu Date: Sun Sep 20 10:56:16 2026 +0100 Fix translation of out-of-line template static methods (#393) commit de1698661c793e9ecad732a2dc49a4e8598e3bd3 Author: Lucian Popescu Date: Sun Sep 20 10:50:39 2026 +0100 Unbox initializer list in refcount (#392) commit 4dcdf6181cbbfc49123ef0980735160291928df3 Author: Lucian Popescu Date: Sun Sep 20 10:50:29 2026 +0100 Default initialize reamaining fields in small initializer list (#391) commit d8a53d495392fdab9b0027350fe3bbebd84f4265 Author: Lucian Popescu Date: Sun Sep 20 10:29:54 2026 +0100 Default initialize object allocated with new (#390) commit 62e28c9ce8ef3a09d5a39a08a40c3ddc7d35fb70 Author: Nuno Lopes Date: Sun Sep 20 10:21:21 2026 +0100 fix #377: modify global variables without cloning the Rc commit cc7dcb0fe15603053faef87c751709a765ca82d3 Author: Lucian Popescu Date: Sun Sep 20 09:24:06 2026 +0100 Translate variable template declarations (#389) commit deac5b5bfa5311954a36158d7976517e254acd83 Author: Lucian Popescu Date: Sun Sep 20 08:58:39 2026 +0100 Add rules for operator new and operator delete (#380) commit e7fb91fbafd54a12bb764ce1847861f4838b0d2d Author: Lucian Popescu Date: Sun Sep 20 08:57:12 2026 +0100 Ignore explicit casts to reference types (#382) commit f65bef426f25fc50abf5ff99a4ce6db545f3284a Author: Lucian Popescu Date: Sun Sep 20 08:56:09 2026 +0100 Handle more patterns in mangler (#384) commit f9fc65d8de0870e14b44b83bfbb34dcf98c3bb24 Author: Lucian Popescu Date: Sun Sep 20 08:54:32 2026 +0100 Derive Clone when all fields are references (#388) Also when there are no fields. commit 21842412bfa0cb40dea1ad50437427c99075bb3e Author: Lucian Popescu Date: Sun Sep 20 08:53:06 2026 +0100 Don't call destructor for trivially destructible types (#383) commit deba8923b7fbb7885bd859234811c1aa18ddad4e Author: Lucian Popescu Date: Sun Sep 20 08:51:20 2026 +0100 Implement Ord for Ptr (#385) commit 754f8181a72a417954a0a53f62695533a9f15aaa Author: Nuno Lopes Date: Fri Sep 18 09:29:51 2026 +0100 fix #193: remove Brotli headers from repo commit 78fe7c436e26161147214a6b9f1589c11e38d24d Author: Nuno Lopes Date: Fri Sep 18 09:00:40 2026 +0100 cmake fixes commit d8cc325ba5874fdea9c4905b09d88fe3f1778434 Author: João Gouveia Date: Fri Sep 18 08:18:14 2026 +0100 Make rule-preprocessor process a single directory at a time (#372) Changes `rule-preprocessor` to process a single directory per invocation, making it possible to parallelize. This matches what is already the case for `cpp-rule-preprocessor`. Additionally, this drops `modules.rs` from source control. commit 00d90a0dc79cf2cbce4e6a99c27b438e301738a6 Author: Nuno Lopes Date: Thu Sep 17 15:31:51 2026 +0100 Read global variables without clonning the Rc Part of #377. Needs to fix the writing as well. commit e54bd9ba2640d3874095e9868530adb7ed1b425c Author: Nuno Lopes Date: Thu Sep 17 14:56:58 2026 +0100 Fix #114: Rust 2024 compatibility commit 79e0bd2ac802e6c499c69b4f0f2a97e144589780 Author: Lucian Popescu Date: Thu Sep 17 14:42:03 2026 +0100 [unsafe] Fix non-const global initializers and [unsafe, refcount] order of global initialization (#366) Rust requires initializers of global variables to be const. C++ does not require that. To fix this tension, wrap global variables in `std::cell::LazyCell` so that initialization does not depend on the constness of the initializer. Another problem that this PR fixes is the moment when global variables are initialized in both refcount and unsafe. `thread_local` in refcount and the new `LazyCell` in unsafe initialize the variable on the first usage. This is wrong if the constructors of the global variables have side effects that must be visible in the program, for example: ```cpp #include static int total = 0; struct S { S(int x) { total += x; } }; // there is no explicit usage of a and b inside the program other than the initialization // however the constructor has side effects that are visible on the total variable // so the initialization has to happen before entering main, not lazily as thread_local // and LazyCell normally do static S a(1); static S b(10); int main() { assert(total == 11); return 0; } ``` To fix this, I added a new function that runs before main and forces the initialization of global variables: `__cpp2rust_init_globals()`. --------- Co-authored-by: Nuno Lopes commit 9e771ccf8ea1d2eccd4f3f95e25d5508d06038a9 Author: Nuno Lopes Date: Thu Sep 17 13:34:31 2026 +0100 format commit 69ebb2c47922d9eecdb568d31cc4f2cb85d853a9 Author: Nuno Lopes Date: Thu Sep 17 12:31:53 2026 +0100 Don't generate empty thread_locals Co-authored-by: Francisco Gouveia commit c99fe02d40c1cecf0657d5b688e98b001cc94bc7 Author: Lucian Popescu Date: Thu Sep 17 09:56:44 2026 +0100 Translate user defined moves (#369) This PR translates move constructors as ordinary constructors: `fn S_pmutS(_a0: *mut S) -> Self`. The rules for translating and using the move constructors are: |No | Move Constructor (MC) | Copy Constructor | Result | | --- | --- | --- | --- | | 1 | user-written | implicit/defaulted/deleted | translate MC and always use it | | 2 | user-written | user-written | translate MC and always use it | | 3 | implicit/defaulted | implicit/defaulted | don't translate MC and use `.clone()` on move | | 4 | implicit/defautled | user-written/deleted | translate MC and always use it | For 1 and 2, we always use the translated move constructor because it might contain custom logic that needs to run on every move. For 3 we use `.clone()` because it has the same effect as a move would have. For 4 `.clone()` is not available (deleted) or might contain custom logic (user-written) so we synthesize an implicit/defaulted move constructor and use it. `std::move` becomes a transparent construct, i.e. no move decision is taken at the time of the cast. It's only used by clang to choose the right constructor. Moves happen on CXXConstructExpr. Each STL type has move construction rules. Rules that move using `std::mem::take` have their bodies rewritten as follows: * record has move constructor => rewrite `std::mem::take` as a call to the move constructor * record does not have move constructor and is copyable => convert as rvalue (without clone) * record does not have move constructor and is not copyable => convert as fresh rvalue (with clone) * record is STL type => use the rule for STL move constructor * temporary value => convert as rvalue commit 9138d71748d2826cfe700b361e5d5e75375d3a87 Author: Lucian Popescu Date: Wed Sep 16 21:11:15 2026 +0100 Remove duplicated lamda/function pointer rules (#373) `stable_sort` rules had 2 version of rules: one that takes a lambda and one that takes a function pointer. This PR removes the duplication by adding a new trait in libcc2rs: Callable. Callable is implemented by `Fn` (closures, safe function items, safe function pointers) and `unsafe fn` pointers. Unsafe functoin items reach the `Callable` trait to an explicit cast to `unsafe fn` pointers because they don't implement `Fn`. commit 20563817f8ff43e353d525d14b52b5daa3d8e11f Author: Lucian Popescu Date: Wed Sep 16 21:10:30 2026 +0100 Move function-local records in global scope (#375) ```cpp template int get(T t) { return t.x; } int main() { struct Local { int x; }; Local l{7}; assert(get(l) == 7); return 0; } ``` The `get` function template lives in the global scope while `Local` (used to instantiate `get`) lives at function scope. In the AST instantiations live in the global scope as well, so it gets translated as: ```rs // Wrong: the global scope has no Local_1. It only exists inside main pub unsafe fn get_0(mut t: Local_1) -> i32 { return (t.x as i32); } unsafe fn main_0() -> i32 { pub struct Local_1 { pub x: i32, }; let mut l: Local_1 = Local_1 { x: 7 }; assert!(((unsafe { get_0(l,) }) == (7))); return 0; } ``` This PR fixes this by moving the definition of `Local_1` at a global scope: ```rs // Correct: now Local_1 is visible for the instantiation pub unsafe fn get_0(mut t: Local_1) -> i32 { return (t.x as i32); } unsafe fn main_0() -> i32 { let mut l: Local_1 = Local_1 { x: 7 }; assert!(((unsafe { get_0(l,) }) == (7))); return 0; } pub struct Local_1 { pub x: i32, }; ``` commit 07be1084b140f2ed31a15febfce8895eb9cbee91 Author: Lucian Popescu Date: Wed Sep 16 15:38:24 2026 +0100 Delete test banners and the unused address_taken.cpp (#374) commit ae8355b1fd267abcdd718217fbd940c0570dd8b9 Author: Lucian Popescu Date: Tue Sep 15 22:17:38 2026 +0100 Add codegen/types docs (#370) This doesn't have all the info updated. For example enums are no more transalted as enum in Rust, I plan to update that in a future PR. For the moment I would like to get the core of codegen/types merged, then I will make the appropiate edits. commit 1d6980f06b38889c69298f09b9c39fb8f21556ee Author: Lucian Popescu Date: Tue Sep 15 15:26:22 2026 +0100 Remove unneeded clones in converter and rules (#371) This PR removes unneeded clones **in converter** by calling `SetValueFreshness`/`computed_expr_type_`/`SetFreshType` on every fressh-value-producing path. It asserts that `computed_expr_type_` is not Unknown after a conversion to make sure that we did not miss any path. PendingDeref carries freshness separately. Producers of PendingDeref set freshness and consumers act based on that. I also added `ComputedExprType::Pending` because freshness is unknown until the consumer of PendingDeref sets it. This PR also removes unneeded clones **in rules** using the `Access` IR attribute. Rules no longer clone their arguments. The preprocessor infers how each argument is used: `borrow, borrow_mut, move, take`. A `move` (`let s = a0`) consumes the argument, and it's cloned if it's not already fresh. A `borrow` usage (`a0.len()`) is never cloned. Besides removing unneeded clones, on the converter side, this fixes https://github.com/Cpp2Rust/cpp2rust/issues/363. On the rules side, `rule-preprocessor` infers the access type of each argument automatically instead on relying on the rule author to encode it properly. This caught a bug in `std::vector::push_back(T&&)`: the rule argument took the argument by value and cloned it, instead of moving it. commit 04f96ff48129eacbeb376f25adf3eb5d7deef55b Author: Lucian Popescu Date: Mon Sep 14 22:13:54 2026 +0100 Skip cast when reference coerces to pointer (#368) commit d40c5c8b75aec28d784a1b1b44412a864422897f Author: Lucian Popescu Date: Mon Sep 14 15:57:26 2026 +0100 Add more rules for move assignment and constructor (#362) commit d96ed5a414b2f1214e8af73459bcbead112ebed0 Author: Lucian Popescu Date: Mon Sep 14 10:36:21 2026 +0100 Translate nameless C++ params as _a1, _a2, etc (#361) commit 4290c68af058382c57bf33ce6548091822c95ce9 Author: Lucian Popescu Date: Mon Sep 14 10:04:05 2026 +0100 Add move rules for STL containers (#360) Don't special case the move constructor of unique_ptr inside the converter. Instead add a rule for it. Also added move constructs and assignment rules for array, vector, map. commit c2bf86b6a727262b4c5f316073c2b64b8ad38b66 Author: Lucian Popescu Date: Sun Sep 13 18:30:38 2026 +0100 Bump to LLVM 23 (#359) Co-authored-by: Nuno Lopes commit 8ffb93b50e99c4eb9f9cdbe0b13e03ca2aae4de6 Author: Nuno Lopes Date: Sun Sep 13 13:51:31 2026 +0100 Fix clang 23 build commit bf1b2d49004b24387c3af4ddeeb0b50e4109f03c Author: Nuno Lopes Date: Sun Sep 13 09:14:57 2026 +0100 fix build with clang 24 commit 8275e7eb20d9bce411316e752523f0223bfec711 Author: Lucian Popescu Date: Sun Sep 13 08:58:14 2026 +0100 Translate defaulted and implicit comparison operators (#356) This PR translates defaulted comparison operator such as `==` or `<=>` and the implicit comparison operators derived from `<=>`. It asks Sema to define the defaulted/implicit operators and then cpp2rust translates them as normal methods. commit 345588146b165049905f33c907a074e81a393e75 Author: Lucian Popescu Date: Sun Sep 13 08:56:27 2026 +0100 Translate reference to array (#357) Reference to array is translated as `Ptr>` in refcount and `*const/mut [T; N]` in unsafe. I added a new ConversionKind called Pointee to translate reference to array in refcount. The semantics are: * `FullRefCount`: `Value` * `Pointee`: the object itself * `Unboxed`: same as `Pointee`, except arrays which are `[T; N]` intead `Box<[T]>` I also added `Ptr::from_string_literal_array` which is similar to `Ptr::from_string_literal`, the only difference is that that the new function returns `Ptr>` instead of `Ptr`. commit 329a61aace8b1c7707b44abcdb430028365c12f1 Author: Lucian Popescu Date: Sat Sep 12 13:59:07 2026 +0100 Add traits on field-less structs (#354) Instantiated templates can be generated as structs with zero fields, aka unit struct. We need unit structs to be copyable, clonable, defaultable and byterepr because they can be included in other structs that already satisfy those traits. For example: ```cpp // This will be converted as a unit struct, it has no fields class Probe { public: Probe &operator++(); }; // Wrapper contains a unit struct, the unit struct must be clonable, copyable, etc, // so that Wrapper can be clonable, copyable, etc template struct Wrapper { T base_; int tag; }; int main() { // Instantiating Wrapper with Probe Wrapper a{}; } ``` I also implemented ByteRepr as a proc-macro. Currently it only supports unit structs. commit dbcc393fc5fef54cd47e1d26571da1e116ff3bde Author: Lucian Popescu Date: Sat Sep 12 13:56:46 2026 +0100 Fix comparison operator call for move-only structs (#355) A move-only type, i.e. one that has copy constructor and assignment deleted, cannot be `.clone()`'ed. PartialOrd/Ord assumed that the type is aways clonable. This PR fixes PartialOrd/Ord by cloning the Rc's of the fields, instead of calling clone on the entire struct. The comparison semantics are not changed. commit 94cd7faba93d94b1f0545484c2fdeac47a0763bc Author: Lucian Popescu Date: Fri Sep 11 16:11:52 2026 +0100 Mangle overloaded methods (#353) commit 3af40fb40bd11baff1ee140bac06399bbfdade5e Author: Lucian Popescu Date: Thu Sep 10 21:57:38 2026 +0100 Don't translate constexpr functions to const fn (#352) In C++ constexpr can call non-constexpr functions. Rust does not allow calling non-const function in const fn. This includes user-defined non-const functions and std functions like `.clone()`, `Rc::new()`, etc. commit 859782e1bda83ae2c39ced5a5972bdf403dce110 Author: Lucian Popescu Date: Thu Sep 10 21:44:18 2026 +0100 Translate friend functions and friend specializations (#351) commit 20916e6bef664f80d85efc79a1427246100bb5ac Author: Lucian Popescu Date: Thu Sep 10 21:42:04 2026 +0100 Add more builtin types (#350) commit 95e27428eb9199c874a595b26ba2d22cbcd549fd Author: Lucian Popescu Date: Thu Sep 10 19:45:31 2026 +0100 Fix default arguments in constructor (#349) commit 2aa0f7108631c771956942bb76ea0404a396afc1 Author: Lucian Popescu Date: Thu Sep 10 08:10:19 2026 +0100 Translate scalar value init and pseudo destructor (#346) commit e2f80af959fd3eb135ffed93bd18be5d772316e2 Author: Lucian Popescu Date: Thu Sep 10 08:08:50 2026 +0100 Translate move constructor and move assignment (#343) Move constructor is translated as a normal constructor: ```rs impl MoveOnly { pub unsafe fn MoveOnly_pmutMoveOnly(o: *mut MoveOnly) -> Self { let mut this = Self { v: (*o).v }; (*o).v = 0; this } } ``` Move assignment is translated using `operator_assign`: ```rs impl MoveOnly { pub unsafe fn operator_assign_pmutMoveOnly(&mut self, o: *mut MoveOnly) -> *mut MoveOnly { if ((self as *mut MoveOnly) == (o)) { return &mut (*(self as *mut MoveOnly)) as *mut MoveOnly; } self.v = (*o).v; (*o).v = 0; return &mut (*(self as *mut MoveOnly)) as *mut MoveOnly; } } ``` The combination defaulted move constructor and non-default copy constructor is not supported yet. `std::move` is translated as a clone() currently. It assumes that a clone is bitwise, i.e. the copy constructor is implicit. If it's not, the `std::move` would call the user defined copy constructor which is wrong. I will work on this in a future PR. commit e843e77b258b6ce01161c1cbf814542c6490e0fc Author: Lucian Popescu Date: Thu Sep 10 08:01:32 2026 +0100 Translate alignof and alignas (#344) commit 187f2ce340129558ffb62a5589764f36ad4daee5 Author: Lucian Popescu Date: Wed Sep 9 16:54:24 2026 +0100 Translate copy constructor and copy assignment (#342) Copy constructors are translated in the `impl Self` block, they take no receiver and build the `Self` object using the provided argument. If the copy constructor is not defaulted and not deleted, both unsafe and refcount generate it and Clone calls it. ```rs impl Counted { // Copy constructor pub unsafe fn Counted_pconstCounted(o: *const Counted) -> Self { let mut this = Self { v: (*o).v }; copies_0.prefix_inc(); this } } impl Clone for Counted { // Clone implementation calls the copy constructor fn clone(&self) -> Self { unsafe { Counted::Counted_pconstCounted(self as *const Counted) } } } ``` If the copy constructor is defaulted, unsafe continues to derive Clone and refcount continues to generate a deep copy: ```rs fn clone(&self) -> Self { Self { field: Rc::new(RefCell::new(field.clone())); ... } } ``` Copy assignment is translated using `operator_assign`: ```rs impl PartialImpl for Ptr { fn operator_assign(&self, o: Ptr) -> Ptr { if ((*self) == (o)) { return (*self).clone(); } ... return (*self).clone(); } } ``` And users of copy assignment are translated as `PartialImpl::operator_assign(&a.as_pointer(), b.as_pointer())` commit 549935b18b4571cd39f04e88e87376af47356567 Author: Lucian Popescu Date: Wed Sep 9 15:38:30 2026 +0100 Convert `&self` to `*mut/*const Self` in pointer context (#341) Converts `self` to a Rust pointer only when needed: in pointer comparisons or pointer casts. Otherwise `self` continues to be a reference, for example when accessing a field of the object. commit 83e9a9250f3c549294d5a15193f1f6a3ccd46f4c Author: Lucian Popescu Date: Wed Sep 9 11:08:33 2026 +0100 Fix default arguments (#340) If no default argument is provided, the Rust argument should be `None`, not `Some(Default)` commit 47a87313ba6ac2baaed32d3e2b943d42084ee961 Author: Lucian Popescu Date: Wed Sep 9 10:36:44 2026 +0100 Call take on unique_ptr in unsafe (#339) After a `std::move` from a `std::unique_ptr`, C++ nulls the unique_ptr. Refcount already did that, unsafe did not. Also call `take()` in unsafe to null the pointer. commit 709cf6379ca81ac54701feffe686f49ec3df5286 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue Sep 8 10:10:17 2026 +0100 Fix std::array default initializer generating empty Vec instead of sized Vec (#337) `std::array v = {};` incorrectly generated `vec![]` (0 elements) instead of a 3-element vector with default values. ```cpp std::array v = {}; ``` Before: ```rust let mut v: Vec = vec![]; ``` After: ```rust let mut v: Vec = std::array::from_fn::<_, 3, _>(|_| Default::default()).to_vec(); ``` - Fixes #313 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit ac089458bf874d7deecd472bea94453f2d816957 Author: Lucian Popescu Date: Tue Sep 8 09:56:01 2026 +0100 Translate operators (#335) This PR adds support for the following operators: * arithmetic: `+, -, unary +, unary -, *, /, %, ++ prefix and postfix, -- prefix and postfix` * relational: `==, !=, >, <, >=, <=, <=>` * logical: `!, &&, ||` * bitwise: `~a, &, |, ^, <<, >>` * assignment: `=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=` * member and pointer: `[] and static [], *, &, ->` * other: `() and static (), , (comma), user defined conversion` Not supported: * `<<` for streams * `a->*b` pointer to member * `"a"_b` user defined literal * `new`, `delete` In Rust, operators are translated as `operator_call`, `operator_comma`, etc. Operators that are cv or ref-qualified contain that information in the name, for example `operator_call_const_volatile_rref`. We support both method versions and free function versions of operators. Furthermore, we also support static `operator[]` and static `operator()`. The following operators are used to generate Rust traits: * `operator<` or `operator<=>` generate PartialOrd and Ord * `operator==` or `operator<` or `operator<=>` are used for PartialEq and Eq * the operators are implemented on `Ptr` while the traits are implemented on `Self` I also added rules and tests for `operator<=>`. Since this is a C++20 feature, I had to change CMakeLists, rules/ and Cpp2RustTest.py in order to compile everything related to `operator<=>` with `-std=c++20`. commit 75b2f4d038b16448f979c236a7eda95e0a47cc4c Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue Sep 8 09:30:29 2026 +0100 Fix #319: Fix implicit pointer cast between types with different Rust representations (#336) When size_t and unsigned long (or similar typedef-aliased types) are used interchangeably as pointer types, Clang emits a CK_NoOp ImplicitCastExpr in the AST. The translator was silently dropping this, producing a type mismatch in the generated Rust code (`Ptr` passed where `Ptr` is expected). --------- Co-authored-by: Nuno Lopes commit 74405d773c4137e897088c5ff973bb69d3d4df22 Author: Lucian Popescu Date: Mon Sep 7 11:14:02 2026 +0100 Add documentation for libcc2rs (#308) While writing the docs for libcc2rs I realized that rc.rs is very heavy. So I took some of the code and put it in other files: * void.rs - Everything related to AnyPtr. @nunoplopes do you think VoidPtr is a better name? * ptr_dyn.rs - Everything related to PtrDyn * cstr.rs - The `Ptr` specialization for strings * move CStringiterator and StringIterator in iterator.rs I also deleted the unused `impl PostfixInc for Cursor<*mut T>` commit 022be77ea494d25f5684fe418f609cdf73639f41 Author: Lucian Popescu Date: Mon Sep 7 11:03:47 2026 +0100 Convert late instantiated methods on `Ptr` instead of `self` (#334) After #333, late instantiated methods from templates were not added in the `impl X for Ptr` block. For exmaple: ```cpp // 2 methods, each instantiated in separate TUs. Second TU got it wrong template struct S { T x = 0; // visited in first TU: correctly added in `trait SImpl` and `impl SImpl for Ptr` void set(T v) { x = v; } // visited in the second TU: not added in `trait SImpl` at all and incorrectly added // in `impl S` instead of `impl SImpl for Ptr` T get() { return x; } }; ``` This PR solves it by also deferring trait blocks at the end of the file and adding late instantiated methods in both `trait SImpl` and `impl SImpl for Ptr`. commit ae78af8b6fd8e49c8f7eeb62f379a314d17cbdda Author: Nuno Lopes Date: Mon Sep 7 10:43:26 2026 +0100 cache regex compilation for a 20% speedup commit 84a45a85f0893e1bd500753972599e8c69d9878a Author: Nuno Lopes Date: Mon Sep 7 10:27:25 2026 +0100 remove some string copies commit 3c41dfd21b2600957ea10cf3893c297470bb99fd Author: Lucian Popescu Date: Sun Sep 6 20:14:28 2026 +0100 Translate `this` as `&Ptr` instead of `&self` (#333) cpp2rust translates `this` as `&self` which is wrong for methods that return a reference or a pointer to this, for example: ```cpp S& inc() { a++; return *this } ``` This PR translates `this` as `&Ptr` instead. Below, I will describe how this changes the generated code for: methods, static methods, constructors, and destructors. Methods stop being implemented in a `impl S {}` block, instead they are implemented as: ```rs pub trait SImpl { pub fn inc(&self); } impl SImpl for Ptr { pub fn inc(&self) { (*(*(*self).upgrade().deref()).a_.borrow_mut()).postfix_inc(); return (*self).clone(); } } ``` Rust does not allow implementing methods on foreign types, i.e Ptr which is defined in libcc2rs, not in the translated code. But it allows implementing local traits (`SImpl`) on foreign types (`Ptr`). Static methods are unchanged because they don't operate on `this`. Inside constructors, `this` is constructed from a local variable which represents the partially constructed object. It allows initializing fields and calling methods. Then the value inside this is returned at the end of the constructor. Destructors stop being translated using the Drop trait. Rust does not allow Drop implementation on `Ptr` and the order of destruction [is different](https://github.com/Cpp2Rust/cpp2rust/issues/331) than in C++. Those 2 limitations make Drop unusable. Instead, for each destructible record, we translate the destructor as a normal method on `Ptr` called `destructor`, and we create a `ScopedDestructor` that runs at the end of the scope and calls our `destructor` method: ```rs let o: Value = ...; let _dtor_o = ScopedDestructor::new(&o, |__p| __p.destructor()); ``` A good implication of `destructor` being implemented as a normal method, instead of Drop, is that now we support method calls inside destructor. Other important notes about this implementation: * Rust does not allow multiple `impl SImpl for Ptr {}` blocks. Because of this we gather all converted methods and only emit them at the end of the file in a single impl block * a class that has a `write` or `read` method clashes with `Ptr::write` and `Ptr::read` inside `impl SImpl for Ptr`. To avoid this, we use the universal function call syntax (UFCS): `SImpl::write` and `Simpl::read` commit d0b867943482339c14ec2cebf7f0b1a7e7f7eb35 Author: Lucian Popescu Date: Sun Sep 6 11:52:25 2026 +0100 Translate destructors in unsafe (#330) Fixes #310 Furthermore, droppable types don't implement copy. commit ac7ef494a34058da3d45b2cdada57269722da5b6 Author: Lucian Popescu Date: Fri Sep 4 23:22:14 2026 +0100 Only translate used methods of a template class (#329) cpp2rust materializes template specializations in order to have access to defaulted copy or move constructors. However, materializing all the methods of a class is wrong. Some methods might not satisfy the constraints of the type that specializes the class, for example: ```cpp struct Point { int x; }; template struct Box { T val; T get() { return val; } // This only works for T's that have operator+. // int is fine, Point is not. T twice() { return val + val; } }; ``` Specializing `Box` with `int` satisfies both get and twice, however `Point` only satisfies get. This PR only materializes methods that are used by the specialization. commit 626b94069a1791ff3831fc6b15d57a734daba574 Author: Lucian Popescu Date: Fri Sep 4 23:00:31 2026 +0100 Translate scopes (#326) Each C++ scope: ```cpp int foo() { int a; { int a; } } ``` Becomes a proper Rust scope: ```rust fn foo() { let a: i32; { let a: i32; } } ``` commit 57a2ad4e1d546092c8f67deeb1b3452629d65e8d Author: Lucian Popescu Date: Fri Sep 4 22:57:21 2026 +0100 Use borrow instead of clone in void cast (#327) In a void cast we clone the argument and immediately drop it. Not all types implement Clone. Instead, take a reference and immediately drop it. This works for types that don't implement Clone. commit ec8ea09efc621d165a074709951ac8b0e721d8c0 Author: Lucian Popescu Date: Fri Sep 4 22:55:37 2026 +0100 Translate sizeof pack (#328) commit ce3ee01f628438456a0793fefb7342870f7b356b Author: Lucian Popescu Date: Fri Sep 4 22:54:31 2026 +0100 Translate C++ enums as Rust integers (#325) The previous translation, C++ enums -> Rust enums had two weak points: * it did not allow enums that have equal variants, for example `enum E { A = 1, B = A }` * operations on the enum could not be used in const context, for example: `int global = E::A != E::B`. `!=` for enums is not const so it cannot be used in const expressions like initializing a global variable The new translation does C++ enums -> Rust integers and the name of the values becomes `E_A` instead of `E::A`. commit 7c40d9fb9a6c528c7b2b14d2e347ff3809ba7e8d Author: Lucian Popescu Date: Fri Sep 4 20:17:15 2026 +0100 Allow records with system header fields to implement ByteRepr (#300) commit 048c2713edd6a1bbe6526ced15251d669e0d6fee Author: João Gouveia Date: Fri Sep 4 20:15:19 2026 +0100 Ensure deterministic tests always return 0 (#324) commit 75b95e5d751757e723bb9e5d20e00a9c26d655e2 Author: Lucian Popescu Date: Fri Sep 4 14:34:09 2026 +0100 Translate template packs (#323) Their body is fully instantiated at every call site. In the AST, the names of all expanded arguments is `args` which is wrong in the translated code because arguments have to have different names. I added support in the converter to translate them as `args_1`, `args_2`, etc. commit ee30b08bfc0634928f02006672060d5fbc10e6dc Author: João Gouveia Date: Sun Aug 30 10:41:06 2026 +0100 Translate rvalue references to `Ptr` (#318) commit dd048b55bec4ac60fb13a679a11b9bf9bfa03f5a Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat Aug 29 12:40:00 2026 +0100 Update Rust nightly version (#321) Recent Cargo versions no longer produce a double `lib` prefix for crates whose names start with `lib` (e.g., `libcc2rs` now produces `libcc2rs-.rlib` instead of `liblibcc2rs-.rlib`). This broke the rule-preprocessor's `find_rlib` lookup, causing `rustc_driver::run_compiler` to fail with unresolved `libcc2rs` imports across all rule files. Fixes #317 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 50038e48e05744ea9d89774687ef82388769f4f9 Author: Nuno Lopes Date: Fri Aug 21 13:40:16 2026 +0100 Update Rust to 1.98 (#316) commit 519f2190ef6aa0ac0febf2a5ce4c895d5231ab6b Author: Lucian Popescu Date: Thu Aug 20 15:57:22 2026 +0100 Add documentation for rules (#302) It covers: * how to write a rule * how cpp-rule-preprocessor and rule-preprocessor work * the format of Rules IR * etc See docs/src/SUMMARY.md for a complete list. commit bb8821d1035a39849804287765602d213f162057 Author: João Gouveia Date: Tue Aug 18 14:21:46 2026 +0100 Fix operator name collisions (#307) Changes how operators that are spelled using `<` or `>` are printed. Printing `<` or `>` collided with the search performed by `Mapper::matchTemplate`. commit 11f0b21682361d664c48190fb2b8aa95ca0384dd Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun Aug 16 10:37:25 2026 +0100 Bump actions/deploy-pages from 4 to 5 (#304) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5.
Release notes

Sourced from actions/deploy-pages's releases.

v5.0.0

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.5

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.4

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.3

Changelog

... (truncated)

Commits
  • cd2ce8f Merge pull request #404 from salmanmkc/node24
  • bbe2a95 Update Node.js version to 24.x
  • 854d7aa Merge pull request #374 from actions/Jcambass-patch-1
  • 306bb81 Add workflow file for publishing releases to immutable action package
  • b742728 Merge pull request #360 from actions/dependabot/npm_and_yarn/npm_and_yarn-513...
  • 7273294 Bump braces in the npm_and_yarn group across 1 directory
  • 963791f Merge pull request #361 from actions/dependabot-friendly
  • 51bb29d Make the rebuild dist workflow safer for Dependabot
  • 89f3d10 Merge pull request #358 from actions/dependabot/npm_and_yarn/non-breaking-cha...
  • bce7355 Merge branch 'main' into dependabot/npm_and_yarn/non-breaking-changes-99c12deb21
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/deploy-pages&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit f85817463540a903e301544bbaed3f6ec186ded2 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun Aug 16 10:37:13 2026 +0100 Bump actions/checkout from 4 to 7 (#305) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
Release notes

Sourced from actions/checkout's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6.0.3...v7.0.0

v6.1.0

What's Changed

https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/ for more details about this breaking change

Full Changelog: https://github.com/actions/checkout/compare/v6.0.3...v6.1.0

v6.0.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.3

v6.0.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v6.0.1...v6.0.2

v6.0.1

What's Changed

... (truncated)

Changelog

Sourced from actions/checkout's changelog.

Changelog

v7.0.1

v7.0.0

v6.0.3

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit acd7f6bc1aa12897ec269e63631182bdd671b9a6 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun Aug 16 10:36:52 2026 +0100 Bump actions/upload-pages-artifact from 3 to 5 (#306) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 5.
Release notes

Sourced from actions/upload-pages-artifact's releases.

v5.0.0

Changelog

See details of all code changes since previous release.

v4.0.0

What's Changed

Full Changelog: https://github.com/actions/upload-pages-artifact/compare/v3.0.1...v4.0.0

v3.0.1

Changelog

See details of all code changes since previous release.

Commits
  • fc324d3 Merge pull request #139 from Tom-van-Woudenberg/patch-1
  • fe9d4b7 Merge branch 'main' into patch-1
  • 0ca1617 Merge pull request #137 from jonchurch/include-hidden-files
  • 57f0e84 Update action.yml
  • 4a90348 v7 --> hash
  • 56f665a Update upload-artifact action to version 7
  • f7615f5 Add include-hidden-files input
  • 7b1f4a7 Merge pull request #127 from heavymachinery/pin-sha
  • 4cc19c7 Pin actions/upload-artifact to SHA
  • 2d163be Merge pull request #107 from KittyChiu/main
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-pages-artifact&package-manager=github_actions&previous-version=3&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit a721130030157defee5fafbd0ec9953f89c809f9 Author: Lucian Popescu Date: Thu Aug 13 16:43:53 2026 +0100 Add more flags in fopen (#299) commit f5b0d881f40378df23be284053e054a3b023d83d Author: Lucian Popescu Date: Thu Aug 13 15:44:08 2026 +0100 Add documentation infrastructure (#301) The documentation will sit in the `docs/` dir which contains a mdbook. The new `Deploy docs` workflow (inspired from [here](https://github.com/actions/starter-workflows/blob/main/pages/mdbook.yml)) builds the mdbook and, using Github Pages, hosts it at https://cpp2rust.github.io/cpp2rust/ commit 82fc663c8aab1080c7ca3ce554a8dbb77f64e6be Author: Lucian Popescu Date: Sun Aug 9 08:51:22 2026 +0100 Add full function type when converting FnPtr::null (#298) This avoids situations when rustc cannot infer the type of the null function pointer. commit 8f67912b1659526b177ea446cc10bab149008e72 Author: Lucian Popescu Date: Mon Jul 27 08:51:34 2026 +0100 Use with_mut/with for Ptr::write/Ptr::read (#292) This just simplifies the implementation of Ptr::write/Ptr::read commit 6ca8964fbd9079f0b47417b23c07f8e7b3ed1497 Author: Lucian Popescu Date: Sun Jul 26 22:35:12 2026 +0100 Remove clones on fresh values (#293) commit b3ac58a941738ae0e0d4081671d248f85d7016f8 Author: Nuno Lopes Date: Sun Jul 26 14:58:54 2026 +0100 Update Rust stable and nightly versions (#291) commit cae6cf47e2e6b58285fa295a7895dba6517162db Author: Lucian Popescu Date: Sun Jul 26 14:04:18 2026 +0100 Replace std::fs::File with libcc2rs::CFile (#286) commit ffcf0c29535dc1b30ade18b65f8692d8086aa22f Author: Lucian Popescu Date: Sun Jul 26 13:32:13 2026 +0100 Add safe sockopt rules (#284) I implemented setsockopt in libcc2rs/src/libc_shims/socket.rs because it contains a lot of match arms and some of them are linux specific. It doesn't make sense to have them in the generated code for macos, for example commit 76a34d55b475b1cb7fdf7297abdb841db88bc851 Author: Lucian Popescu Date: Sun Jul 26 13:08:37 2026 +0100 Avoid clone on fresh function pointer (#290) commit 365ba980202cf7d5a7461cb0553a3ad04a530c2c Author: Francisco Gouveia Date: Sat Jul 25 17:12:56 2026 +0100 Add a `rust-toolchain.toml` for the rule-preprocessor (#288) As this crate requires the `rustc-dev` component, which is not bundled by default, we need to add it explicitly. Now, a user can just do `cargo build` on this crate. commit 2551bf25646e3c525ff57be62b1f3a10c234d26c Author: Lucian Popescu Date: Sat Jul 25 17:11:46 2026 +0100 Pin ruff version and fix sporadic double unlink in tests (#287) ruff got suddenly updated and our python linting broke. Pin ruff to a fixed version so linting is stable. commit ff65bf1052fcdbb4657cb62d32b37ec8c370efa7 Author: Lucian Popescu Date: Thu Jul 23 19:20:49 2026 +0100 Add select and poll safe rules (#282) This also adds `FdRegistry::with_fds` which receives multiple raw file descriptors (i32) and applies function f on the BorrowedFd's derived from the raw file descriptors. The single-fd variant of `with_fds` is the already existing `with_fd` commit 1c831f5d31709271dd4d2a638af672ac3dc9ada3 Author: Lucian Popescu Date: Thu Jul 23 18:37:15 2026 +0100 Add fsetxattr safe rule (#285) commit 74598095b5d36dd974b01576bef0ef011e1eee77 Author: Lucian Popescu Date: Thu Jul 23 18:35:46 2026 +0100 Add safe fcntl (#283) commit b6670b9eb473ed2f28757710ce03de5d21782598 Author: Lucian Popescu Date: Thu Jul 23 18:34:06 2026 +0100 Add fstat and termios safe rules (#281) commit de9bae3f99f1ff6098427a244086956e3145af26 Author: Lucian Popescu Date: Thu Jul 23 14:24:44 2026 +0100 Add safe rules for lseek,ftruncate,isatty,listen,eventfd (#279) commit b992bd16169d90b73aa37dc457a90e5ca23b4173 Author: Nuno Lopes Date: Thu Jul 23 13:55:43 2026 +0100 CodeQL: removed concurrency settings and unneeded Rust setup (#280) commit c5462d5b306846e879eac07fb3a0e12bfbff16bd Author: Lucian Popescu Date: Thu Jul 23 13:50:21 2026 +0100 Add safe rules for socket/socketpair/pipe (#278) commit 20db039d9d095f288cb3d04185b2085918a8f755 Author: Lucian Popescu Date: Tue Jul 21 19:01:47 2026 +0100 Add safe translation for open/read/write/close (#277) This PR adds the building blocks of safe file descriptor IO translation. In Rust, C file descriptors (int), are usually translated as OwnedFd/BorroweFd. nix also uses OwnedFd/BorrowedFd inside the IO APIs, for example [open](https://docs.rs/nix/latest/nix/unistd/fn.write.html) is defined as: `pub fn open(path: &P, oflag: OFlag, mode: Mode, ) -> Result` However, we cannot directly translate integers to OwnedFd/BorrowedFd. An integer can be used as both integer and OwnedFd at the same time, which Rust does not allow. For example: ```c int fd = -1; // plain integer fd = open(...) // file descriptor semantics if (fd > 0) // plain integer write(fd, ...) // file descriptor semantics ``` To overcome this limitation, allow integers to be translated as i32, and keep a global FdRegistry which contains mappings of type `i32 -> OwnedFd`: ```rs pub struct FdRegistry { fds: Vec>, } impl FdRegistry { // Creates a new i32 -> OwnedFd entry pub fn register(fd: OwnedFd) -> i32 // Applies function f over the BorrowedFd: f(FdRegistry[fd]) pub fn with_fd(fd: i32, f: impl FnOnce(BorrowedFd<'_>) -> R) -> R // Removes the i32 -> OwnedFd entry pub fn close(fd: i32) -> i32 } ``` This way, integer operations on file descriptors continue to work, for example `int fd = -1` or `if (fd > 0)` and functions that use file descriptor access the OwnedFd/BorrowedFd through the FdRegistry, for example: ```rs let fd: i32 = -1; fd = FdRegistry::register(nix::fcntl::open(...)); if (fd > 0) FdRegistry::with_fd(fd, |bfd: BorrowedFd| nix::unistd::write(bfd, ...)) ``` In the following PRs I will add the remaining safe IO rules. --------- Co-authored-by: Nuno Lopes commit 69e0838a0af248ee455b467b7326ddff57058fee Author: Lucian Popescu Date: Tue Jul 21 14:16:39 2026 +0100 Add safe snprintf rule (#275) The snprintf safe rule uses the new `libcc2rs::format_c` function. The rule is: ```rs 1. let s = libcc2rs::format_c(&a2.to_rust_string(), va); 2. let b = s.as_bytes(); 3. if a1 > 0 { 4. let n = min(b.len(), a1 - 1); 5. a0.with_slice_mut(n + 1, ... copy n+1 + NUL byte in a0) 6. } 7. b.len() ``` Initially, I planned to use `format!` instead of `libcc2rs::format_c` at line 1 because that's definitely more idiomatic. However we need to support runtime format strings, for example: ```c char fmt[] = "%d", snprintf(buf, n, fmt, 123); ``` The `format!` macro in rust does not support runtime format strings. It only works with compile time format strings. For this reason I created `libcc2rs::format_c` which internally uses the [sprintf crate](https://docs.rs/sprintf/latest/sprintf/index.html). The role of `format_c` is to validate the format string, convert each `libcc2rs::VaArg` to `sprintf::Printf` then do the actual formatting. commit ebb8d83a9019cdc6021c27cf2290f9183b58fbb9 Author: Lucian Popescu Date: Sun Jul 19 19:31:58 2026 +0100 Add rules for CLOCK_MONOTONIC constants (#274) commit cf8fefdf5d729305ba8d7bbc79ed1808e1d4487c Author: Nuno Lopes Date: Sun Jul 19 09:38:49 2026 +0100 fix test commit 88c549a69212c62dbdea39d16cbe8c98be9fe8e5 Author: Lucian Popescu Date: Sun Jul 19 09:20:04 2026 +0100 Add safe getpwuid (#267) commit f7a94cbb3f5f8b3106b2a65856db30a88675cf68 Author: Lucian Popescu Date: Sun Jul 19 09:15:20 2026 +0100 Add safe socket rules (#272) commit e351e88f655e1617f3e1752783e515e4c801783b Author: Lucian Popescu Date: Sun Jul 19 08:59:33 2026 +0100 Add safe pton and ntop rules (#243) commit 3f15fab0d34b717698061e710ed5d6c7d02cc4d0 Author: Lucian Popescu Date: Sun Jul 19 08:58:24 2026 +0100 Update tests (#273) commit 3d4a247fff303ceb2bd02beaeaaec499897a6776 Author: Lucian Popescu Date: Sat Jul 18 18:06:22 2026 +0100 Add if_nametoindex safe rule (#270) commit 4db427783476166db8e16ebddc0a864c66272bb1 Author: Lucian Popescu Date: Sat Jul 18 17:56:53 2026 +0100 Add safe realpath (#269) commit 14fad49e093cadf752b896ce2180a93e9e10d92a Author: Lucian Popescu Date: Sat Jul 18 17:53:56 2026 +0100 Use with_slice_mut instead of looping over bytes (#268) commit 543057114046f54b412b04cf755826b8d4b9d28d Author: Lucian Popescu Date: Sat Jul 18 17:50:45 2026 +0100 Add rules for errno constants (#271) commit fd129478eb95d890b13c62b617a0289a5597d42f Author: Lucian Popescu Date: Sat Jul 18 15:19:26 2026 +0100 Add safe cstring rules (#250) commit 66277eca254e390bada91f6aa7a15810ca849ebb Author: Lucian Popescu Date: Sat Jul 18 15:06:27 2026 +0100 Add safe stdio rules (#261) commit 3edb1f0cc483e6aa096f60de28562bc143718e78 Author: Lucian Popescu Date: Sat Jul 18 15:01:47 2026 +0100 Add send/recv safe rules (#262) This also introduces Ptr::with_slice and Ptr::with_slice_mut that create a slice of the pointed to data. commit 337933b2e7bfe0af0205d461e2730ad796691602 Author: Lucian Popescu Date: Sat Jul 18 14:59:49 2026 +0100 Add localtime_r safe rule (#266) commit b49b7afe59bf6f9fde938f8602626cda1bef756d Author: Lucian Popescu Date: Sat Jul 18 14:23:23 2026 +0100 Add safe rules for time (#260) This also adds a new dependency called jiff. jiff is used to avoid manually implementing strftime and localtime_r In general, nix only contains safe wrappers over syscalls. Everything which is a pure library implementation is not included commit 98330f954bac3d4362a087f550956afb4024efaa Author: Lucian Popescu Date: Sat Jul 18 14:21:20 2026 +0100 Add safe fdset macros rules (#263) Nix also contains a FdSet, but its semantics are too strict for mechanical translation: https://docs.rs/nix/latest/nix/sys/select/struct.FdSet.html nix::FdSet has lifetime attached and the methods insert/remove/etc take a BorrowedFd, not a raw integer. This is too strict for the C semantics. Instead, in C, you can modify the fd set in whichever way you want, and only a later call to select will validate if the fd set is in a valid state or not. To fix this limitation I added CFdSet which behaves like the C version of fd set. commit d0f91e24690d4264f55a99667df0b817e6de61d2 Author: Lucian Popescu Date: Sat Jul 18 14:19:12 2026 +0100 Add getifaddrs, freeifaddrs, getaddrinfo, freeaddrinfo safe rules (#265) commit 8763f98cd1ef23a1c93f673c0c82553e2e47b751 Author: Lucian Popescu Date: Sat Jul 18 14:15:28 2026 +0100 Add misc safe rules (#264) commit a93c54bd46132f05ba225cf33393d62e5b8a5f9c Author: Lucian Popescu Date: Thu Jul 16 21:44:51 2026 +0100 Add unistd safe rules (#259) commit 261194b3094d84aa670156243723a325086bd681 Author: Lucian Popescu Date: Thu Jul 16 18:59:05 2026 +0100 Add qsort and bsearch safe rules (#258) commit 1b2d86ff796e5b4c4a0f9d195d00f3e1834accfc Author: Lucian Popescu Date: Wed Jul 15 14:28:31 2026 +0100 Add translation rules for variadic libc calls (#159) Variadic rules are defined with the following syntax: ```cpp template int f1(int a0, unsigned long a1, Args... args) { return ioctl(a0, a1, args...); } ``` and ```rs unsafe extern "C" { fn f1(a0: i32, a1: u64, ...) -> i32; } ``` For the C++ side, use a template argument pack. This makes it clear that the function call is variadic. For the Rust side, use a variadic function inside an extern block, that's the only place Rust allows variadic functions. I added a new attribute in the IR, called is_extern. It's used by the new `Mapper::IsLibcPassthrough`. A libc passthrough is a rule that it's declared in Rust extern block. In the above example ioctl is defined as a passthrough for `libc::ioctl`. In the future, we can use this for non-variadic rules as well. commit a8d33987b165fa194d2d6a67d6109d507edb1eba Author: Lucian Popescu Date: Wed Jul 15 12:07:02 2026 +0100 Add rules for libc structs (#257) This adds rules for both unsafe and recount libc structs. For unsafe the rule is trivial: ```rs fn t1() -> libc::sockaddr { unsafe { std::mem::zeroed() } } ``` For refcount, I added a new dir in libcc2rs, called libc_shims. It defines refcount shim structs for each libc struct, for example: ```rs pub struct Sockaddr { pub sa_family: Value, pub sa_data: Value>, } ``` The shims exist for 2 reasons: * generated code expects each field to be boxed * libc fields can contain raw pointers, which are not permitted in refcount The refcount rule for sockaddr becomes: ```rs fn t1() -> libcc2rs::Sockaddr { Default::default() } ``` commit 3a25bb8e7fbe7e9cbfce707474a31c805f6b92a3 Author: Lucian Popescu Date: Tue Jul 14 21:27:15 2026 +0100 Add Ptr::from_int and Ptr::to_int (#229) commit 0a35c2494dd10da92e73229551fa3c35f3958473 Author: Lucian Popescu Date: Tue Jul 14 21:24:18 2026 +0100 Fix default for 2D array (#242) commit f67b2d57b85ded6a7a4c1bf4e99e2a39a92b7fd8 Author: Lucian Popescu Date: Tue Jul 14 21:16:24 2026 +0100 Fix hoisted initialization outside goto_block (#239) commit e024a43d2ea0a41e6314f5456399e0c12fd088e4 Author: Lucian Popescu Date: Tue Jul 14 20:58:05 2026 +0100 Add safe errno (#256) Safe errno is a global Value in libcc2rs. Refcount rules will write the correct errno number into this global Value commit f8b87b8dffa5bd9cd5f2b80a6eed2a36e1ea4ffa Author: Lucian Popescu Date: Tue Jul 14 17:13:45 2026 +0100 Add isize conversion for enum values used as subscript (#254) C allows indexing an array with an enum value commit 7f06e506eb1bd45ea52497a27b335fa97cdca76c Author: Lucian Popescu Date: Tue Jul 14 17:02:08 2026 +0100 Fix implicit cast of string literals (#253) commit 971f2fd954c1c13697683549808acd7872b2c3ad Author: Lucian Popescu Date: Tue Jul 14 13:49:53 2026 +0100 Translate offsetof in refcount (#255) Refcount structs don't have the same layout as C structs. Don't translate offsetof as std::mem::offset_of, instead let clang evaluate the offset and hardcode it in the generated rust code. ByteRepr takes care of using it correctly. commit 0b660511f74bdb1b4319c709793dca6ec9bf2a12 Author: Lucian Popescu Date: Tue Jul 14 12:32:57 2026 +0100 Translate unions in C++ mode (#251) commit def0dda2bbe966e772264c22519952227c3cc7cf Author: Lucian Popescu Date: Tue Jul 14 12:31:45 2026 +0100 Fix null initialization (#252) commit 9ecb860033fbe00c860b9329f48d555e2eb72a1e Author: Nuno Lopes Date: Sat Jul 11 19:50:43 2026 +0100 fix #247: various spurious clones commit 55c54ffed76fd64f26e8de4292ad4b380520bf3f Author: Nuno Lopes Date: Sat Jul 11 19:36:27 2026 +0100 remove spurious clone commit 4c46ef2114bea562e59159c366e6be8e686e4b47 Author: Lucian Popescu Date: Sat Jul 11 19:31:58 2026 +0100 Add void pointer implicit conversion (#231) commit 8ebc622a22859cb09a1cd675075c94a90398270c Author: Lucian Popescu Date: Sat Jul 11 17:19:37 2026 +0100 Fix boxing in ConvertPointeeType (#240) commit 7f89f9289ec75842343ba3a367e5b5c408e18d28 Author: Lucian Popescu Date: Sat Jul 11 11:42:44 2026 +0100 Add VaArgGet for AnyPtr (#238) commit de584923bec396544142f16f320ec96c72538503 Author: Lucian Popescu Date: Sat Jul 11 11:41:59 2026 +0100 Add AddAssign impl for i64 (#237) commit 78a1b5c846f0d3015536fb14f443fdb36394e2d2 Author: Lucian Popescu Date: Sat Jul 11 11:41:36 2026 +0100 Add void cast on function ptr unused arg (#241) commit 97dcc89d680b659e5d200acc772713efd9734583 Author: Lucian Popescu Date: Sat Jul 11 11:26:29 2026 +0100 Delete rust target dirs in ninja clean (#244) commit 6c76975df4b0a4cb542f839b04f54f012bfba7e6 Author: Lucian Popescu Date: Sat Jul 11 11:21:54 2026 +0100 Use correct expr kind when converting pointer offset (#236) commit f7c326e3e8bf53cbacefdcc7ef7355586b42cf71 Author: Lucian Popescu Date: Sat Jul 11 11:21:20 2026 +0100 Increase max arity of FnPtr (#235) commit a97abed3dc4816d524955e72a43eb8ca7ebf1cd0 Author: Lucian Popescu Date: Sat Jul 11 11:20:52 2026 +0100 Add safe malloc/calloc/realloc/free/strdup (#234) commit 23e54d83a201da98bdf38bd52a3e56659ae589dc Author: Lucian Popescu Date: Mon Jul 6 21:40:10 2026 +0100 Add Clone impl for C structs (#232) commit 405edd8e9fffb0e6b6d9585aa0a3f3461c47ad7e Author: Lucian Popescu Date: Mon Jul 6 21:38:30 2026 +0100 Avoid moves when creating variadic pointer arguments (#233) commit 4995be562caab27fac7ee04d829e4b8169f5cc88 Author: Lucian Popescu Date: Mon Jul 6 21:31:18 2026 +0100 Fix paren in compound assign (#230) commit 600ea4761ff8bc5011c4ca3ea44608057bc48495 Author: João Gouveia Date: Mon Jul 6 09:37:50 2026 +0100 Fix compound assignment type casts (#228) Fixes type casts inside compound assignments under the `unsafe` model and removes unnecessary casts under the `refcount` model. When translating binary operators, both `unsafe` and `refcount` have a special case for translating compound assignments involving different types. The types being compared were the original C++ types. However, differences between the original C++ types do not necessarily mean that the corresponding Rust types differ. This caused unnecessary casts to be introduced and, under the `unsafe` model, resulted in translations that do not compile, such as the following: ```cpp int main() { std::vector v; v.push_back(10); v.front() += 5; assert(v.front() == 15); return v.front(); } ``` ```rust pub fn main() { unsafe { std::process::exit(main_0() as i32); } } unsafe fn main_0() -> i32 { let mut v: Vec = Vec::new(); v.push(10); ((v).first_mut().unwrap()) = ((((v).first_mut().unwrap()) as i32) + 5) as i32; // invalid cast assert!(((*((v).first_mut().unwrap())) == (15))); return (*((v).first_mut().unwrap())); } ``` commit 4e137c587a63a834686f091103e134b4bf9edfae Author: João Gouveia Date: Sun Jul 5 16:49:36 2026 +0100 Add support for dependent declaration names in rules (#227) Adds support for rules such as the following: ```cpp template T1 &f2(const std::reference_wrapper &a0) { return a0.operator T1 &(); } ``` commit 801ed6dc9dacc3656a1e6204e2a5f406f1a1c943 Author: Lucian Popescu Date: Sun Jul 5 07:55:24 2026 +0100 Translate access of union array members as `Ptr` instead of `Ptr>` (#218) commit de8813c9a21160dae30a3f0c23ac56b83bb2e851 Author: João Gouveia Date: Sun Jul 5 07:54:58 2026 +0100 Allow passing a specific directory to rule-preprocessor (#226) Adds an optional command-line argument that allows specifying a directory other than the default `../rules`. commit cf07d569960d6e3481901ab437f2da3ce4596e46 Author: João Gouveia Date: Sun Jul 5 07:50:41 2026 +0100 Extend cpp-rule-preprocessor supported C++ rules (#187) Extends the set of rules that `cpp-rule-preprocessor` can handle. The biggest additions are `createMirrorType` and support for adding type hints to IR rules in the form of default template arguments. One limitation on the kinds of rules that could be processed was that the synthesized PODs did not necessarily exhibit all of the characteristics required to instantiate a given C++ construct. This patch adds support for specifying default template arguments in rules, which are used as a reference for the characteristics that the synthesized structs must exhibit. This information is then used by `createMirrorType`. When the provided type is a non-template class, the synthesized struct simply inherits from it. When the provided type is a class template, the synthesized struct becomes an instantiation of a synthesized class template that inherits from it. Roughly: ```cpp // rule with hint template > void f1() { (...) } // what is roughly synthesized: // POD, same as before struct T1 {}; // synthesized class template that inherits from the hint template class Mirror : public std::allocator<_Tp> {}; // instantiation of the synthesized class template template <> class Mirror {}; ``` Using a synthesized class template rather than inheriting directly allows the synthesized struct to match the hinted type structurally. This is useful when instantiating constructs that perform structural checks, such as: ```cpp // bits/alloc_traits.h static_assert(is_same< typename __replace_first_arg<_Tp, typename _Tp::value_type>::type, _Tp>::value, "allocator_traits::rebind_alloc must be A"); ``` These changes enable `cpp-rule-preprocessor` to parse 479 automatically generated rule sources for `std::vector` and its specializations. commit c27e64425e70b37a33c4aef628cf5210848f7e5e Author: Lucian Popescu Date: Wed Jul 1 19:29:44 2026 +0100 Handle strings with interior null pointer (#222) commit 6e26f658bbde8e4679222b71b3ceca87d4f276a6 Author: Nuno Lopes Date: Wed Jul 1 19:26:39 2026 +0100 typos in README commit eb408ce300d4054829b111dc18232685039c4095 Author: Nuno Lopes Date: Wed Jul 1 19:17:34 2026 +0100 Update Rust stable and nightly versions (#223) commit 13edc5cf86523e2af207a51948eccb8b2c5a9873 Author: Nuno Lopes Date: Wed Jul 1 19:16:01 2026 +0100 Fix comment syntax (#224) _This PR applies 1/3 suggestions from code quality [AI findings](https://github.com/Cpp2Rust/cpp2rust/security/quality/ai-findings). 2 suggestions were skipped to avoid creating conflicts._ Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> commit dd47cc26b61a8a999ac64702db824856fcacdcb6 Author: Nuno Lopes Date: Wed Jul 1 19:09:07 2026 +0100 cpp2rust driver: run runstfmt on our fixed stable version commit 7235bb852fa5ec082e881377104d35a1b9151f8a Author: Lucian Popescu Date: Wed Jul 1 17:07:01 2026 +0100 Translate char as libc::c_char in unsafe (#194) Translates char as c_char instead of u8 in both unsafe. Refcount is unchanged. This contains: 1. change all unsafe rules to use c_char instead of u8 2. use c_char instead of u8 in mapper and VisitBuiltinType 3. use c_char in argv for both unsafe 4. delete the IsCharPointerFieldFromLibc and IsCharArrayFieldFromLibc hacks 5. use the edition 2024 `c""` syntax to define c_char compatible string literals 6. use getTypedLiteral to write `0 as c_char` (correct) instead of `0_c_char` (incorrect) The big advantage of this PR is 4. IsCharPointerFieldFromLibc and IsCharArrayFieldFromLibc were temporary solutions to interact between our u8 and libc's c_char. After this PR, both functions are deleted. commit 8afd5a0cfb4afe685d9ee4fb0af45db3aa627281 Author: Lucian Popescu Date: Wed Jul 1 16:37:25 2026 +0100 Use reinterpret_cast to round-trip through void (#217) commit c3228ae3f05f925e64c40edace2a9300a1c11472 Author: Lucian Popescu Date: Wed Jul 1 13:43:32 2026 +0100 Fix test expected output (#219) commit c90abd9a4f348992647e03cb5b94c20c9967e8d1 Author: Lucian Popescu Date: Wed Jul 1 12:32:49 2026 +0100 Memcpy a pointer to a struct through ErasedPtr (#207) `AnyPtr::reinterpret_cast` was matching against a hardcoded list of types. This PR adds ErasedPtr::as_bytes which is a `reinterpret_cast::` so that memcpy/memset a pointer to a struct works. I also simplified the implementation for ErasedPtr::equals in this refactor. commit 24cb63bcf72e461d4de01054199e414e3dd2ca96 Author: Lucian Popescu Date: Wed Jul 1 12:13:22 2026 +0100 Handle explicit cast of union member (#210) Union members are translated using accessors: ```cpp union u { int a; } *(char *)&u.a = 0xFF; ``` becomes ```rs struct u { __bytes: Value> } impl u { pub fn a() -> Ptr { ... } } // the accessor u.a().reinterpret_cast().write(0xFF) // Before this PR it did not compile: (u.a() as Ptr).write(0xFF) ``` This PR replaces the wrong `(u.a() as Ptr)` pattern with `u.a().reinterpret_cast()` commit 910f666348088e6d0a64fa1aa554ec5b6f52b159 Author: Nuno Lopes Date: Wed Jul 1 12:03:07 2026 +0100 fix test output commit 3a06ed446dcb41c086bf7f61a94017381afa05fc Author: Francisco Gouveia Date: Wed Jul 1 12:01:07 2026 +0100 Allow `Ptr::offset()` to receive any value that can cast to `isize` (#213) The rationale behind this change is to avoid unnecessary casts that may hurt readability, such as: `0_usize as isize`. With this change, we are allowed to omit the cast, as it will be performed inside the `offset` function. This also has the advantage of panicking whenever we try to cast a usize larger than `isize::MAX`. commit ab27e95d87b4976a226f550be4d07d18a18c87eb Author: Lucian Popescu Date: Wed Jul 1 11:53:51 2026 +0100 Add panic-ub test mode (#214) The `panic` mode is currently used for both describing UB tests (e.g. translate null ptr deref as panic) and for describing translation gaps (panic in libcc2rs for types that don't implement ByteRepr). I renamed the first type as `panic-ub` and the second type as `panic`. So that the `Expectedly Failed Tests` in lit includes the second type, i.e. translation gaps. commit 9d37a1eb6fee3da7fd41178586a3c9b3a68ae1f8 Author: Lucian Popescu Date: Wed Jul 1 11:53:16 2026 +0100 Add panic'ing ByteRepr impl for Ptr and AnyPtr (#215) This just adds panic'ing implementations for Ptr and AnyPtr so that some tests move from no-compile to panic and adds the ByteRepr boilerplate where needed. It will make the PR with the real ByteRepr implementatoin for Ptr and AnyPtr smaller. commit b8edd481b81d3a32d635504339e1769381dd7118 Author: Lucian Popescu Date: Wed Jul 1 11:51:16 2026 +0100 Handle array to pointer decay in union accessor (#211) commit 297f77306d249147eb908be52370dcfde363b76c Author: Lucian Popescu Date: Mon Jun 29 19:53:48 2026 +0100 Add ByteRepr for unions (#212) commit 459c1ac18446bd87ee81a1a6ea14c1e4ca044663 Author: Lucian Popescu Date: Fri Jun 26 18:07:58 2026 +0100 Use C size for serialization/deserialization (#208) This adds a new method in ByteRepr called byte_size. It is used to describe the C/C++ byte size of the object and it's used for serializatoin/deserialization through from/to_bytes. In refcount, Rust structs have a different size than the C/C++ structs because each field is wrapped in `Value<>`. For example: ```cpp struct S { int32_t a; int32_t b; } sizeof(S) == 8 ``` becomes ```rs struct S { a: Value, b: Value } size_of(S) == 16 // one Rc is 8 ``` This difference in size breaks ByteRepr for structs. To do correct serialization we need the C/C++ size. Codegen embeds this into the generated code using the ByteRepr::byte_size method. Then, when a reinterpreted pointer is created, ReinterpretedView::elem_byte_size saves ByteRepr::byte_size. This is an optimization so that the ByteRepr trait is not exposed to all Ptr methods, even into methods that don't have to do with reinterpreted pointers. The goal of this is to support structs inside unions. Structs, as any other type that takes part in an union, is a reinterpreted view into the byte buffer of the union. commit 4a5adb65c72c1d64e4a8a727ee0eaf3177714ff8 Author: Lucian Popescu Date: Thu Jun 25 22:00:05 2026 +0100 Add safe translation for unions (#205) C/C++ unions are translated as a byte buffer + accessors that return a reinterpreted pointer into the byte buffer. This C/C++ union: ```cpp union basic { int i; float f; }; ``` becomes: ```rs pub struct basic { __bytes: Value>, } impl basic { pub fn i(&self) -> Ptr { (self.__bytes.as_pointer() as Ptr).reinterpret_cast() } pub fn f(&self) -> Ptr { (self.__bytes.as_pointer() as Ptr).reinterpret_cast() } } ``` Where __bytes is the byte buffer and i() and f() are the accessors that return reinterpreted views into the backing buffer. Compared to Rust unsafe/C/C++ unions, this implementation is safe because it always serializes/deserializes the bytes into valid values or panics otherwise. commit c76680720b1cc8de06df416f219f9f3d0c734f5f Author: Lucian Popescu Date: Thu Jun 25 15:01:21 2026 +0100 Add ByteRepr for enums (#206) commit fd1dd21fea3ab0a56be5fe624471a21a06433b1a Author: João Gouveia Date: Wed Jun 24 08:59:15 2026 +0100 Ensure `CheckTemplateArgumentList` is always called with a non-empty `CodeSynthesisContext` (#202) Fixes an internal Clang assertion failure triggered when preprocessing the rules for `std::unique_ptr` with `--cxxflags='-stdlib=libc++'`. commit f3323f5278cfe4f38c922c4691c4dc852c81963e Author: Lucian Popescu Date: Tue Jun 23 18:11:47 2026 +0100 Avoid hoisting for function call arguments that don't alias (#203) In terms of converter, the PR is small. I moved GetAllVars and MayCauseBorrowMutError from converter to converter_lib because they are converter-agnostic. Then I added the aliasing logic in CollectCallInfo 2 functions in converter_lib: ReferencesThis and ArgsMayAlias. The core idea is: if an argument of a function call does not alias with any other argument, then it can be inlined instead of hoisted. This includes aliases over this, for example `this.method(this.member)` commit ad57c2d68b226ecc59780b0b2773b629c34bfbc1 Author: Lucian Popescu Date: Mon Jun 22 23:24:26 2026 +0100 Add eventfd rules (#200) commit 64e3f29ca2d23140ca744e9ba5b264a7e67cac06 Author: Lucian Popescu Date: Mon Jun 22 23:24:02 2026 +0100 Fix rustls rules (#201) commit b834831d8d91e34a4775dc572b0f4e0af1d4efe7 Author: Lucian Popescu Date: Mon Jun 22 23:22:51 2026 +0100 Handle ops between i64 and isize (#199) commit 7e3c546308d369a3c4928b13125bb6649cb92666 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun Jun 21 06:58:58 2026 +0100 Bump actions/checkout from 6 to 7 (#198) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 0506a867b12c86b43cb35f6673076ba3a5d79326 Author: João Gouveia Date: Sat Jun 20 08:01:02 2026 +0100 Fix class template translations (#195) Fixes how class templates are translated. Class templates are currently monomorphized: https://github.com/Cpp2Rust/cpp2rust/blob/239fd50b0295ce7b89a5ffec8f39a917b4cdd3a3/cpp2rust/converter/converter.cpp#L3316-L3321 However, the name of each translated Rust struct was the plain C++ name, leading to collisions. Additionally, the C++ name used in the translation rule inserted into Mapper was also the plain C++ name. This name does not match how the type itself is printed, which led to internal cpp2rust assertion failures due to missing translation rules. commit 0ff70c49090f6bf7cfcf19fceb5e2bb8a7c80bb9 Author: João Gouveia Date: Sat Jun 20 07:57:59 2026 +0100 Fix translations for unnamed function parameters (#197) The current implementation of `GetNamedDeclAsString` defaults to returning `self` whenever the declaration corresponds to an unnamed function parameter. This leads to incorrect translations such as the following: ```cpp int main(int, char**) { return 0; } ``` ```rust fn main_0(self: i32, self: Ptr>) -> i32 { let self: Value = Rc::new(RefCell::new(self)); let self: Value>> = Rc::new(RefCell::new(self)); return 0; } ``` This patch addresses this issue by changing `GetNamedDeclAsString` to return `self` only when it is actually required, and `_` otherwise. commit bd1326b15101a27a545788d02345a3258af9e1b4 Author: João Gouveia Date: Mon Jun 15 21:25:13 2026 +0100 Export compiler related constants from CMake (#196) Fixes #115 commit 239fd50b0295ce7b89a5ffec8f39a917b4cdd3a3 Author: Lucian Popescu Date: Sat Jun 13 09:07:31 2026 +0100 Use from_fn for non-Copyable Rust types (#192) In Rust, a struct is Copyable if it explicitly derives or implements Copy. All fields being Copy is not enough. Add the `derives` attribute in the IR that lists all traits that a mapped Rust type derives. For example rustls_slice_bytes, a struct with Copyable fields, doesn't derive Copy. So `derives` contains only Default. User defined types and builtin types also have their `derives` field filled. The entire PR is built around the following check: ```cpp if (auto *rec = element_type->getAsRecordDecl()) { if (!RecordDerivesCopy(rec)) { return std::format("std::array::from_fn::<_, {}, _>(|_| {})", size_as_string.c_str(), element_type_as_string); } } ``` Arrays of non-copyable types cannot be initialized with `[non_copyable_default, N]`, they have to use the `std::array:from_fn` format. commit e070485997d09123861229a79e721942cf446f7c Author: Lucian Popescu Date: Fri Jun 12 22:14:13 2026 +0100 Add rules for stdlib, rustls, and stdio (#183) This mainly adds translation rules for rustls_ffi. I needed to do a few modifications in `cpp2rust/` to be able to add translation rules for typedef'ed integers, for example rustls_ffi::rustls_result (`typedef uint32_t rustls_result`): * add support in cpp-rule-preprocessor to define rule types as `typedef` in C and `using` in C++. rustls.h uses C specific features, hence the source rules must be put in a src.c file. For example in rustls.h uses the following which is valid C but invalid C++: ```c enum rustls_result { ... } typedef uint32_t rustls_result; // same name as the enum ``` * zero-initialize rust enums using the enum default value, not using 0, in getIntegerLiteral, for example: `rustls_result r = 0` * install rustls.h into rules/rustls using the cargo installation instead of copy-pasting the vendored rustls.h file. `rules/build.rs` takes care of this commit 0cf2216ba2d6bd3d03c3d2233f5209c6a6ea1856 Author: Lucian Popescu Date: Fri Jun 12 22:01:03 2026 +0100 Translate goto label attached to switch case (#189) Translates a label atttached to a switch case, from: ```cpp switch (n) { target: case 0: ... case 1: ... goto target; ``` to ```rs switch!(match n { __v if __v == 0 => 'target: { ... } __v if __v == 1 => { ... goto!('target); } ``` To make this work, I refactored all switch-analysing functions into a single struct, called SwitchArm. Now, VisitSwitchStmt first calls AnalyzeSwitchArms, then iterates over the SwitchArms and generates the corresponding Rust code. Each SwitchArm has a field called label used for generating ` __v if __v == 0 => 'target: {`, i.e. a labeled block in Rust. The changes in libcc2rs-macros/src/switch.rs are minimal. I added support for accepting a labeled block in the switch! macro. commit ae30df3a67c4c61a74b38b3aa73a154fde5d64a1 Author: Lucian Popescu Date: Fri Jun 12 18:56:04 2026 +0100 Delete as u64 from usize-returning offsetof (#191) commit 693eb861bfe6beda251ba160690a0afbd00ab85f Author: Lucian Popescu Date: Fri Jun 12 14:01:42 2026 +0100 Translate size_t/ssize_t as usize/isize (#185) This PR translates `size_t/ssize_t` as `usize/isize` instead of `u64/i64`. All tests and rules are affected by this change. That's why this PR is very big. However the core of this change is quite small. In `rules/` I had to change every `u64/i64` with `usize/isize`. This makes the rules cleaner because we get rid of artificial `as usize` casts in arguments and artificial `as u64` casts in return values. In `libcc2rs/` I changed the arguments of some transalted functions (malloc, fwrite, etc) from u64 to usize. I also added usize/isize branches for VaArg. In `cpp2rust/` the change has 3 parts: 1. I put usize/isize in `addBuiltinTypes`. This adds rules for: `size_t / size_type / __size_t -> usize` and `ssize_t -> isize`. __size_t is an internal type that describes the return value of sizeof 2. `ToString` desugars its type by default, so I gave it a flag to leave scalars alone, and made `search(QualType)` look for the sugared type before the desugared one: ```cpp std::string ToString(clang::QualType qual_type, ScalarSugar sugar = ScalarSugar::kDesugar); ``` This is needed because size_t isn't a real type, just a typedef over an integer. 3. In `Converter`, I added an extra default argument to `Convert`, `ConvertRValue` and `ConvertFreshRValue`: `std::optional implicit_convert_to`. In Clang, both `size_t` and `unsigned long` are represented as the same type, but in Rust, `usize` and `u64` are different types, an implicit cast between the two is not allowed. To solve this, `Convert` is the single point of adding a cast between 2 types that share the same underlying type in C but have different types in Rust, this is decided by `NeedsImplicitScalarCast`. Callers of `Convert` have to pass the optional `implicit_convert_to` in order to trigger the scalar cast. Callers pass the target type, for example `ConvertFreshRValue(rhs, lhs->getType());` in ConvertAssignment, and `Convert` takes care of the explicit Rust call if needed. Binary operations do the same using GetOperandImplicitConversionTarget, which converts both arguments to the same Rust type. commit bcfacafa6b652a4a42871a9525e6add1d683554e Author: Lucian Popescu Date: Fri Jun 12 13:57:44 2026 +0100 Avoid from_fn for initializing integer arrays (#190) from_fn cannot be used in const initializers in Rust. commit 7e3376efc2b51fe260a02d0464fd648735e58fd6 Author: João Gouveia Date: Fri Jun 12 10:48:50 2026 +0100 Add --cxxflags option to cpp-rule-preprocessor (#188) Adds a command-line option for passing additional flags to `cpp-rule-preprocessor`. This enables parsing files that require specific compilation options, such as `-std=c++23`. commit c254f9897a2a8e62f78a1cee45105b995ce8b417 Author: Lucian Popescu Date: Thu Jun 11 08:03:46 2026 +0100 Translate offsetof (#186) commit 5e18dbf5ffe28d5dabb2781619e1c3b4b972e8c9 Author: João Gouveia Date: Mon Jun 8 14:50:04 2026 +0100 Add llvm compile flags to cpp2rust_core (#184) Ensure `cpp2rust_core` is compiled with flags consistent with LLVM's. This fixes linking issues when using LLVM builds with RTTI disabled. commit 84c8c06db9fe8dfa0cff5ee795d33c8e80ad6974 Author: Nuno Lopes Date: Mon Jun 8 09:19:48 2026 +0100 move benchmarks to the testsuite repo commit 61da15320179f3e4df359d44b4bafbf467c4f96d Author: Lucian Popescu Date: Fri Jun 5 08:22:32 2026 +0100 Hoist const variables from goto_block (#182) commit e3cbe23859229b88e19c1a7d17f1d4af622021cc Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu Jun 4 18:45:03 2026 +0100 Remove unreachable code and fix typos in converter.cpp (#181) Three minor correctness/quality fixes in `cpp2rust/converter/converter.cpp`. ## Changes - **Remove dead `std::unreachable()`** — the call after a `switch` block where every case (including `default`) already ends with `return true`/`return false` can never be reached. - **Fix typo in assert message** — `"less that total size"` → `"less than total size"` - **Fix typo in comment** — `"Take supress before"` → `"Take suppress before"` Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> commit c869bc8780425f9a2086fa1564d53c6f0e938ba6 Author: Lucian Popescu Date: Thu Jun 4 18:30:39 2026 +0100 Don't hoist literal arguments of function calls (#180) They alias with no other argument commit 86f0e6968487185afd2d4e58b20b8cd6fe4f7cdc Author: Lucian Popescu Date: Thu Jun 4 17:28:03 2026 +0100 Refactor ConvertGenericCallExpr (#145) ConvertGenericCallExpr is a messy function and it became difficult to add new functoinality on top of it. So I decided to refactor it into: `EmitCall(CollectCallInfo(expr))`. In the near future I plan to add 3 new features on top of it: 1. Don't hoist arguments that are integer literals, they don't alias with any other argument 2. Don't hoist if all passed arguments are local variables and all have non-reference/non-pointer types, local variables don't alias each other 3. Translate mapped (variadic) functions that have a direct mapping in libc, for example fcntl -> libc::fcntl In the new version of ConvertGenericCallExpr, CollectCallInfo is responsible for returning the call information: ```cpp 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; }; ``` And EmitCall is responsible for emitting the hoisted arguments, the callee and the argument list using a CallInfo. This model makes the addition of new strategies, for example `Don't hoist arguments that are integer literals, they don't alias with any other argument`, easier because only the CallInfo fields have to be modified. commit 95772f4294444235c51c8850dee86b01189fd717 Author: Lucian Popescu Date: Thu Jun 4 08:20:55 2026 +0100 Handle array to pointer decay for libc members (#176) commit 0afc40caf1c8db70f8b77d26824ac0c0d46e3f0c Author: Lucian Popescu Date: Wed Jun 3 22:50:06 2026 +0100 Wrap variadic arg in paren before calling .into() (#168) `libc::strlen(dummy as *const i8) as u64.into()` (without paren) errors because we can't write `as u64.into()`, we need to wrap the entire arg in a paren before calling .into(): `(libc::strlen(dummy as *const i8) as u64).into()` commit eebe6a076012e500129d51c27a45d5ffef6fa760 Author: Lucian Popescu Date: Wed Jun 3 22:48:27 2026 +0100 Add rules for dirent, fnmatch, ifaddrs, net_if, pwd, socket, stdio (#167) commit 32e2ee560b3bba647bf6336af24932eff8822343 Author: Nuno Lopes Date: Wed Jun 3 22:47:45 2026 +0100 format commit 580196a3a15a43417de7691767f735f4ff34c288 Author: Nuno Lopes Date: Wed Jun 3 22:43:04 2026 +0100 fix build of test suite commit 8a5df4a85340f1c6bc345ebffc6692f3f19054a3 Author: Nuno Lopes Date: Wed Jun 3 22:32:43 2026 +0100 simplify some string rules commit 8c7a3bea584e894febfb8728d1f8ff74bf6223d6 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Jun 3 22:17:39 2026 +0100 Convert single-character `StrCat` string args to char literals (#178) `StrCat` calls were using single-character string literals in many places (e.g. `">"`-style delimiters and punctuation), which is inconsistent with the intended char-based usage for single codepoints. This updates those call sites to pass character literals instead. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> commit 14256e2388fa219875bce2ccf7edddabaedeb0e0 Author: Nuno Lopes Date: Wed Jun 3 21:22:52 2026 +0100 misc perf improvements and cleanups to libcc2rs commit 9b37f6bd220d03969084cb96527a60009b4fb227 Author: Lucian Popescu Date: Wed Jun 3 18:47:52 2026 +0100 Fix name clash between anon enums across TU (#171) `return std::format("anon_enum_{}", GetLineNumber(enum_decl));` results in name clashes between enums declared in different files at the same line, I replaced that with `return GetNamedDeclAsString(enum_decl);` commit add3c77e7617fcfc8393d68340cca696cb17ae61 Author: Lucian Popescu Date: Wed Jun 3 18:44:58 2026 +0100 Rewrite in6_addr::__in6_u::__u8_addr8 as libc::in6_addr::s6_addr (#177) commit 937ea3a756075cf1b640c92905030835bda23771 Author: Lucian Popescu Date: Wed Jun 3 18:43:26 2026 +0100 Fix clash between switch arm variable and local variable (#172) commit 41115ec142ea33d6f307a0c40da736b909f14c69 Author: João Gouveia Date: Wed Jun 3 16:48:57 2026 +0100 Make Rust type rules standalone functions (#164) Changes the format of the Rust side of type rules to: ```rust fn t() -> TYPE { return DEFAULT-INITIALIZER; } ``` Actual changes to rules: - The initializer for the unsafe rules `t1`, `t2`, and `t3` in `fstream` was changed from `::std::fs::File::open("")?` to `::std::fs::File::open("").unwrap()`. This change was made because the `?` operator requires the function to return a value of type `Result` or `Option`. Additionally, other rules for file-related types already use `::std::fs::File::open("").unwrap()` as an initializer (for example, rule `t1` in `iostream`). - Added a missing generic parameter to the refcount rules `f10` and `f11` in `unique_ptr`. These rules were relying on the struct defined at the top of the type. commit 422455529827e9e98e11034f0e18b6ff94dc180f Author: Nuno Lopes Date: Wed Jun 3 16:22:26 2026 +0100 cmake: remove warning about mismatch LLVM versions that didn't work with our Rust toolchaining commit b99bff9ec3c392bf628fab8a9c9a269c46375d21 Author: Lucian Popescu Date: Wed Jun 3 16:09:32 2026 +0100 Handle char array initialization through `{ string literal }` (#175) `const char var[] = {"string literal"}` is a valid initialization. Add support for it in codegen. commit 7a301ae9e79a194063e4d870c63a6eee30256e3b Author: Nuno Lopes Date: Wed Jun 3 16:08:21 2026 +0100 Upgrade to Rust 1.96 (#174) commit 46225d0ac990268e0f6d1bedee53ad615c090ca3 Author: Nuno Lopes Date: Wed Jun 3 15:37:12 2026 +0100 fix bug in Ptr::with that it was writing back changes for reinterpreted ptrs (#173) commit 1758d27fde273dcf15dc5de7e85d16197f6bb27b Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Jun 3 15:19:56 2026 +0100 Fix `Ord` codegen to use `other < self` in reverse branch (#170) The generated `Ord::cmp` for method-based `operator<` used `other.(other)` in its second branch, which can never evaluate true and therefore suppresses `Ordering::Greater`. This change aligns that branch with the intended reverse comparison (`other.(self)`), matching existing model-specific behavior. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> commit de0ef4bb3715a46730d0a7d3cb37ea2d1bce8635 Author: Lucian Popescu Date: Wed Jun 3 15:18:22 2026 +0100 Handle non-printable ASCII in str and byte arrays (#169) `"\xff"` or `'\xff'` are valid strings/char literals in C/C++. They were not escaped and produced an invalid character that broke the compilation. After I escaped them, I realized that they cannot be translated to `\xff` in Rust `str`'s because str only accepts `\x00 - \x7f`. Byte array accepts `> \x7f`. I changed Ptr::from_string_literal to accept a byte array instead of a str. I also changed GetFmtArg to refuse to create Rust `str` that contains chars `> \x7f` and fallback to GetRawArg that produces escaped byte arrays. commit bf5c961d8a4ed2ea1151a6f7c1db8ba004f55760 Author: Lucian Popescu Date: Wed Jun 3 15:06:32 2026 +0100 Avoid collision between tag and typedef identifier (#166) C allows name collision between the following 2 declarations: ```c struct X {}; typedef enum {} X; ``` The 2 declarations live in different name spaces: 6.2.3 Name spaces of identifiers [...] there are separate namespaces for various categories of identifiers, as follows: 1. the tags of structures, unions, and enumerations (disambiguated by following any) of the keywords struct, union, or enum); 2. all other identifiers, called ordinary identifiers (declared in ordinary declarators or as enumeration constants). `struct X {}` lives in namespace 1 and `typedef enum {} X` lives in namespace 2. We cannot translate both to `X` in the Rust code. We need to disambiguate between the 2 names. I chose to translate them as: ``` struct X {} -> X typedef enum {} X -> X_enum ``` C++ does not have the name space rule for identifiers. I added the `tag->getASTContext().getLangOpts().CPlusPlus` check in DisambiguateAnonymousTag to avoid polluting the C++ generated files. --------- Co-authored-by: Nuno Lopes commit a522fd50d46fa3693e18b668d9dec0bc3af24874 Author: Lucian Popescu Date: Wed Jun 3 15:04:20 2026 +0100 Add codegen support for goto (#163) Depends on #161. See #161 for a more detailed description of how goto is implemented. On the codegen part, this PR emits the goto_block and goto macros when it hits functions that use labels and gotos internally. It also hoists local variables at the start of the function so that all goto_block arms can see and use the local variables. commit 42822be49c5f0d82a84eb989df06b61ccf9e7479 Author: Lucian Popescu Date: Mon Jun 1 21:17:47 2026 +0100 Add goto support in libcc2rs-macros (#161) This PR adds support for writing goto and goto_block in the following form: ```rs let mut ret: i32 = 0; goto_block!({ '__entry: { if n < 0 { ret = -1; goto!('out); } ret = 100; } 'out: { return ret; } }); ``` This is equivalent to the following C code: ```c int f() { int ret; if (n < 0) { ret = -1; goto out; } ret = 100; out: return ret; } ``` goto_block is already used by the switch-with-fallthrough macro internally. I modified its syntax from a list of `label = { ... }` to `label: { ... }` so that rustfmt can format it properly. Then, I added GotoRewriter to rewrite each `goto!('label)` into `{ __s = ; continue '__sm; }`. Whenever GotoStateMachine hits a goto macro, it calls GotoRewriter to generate the following state machine: ```rs { let mut __s: u32 = 0; '__sm: loop { match __s { 0u32 => { if n < 0 { ret = -1; __s = 1; continue '__sm; // <- was goto!('out) } ret = 100; __s = 1; continue '__sm; // fall-through to next arm } 1u32 => { return ret; break '__sm; // last arm: exit } _ => break '__sm, // match exhaustiveness } } } ``` Finally, to support nested goto_blocks, for example a switch inside a goto_block, I added StateMachineNames which gives a unique name to each state machine so that each goto targets the correct state machine. After this PR is merged, I will add the codegen counterpart in the `cpp2rust/` dir. A few limitations about how goto will work: * local variables of a function that contains goto will be hoisted outside the goto_block so that all arms of the goto_block macro can see them. c2rust also does this. All hoisted variables will be default initialized. * the rust compiler cannot prove that all paths of the code inside the loop of the state machine return. I reality the loop always returns because the C behavior is preserved. To suppress the rustc error, I added a panic at the end of the function: `panic!("ub: non-void function does not return a value");` commit d6624e4798477978a2173d1567ef0c79e795c4ec Author: Lucian Popescu Date: Mon Jun 1 21:06:24 2026 +0100 Add check-libcc2rs-macros (#162) commit ca9d64745e958f7cf7d925ed62a450c5322af3ef Author: Lucian Popescu Date: Mon Jun 1 21:04:40 2026 +0100 Translate access to flexible array member (#165) Changed the access pattern of flexible arrays from ```rs let bytes: [u8; 1] = ...; bytes[10] = ... ``` to ```rs let bytes: [u8; 1] = ...; *bytes.as_mut_ptr().add(10) = ...; ``` The original access panics because the bounds are violated, i.e. accessing the 10th element in an array declared with 1 element. The new access does not panic, it preserves the C/C++ behavior. commit b377f2670bc06ba8bde9ba44c07436fc0dcd330f Author: Lucian Popescu Date: Sun May 31 11:24:12 2026 +0100 Fix do while continue stmt (#160) A continue statement inside a `do while` breakes the iteration and re-evaluates the condition. Our previous translation, i.e. ```rs loop { ... if !cond { break } } ``` never re-evaluated the condition after a continue, resulting in an infinite loop. To fix this, use while instead of loop. This ensures that the condition is re-evaluated right after the continue: ```rs let mut __do_while = true; while __do_while || cond { __do_while = false; ... } ``` __do_while makes sure that the first iteration of the loop is always executed. commit 6e8c4ecf2ecd744a8dc2ce3e49a5989e273603e3 Author: Nuno Lopes Date: Wed May 27 22:14:21 2026 +0100 format commit 05372793bfd0c3810f3df1ce7295dc0ffa8f6b48 Author: Nuno Lopes Date: Wed May 27 22:13:11 2026 +0100 test: cleanup tmp file commit 88cb2b12bcab1dab07c8f5e4db246920e93f7401 Author: Nuno Lopes Date: Wed May 27 21:38:33 2026 +0100 minor code simplifications commit 851793031a1c1e418c240cf4583f88c7590077cf Author: Nuno Lopes Date: Wed May 27 21:14:53 2026 +0100 remove a few string copies commit dafea93fbcd086530be3ec6550893a6171529830 Author: Lucian Popescu Date: Wed May 27 17:56:09 2026 +0100 Handle non-uniform C++ st_mtim.tv_sec vs Rust st_mtime (#152) `struct stat` contains a field called `st_mtime`. On Linux, this field is defined as a macro expansion to an internal field: ```cpp struct timespec st_mtim; /* Time of last modification. */ # define st_mtime st_mtim.tv_sec ``` Rust libc does not contain the `st_mtim` field, it only contains `st_mtime`. To solve this tension, I rewrite all encoutners of `::struct stat::st_mtim::tv_sec` as `::libc::stat::st_mtime`. I didn't want to touch the converter for this change, but I could not find a solution that only involved rules or the compat layer in `cpp2rust/compat`. commit cef73cadef76f6cb14f60826a002086c16cd3c76 Author: Lucian Popescu Date: Wed May 27 15:15:36 2026 +0100 Use explicit enum default value instead of Default trait (#157) `Default::default()` cannot be used in const context, for example initializing a global variable. For this reason, switch the initialization of enum from `::default()` to `::EXPLICIT_DEFAULT_VALUE`. commit a571c9d297be0a10863295f9c344a78b18096f58 Author: Lucian Popescu Date: Wed May 27 15:13:13 2026 +0100 Fix assignments that produce values (#158) commit 445e85fd4835def229e1f0c9e22f9a15dd196440 Author: Lucian Popescu Date: Wed May 27 12:10:54 2026 +0100 Fix collision of anonymous structs from different functions (#155) This also simplifies the code because it deletes GetAnonIndex and synthesizeAnonRecordName. Instead it uses GetNamedDeclAsString for generating a stable name for anonymous records. commit e30322fe325532a297d63e994b18a17f7e926ab9 Author: Lucian Popescu Date: Wed May 27 11:35:50 2026 +0100 Handle char* fields declared in libc structs (#156) `char*` fields in libc structs are `*mut i8`. We represent `char*` as `*mut u8`. commit d1fdd455a22dde6400731e669661ca41ad9f8e4f Author: Lucian Popescu Date: Wed May 27 11:34:50 2026 +0100 Add rules for fd_set macros (#150) commit 1220d6f0021186cbfc7609e3737e9280364ed83f Author: Lucian Popescu Date: Wed May 27 11:33:27 2026 +0100 Add support for arch-specific rules (#147) commit ee724c1e3b046f4b9104d09e1f642e1f9834d988 Author: Lucian Popescu Date: Wed May 27 11:29:59 2026 +0100 Add rules for strerror_r and gettimeofday (#146) Both strerror_r and gettimeofday have platform specific signatures. Since the IR is not indexed in git anymore, adding rules for them is easy using a combination of ifdef `__linux__/__APPLE__` + target_os `linux/macos` commit 677287bfb6bd5e7a7260682bf1894ce6d6bd5554 Author: Lucian Popescu Date: Wed May 27 11:28:32 2026 +0100 Avoid name collision of static linkage variables (#154) This PR extends GetNamedDeclAsString to handle variables with static linkage. This fixes: * shadowing between global variables and local variables (tests/unit/static_shadow.c) * name collisions beween static variables declared in different functions (tests/unit/static_function_vars_same_name.cpp) * name collisions between global static variables declared in different TUs (tests/multi-file/static_name_collision) commit a9810eaf72a4a1c7bb4b916184263d8d933ab034 Author: Lucian Popescu Date: Tue May 26 21:54:48 2026 +0100 Add Inc and Dec traits for enums (#153) This implements PostfixInc, PrefixInc, PostfixDec, PrefixDec for all user defined enum types. Instead of generatic more than 30 lines of trait implementations per user-defined enum, I declared a macro in libcc2rs that enums call in order to take care of the actual trait implementation. commit 495817c0c06b36c0354bd33ebf4a1507320b8dcd Author: Lucian Popescu Date: Mon May 25 19:47:56 2026 +0100 [unsafe] Records that struct libc types do not derive Default (#151) commit 7edbd6ea575123900f1751eda762b2f47124de02 Author: Lucian Popescu Date: Mon May 25 19:44:55 2026 +0100 Records that contain function ptr derive Copy in unsafe (#149) commit f796d7400a95b7125948569af095f55a8dd02fb5 Author: Lucian Popescu Date: Mon May 25 19:43:43 2026 +0100 Add rules for inet_ntop and inet_pton (#148) inet_ntop and inet_pton don't have mappings in Rust libc. I declared them extern because they exist in the linked-against libc. commit 28c3c052ad565ebb6ebbd51eb7bbf4b6d5f954e8 Author: Lucian Popescu Date: Sun May 24 11:06:56 2026 +0100 Add dummy definition for referenced but never-defined structs (#143) This handles opaque pointers to user defined forward declared structs. It's allowed to use such pointers, even though the struct is not defined in any translation unit. To implement this, I modified record_decls_ to also keep an extra bool with the following meaning: ``` // record name -> true if a definition has been emitted, false if only // referenced. std::unordered_map entries_; ``` At the end of visiting all TU's, for the record_decls_ that are only referenced (not defined), a dummy struct definition is generated: ```rs #[repr(C)] pub struct opaque { _opaque: [u8; 0], } ``` commit 4decd8925a04098d454810f6e7569ccb5ce25bfd Author: Lucian Popescu Date: Sun May 24 11:02:40 2026 +0100 Add rules for more IP constants (#142) IPPROTO_MPTCP is only defined on Linux. Before #144 the target_os attribute was used in the IR to define symbols that are platform specific in order to keep the IR uniform. But since the IR is not indexed in git anymore, the IR now can be different based on the platform. As such, I deleted the target_os from the IR. I also took this opportunity to replace the previous manual parsing of function attributes in rust with a proper parser, i.e. the cfg-expr crate. In the future I plan to use more attributes, other than target_os, for which I will need proper parsing of the function attributes. For example we also need target_arch, not only target_os. commit 00c76717b8ac10ece8cf2c67a08779bdd910592d Author: Nuno Lopes Date: Sat May 23 21:42:19 2026 +0100 cstring: wrap const-generic string functions in parentheses to bypass glibc macros When using glibc 2.43+ and defining _GNU_SOURCE, the header implicitly enables C23 source features. This activates the new type-generic __glibc_const_generic macros for core functions like strchr, memchr, strrchr, strstr, and strpbrk. Consequently, if these functions are passed a `const char*` or `const void*`, the macros enforce a `const` return type, even when compiling in older C standards. Because our test functions act as wrappers that intentionally return mutable pointers from const arguments, compiling this file was generating warnings. Wrap the function identifiers in parentheses to suppresses preprocessor macro expansion, forcing the legacy behavior. commit 0b8b5c85ed9d8d9a3890c98eef29703a1d80ac20 Author: Nuno Lopes Date: Sat May 23 21:30:27 2026 +0100 optimize rule validation commit 27f9966a4fec27b2bd1873e21fa66754a12ab76f Author: Nuno Lopes Date: Sat May 23 21:28:36 2026 +0100 get rid of check-rules commit 627b1d0829985ed9d2e4c317da1e3dd39a66e9a9 Author: Lucian Popescu Date: Sat May 23 20:48:38 2026 +0100 Remove IR files from git (#144) IR for src and rust is now generated in `build/rules`. I modified rule-preprocessor and cpp-rule-preprocessor to generate the IR files in this new dir, and cpp2rust to read rules from thie dir. I removed `check-rules` from the github runner because `ninja` already generates the IR files. I still kept the `check-rules` target because it's useful for forcing the regeneration of the IR files. commit 2a7fdc8bc610b659b34c259777fdb2fe3eedfa9d Author: Lucian Popescu Date: Sat May 23 07:52:16 2026 +0100 Translate socket functions in C mode (#141) `getsockname`, and the rest of the socket functions that receive a `struct sockaddr*` (`__SOCKADDR_ARG`) parameter, are defined differently in C and C++: ```c int getsockname (int fd, __SOCKADDR_ARG addr, socklen_t *len) #if defined __cplusplus #define __SOCKADDR_ARG struct sockaddr *__restrict #else typedef union { struct sockaddr *__restrict __sockaddr__; struct sockaddr_in *__restrict __sockaddr_in__; } __SOCKADDR_ARG __attribute__ ((__transparent_union__)); #endif ``` `__transparent_union__` is an attribute that allows a function whose parameter has that union type can be called with any of the union's arm types directly, without a cast: ```c struct sockaddr_in in; // this would normally generate an error because &in has type // sockaddr_in* but the argument is declared sockaddr* getsockname(fd, &in, len); struct sockaddr_in6 in6; getsockname(fd, &in6, len); // same tension between sockaddr_in6* and sockaddr_in* ``` `__transparent_union` is only available in C. In C++, an explicit cast is required. In the Clang AST, the transparent union object is created through a `CompoundLiteralExpression(InitListExpr(struct sockaddr_in *))`. We convert the argument of CompoundLiteralExpression in order to get the correct rust code. commit f1fe8739119b05a0ff91fa7d45ee4781e6b86cf4 Author: Lucian Popescu Date: Fri May 22 13:13:19 2026 +0100 [unsafe] Add ctlz, popcountl, mut_overflow builtin rules (#140) commit c374e650929710e15c2eb329aae51ee89de3f3a6 Author: Lucian Popescu Date: Fri May 22 10:30:57 2026 +0100 [unsafe] Rules for arpa_inet, cstdlib, cstring, socket, time, xattr (#137) commit 4dedc9913cfaa9c8275f43c51b51f1594fa3670b Author: Lucian Popescu Date: Fri May 22 08:06:16 2026 +0100 [unsafe] Allow taking the address of malloc, free, realloc, calloc, strdup (#136) commit 4b4983d0a8da46aa0aecede9bdaa01242094fe37 Author: Lucian Popescu Date: Fri May 22 07:56:57 2026 +0100 Use ConvertPointeeType instead of Convert(t->getPointeeType()) (#138) `Convert(t->getPointeeType())` does not handle pointers to incomplete types, `ConvertPointeeType` does. commit d3760d98dfb78b1be0f676803d6feeec073293c9 Author: Lucian Popescu Date: Fri May 22 07:54:46 2026 +0100 Update tests (#139) commit ec2e7f125c70ba3b5bdf6e33c90e6bd8da62fc3f Author: Lucian Popescu Date: Thu May 21 16:17:43 2026 +0100 [unsafe] Rules for locale, netdb, pwd, signal, termios (#134) commit bcf828ba63063e6590840bd14622e3d0108b6fcb Author: Lucian Popescu Date: Thu May 21 16:17:00 2026 +0100 [unsafe] Replace fs::File with libc::FILE (#135) Currently we translate `FILE*` as `*mut std::fs:FILE`. This makes it difficult to translate functions such as feof and ferrror. feof and ferror both peek the internal state of the FILE object. std::fs::File doet not keep an internal state, it propagates the error right at the point of the usage, for example when performing a read or a write. On the other side, `*mut libc::FILE`, which is the direct FFI equivalent of `FILE*`, keeps the internal state, making the translation of feof and ferror easy. Another advantage of switching to `*mut libc::FILE` is that it simplifies all stdio.h rules. For example fwrites becomes a simple call to `libc::fwrite`, instead of the hand-rolled implementation that we have now for writing a buffer into `std::fs::File`. commit 4e59c2238aba069dd11174ba2ae6fb8225cbb105 Author: Lucian Popescu Date: Thu May 21 14:40:51 2026 +0100 [unsafe] Add stdlib.h rules (#131) commit d272b05498fbb9af0c55220ccc098a7b65f6b38f Author: Lucian Popescu Date: Thu May 21 14:39:30 2026 +0100 [unsafe] Rules for poll/select/socket (#133) commit 9389e1c7b8f848d3243fd2cd1f58274bd28f7f15 Author: Lucian Popescu Date: Thu May 21 14:38:48 2026 +0100 [unsafe] Add stat.h rules (#132) commit 44560f3a6bded1b3c243cfe2b7776df130468abe Author: Lucian Popescu Date: Wed May 20 14:10:59 2026 +0100 [unsafe] Add unistd rules (#127) commit 2cacf48faa994f19c682be3a60b08edcd8af6d90 Author: Lucian Popescu Date: Wed May 20 14:06:40 2026 +0100 Add errno rule (#128) commit 6ed76137c2c1c55464016eb29295ebf027672334 Author: Lucian Popescu Date: Wed May 20 09:49:09 2026 +0100 Simplify generated code for compound assignments (#120) commit b6148a761f81dd087b08bd2b2b94a6d6e1f93e1d Author: Lucian Popescu Date: Wed May 20 09:47:31 2026 +0100 [unsafe] More stdio rules (fputc, fputs, puts, fileno) (#126) commit 0b6c778032ddb9d582af15e492d8d0437121a071 Author: Lucian Popescu Date: Wed May 20 09:46:27 2026 +0100 [unsafe] Add more string.h rules (#125) commit fc4c0ff867c8515ad2ccdd00abc29417cd1a0af7 Author: Lucian Popescu Date: Tue May 19 20:46:24 2026 +0100 Don't map user-defined expressions (#123) If the user code redefines strchr, don't apply the translation rules on the user-defined strchr, instead call the user-defined strchr. commit 1f228124399cc09f8faa51490f4322ae03c1e004 Author: Lucian Popescu Date: Tue May 19 14:15:11 2026 +0100 Add paren around cast'ed expression (#124) This avoids compilation errors in the generated code. commit 70f4ab717a1c064042cebdfdba1c9b37bb7f0f6e Author: Lucian Popescu Date: Mon May 18 21:00:44 2026 +0100 Implement ByteRepr for structs with ByteRepr fields (#122) This allows memcpy'ing between structs commit 2f46a1a777aeef58f0ef1c8c45087995511061f2 Author: Lucian Popescu Date: Mon May 18 19:40:36 2026 +0100 Suppress clone in iterator conversion (#112) From, tests/unit/redundant_copy_in_conversion: ```cpp // Comparing const_iterator with iterator forces an implicit conversion of // `end` to const_iterator. The AST shape differs between platforms: // // Linux: const_it == ConvertingCtor(end) // macOS: const_it == ConvertingCtor(CopyCtor(end)) // // The extra inner CopyCtor on macOS would emit a redundant .clone() in the // generated Rust. cpp2rust suppresses it so the output matches Linux. const_it == end ? 0 : 1; ``` This only happens on iterator types because the distinction between iterator and const_iterator. Other containers don't have this issue. commit 60ad5c4e6a06af47222c048d213b22c1aea89f47 Author: Lucian Popescu Date: Mon May 18 19:34:14 2026 +0100 Redefine __FILE__ to use only the basename of the file (#121) commit c856782ca4a23416712d7abf39c793854740d03f Author: Lucian Popescu Date: Mon May 18 14:40:02 2026 +0100 Receive &Ptr instead of Ptr in PtrValueIter::new (#119) ```rs let start: Value> = ...; PtrValueIter::new((*start.borrow()), __count).collect::>() ``` tries to move out of `(*start.borrow())` which does not implement the Copy trait because Ptr does not implement Copy. To avoid the move, change the `ptr` argument of `PtrValueIter::new` to `&Ptr` instead of `Ptr` ```rs pub fn new(ptr: &Ptr, n: usize) -> Self ^ ``` This pattern is not new, it's also used in `Ptr::memcpy, Ptr::memset, Ptr::slice_until`. commit 8d154b4944446be36346d21b48f76a23ba2ed8dc Author: Lucian Popescu Date: Mon May 18 10:55:56 2026 +0100 Guard dereference of possibly std::nullopt (#117) commit 9bfdbf9701e27aa595c2313e350a3863bda5946b Author: Lucian Popescu Date: Mon May 18 10:55:33 2026 +0100 Hoist fwrite/fread args to avoid borrow errors (#118) commit 7c5ca66674873ca07b218163c9bdbd51bbeae367 Author: Nuno Lopes Date: Mon May 18 09:26:37 2026 +0100 disallow return statements in rules' bodies commit 224445aa559b29b12a69ad88dd73604825fef169 Author: Nuno Lopes Date: Mon May 18 09:12:20 2026 +0100 fix build commit c6c311e5c3b36d75b889745402aaddae683328c2 Author: Lucian Popescu Date: Mon May 18 09:03:03 2026 +0100 Map default initializer of translated type (#111) commit 09ec8c4793baed8b1b793a7bbddf3dc8fd2c7f5e Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon May 18 08:23:04 2026 +0100 Fix typo and empty check in converter_lib.cpp (#116) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit a944eb2c00094d72701a139b0e9dad87d173300b Author: Nuno Lopes Date: Sun May 17 21:57:11 2026 +0100 regen commit 9a6c1390e0a6645d9a7a0aa80ce93d27be772f2f Author: Lucian Popescu Date: Sun May 17 21:55:24 2026 +0100 [unsafe] Wrap initialization of global variables in unsafe (#106) The initializer of a global variable might use another global variable. We translate global variables as `static mut`. All usages of `static mut` are unsafe, hence the initializer of a global variable must also be wrapped in `unsafe`. commit b8604601d4d0d889014bc02e6a706f1716a01f09 Author: Nuno Lopes Date: Sun May 17 21:51:50 2026 +0100 Add a new value iterator that yields values rather than Ptrs Cloning Ptr isn't cheap, so this is a nice alternative Optimize a few rules to use this new iterator commit 7fff71e6043690067b10ed9e56db37015faa6fd6 Author: Lucian Popescu Date: Sun May 17 19:33:29 2026 +0100 Fix usage of extern functions accross multiple translation units (#71) commit e867de22f87b567d1e87f1e69137db8d645354be Author: Lucian Popescu Date: Sun May 17 19:16:34 2026 +0100 Fix array to pointer decay in rules arguments (#110) commit 3485d8185d3e1a12f32827403f9c390adda6f218 Author: Lucian Popescu Date: Sun May 17 19:14:13 2026 +0100 Fix bad propagation of AddrOf (#109) commit 7762f0d41d02a20b036b3ebc6b90355f925d60c1 Author: Lucian Popescu Date: Sun May 17 19:12:19 2026 +0100 Assert that both C++ and Rust rules use the same generics (#101) commit 41d38a82b25d8359ee2299c06c61c8a70315e6e2 Author: Nuno Lopes Date: Sun May 17 18:26:56 2026 +0100 Speedup test running by running rustc directly instead of cargo (#113) commit 736d0a9c3cbfedc666d85bcfa34d55b7196cc649 Author: Lucian Popescu Date: Sun May 17 15:35:14 2026 +0100 Avoid implicit autoderef on dereferences coming from rules (#107) Adds the `is_index_base` field in the IR for indexed arguments, for example in `a0[a1]`, `a0` is decorated with `is_index_base = true`. Inside Converter, when a deref is needed and `is_index_base == true`, then explicit autoref is triggered. commit 4e33d39855dc266b88030346476b60ebe7aa282a Author: Lucian Popescu Date: Sun May 17 14:59:18 2026 +0100 [unsafe] Fix void cast for pointer dereference (#104) `(void)(*p)` tried to move the dereference value. To fix this, wrap the `*p` in a `&(...)` because borrowing the value does not move it. commit d2bd60815091b9cdb39836fb709bdc1fb82d8637 Author: Lucian Popescu Date: Sun May 17 14:55:41 2026 +0100 Add support for multi-file tests (#76) This PR adds support for writing multi translation-unit tests in the below format. All multi translation-unit tests go into `tests/multi-file`, one test per directory. `test.expectations` is an optional file that contains one of the following test expectations: no-compile, translation-fail, panic, nondet-result; or it can be empty if the test is successful: ``` tests/multi-file/ └── extern_functions ├── a.c ├── b.c ├── CMakeLists.txt ├── out │ ├── refcount │ │ └── extern_functions.rs │ └── unsafe │ └── extern_functions.rs └── test.expectations ``` I took this opportunity to refactor the logic inside `Cpp2RustTest.py` because the `multi-file` addition did not fit well into the current model. Now `Cpp2RustTest.py` is composed of: * `TestExpectations`: dataclass describing the expected output of the test: `should_panic, should_not_compile, should_not_translate, is_nondet_result, xfail`. For classic tests (ub, benchmarks, unit-tests), TestExpectations is populated from the comment line inside the source file, for multi-file tests, TestExpectations is populated from `test.expectatoins` * `TestContext`: dataclass that defines all test steps (`setup, translate, check_expected, build_cpp, build_rust, run_cpp, run_rust, compare`) and keeps test specific data as member variables, including `TestExpectations` * `Cpp2RustTest`: entry point of lit, contains `execute` (which calls directly into `TestContext`) `getTestsForPath` and `getTestsInDirectory` * free helper functions commit 59d5ee3e09c5fc60bf11d6400b5870074dddde24 Author: Lucian Popescu Date: Sun May 17 14:52:37 2026 +0100 Emit static variables declared inside record (#108) commit cbc163a00ce36ac7020e2717265a3710ad036c0c Author: Lucian Popescu Date: Sun May 17 14:51:52 2026 +0100 Distinguish between empty match and non-match (#103) After d4a10c4, the matcher inside mapper.cpp broke for the following case: ``` template: "T1 & std::vector::back()" instantiated: "& std::vector & std::vector>::back()" Leading & comes from the call inside VisitUnaryOperator ``` The first T1 is matched to the empty string: ``` T1 & std::vector< T1>::back() & std::vector< int> & std::vector>::back() ``` Then, when the second T1 is matched (`int> & std::vector`), it goes to the else branch because it believes that the first T1 was never encountered: https://github.com/Cpp2Rust/cpp2rust/blob/f5f0e929e59ced0ded143d1144737d4bbdc48c89/cpp2rust/converter/mapper.cpp#L253-L260 The fix is to replace the captured `std::vector` with `std::vector>` so that empty matched string can be distinguished from non-matched stirng. commit eb3bffe08ffd31a9b4a58b520d11c303f73f0cc7 Author: Lucian Popescu Date: Sun May 17 14:49:49 2026 +0100 Only emit mem::zeroed() on POD types (#105) The previous check, i.e. `ctx_.getSourceManager().isInSystemHeader(record->getLocation())`, was too permissive and also matched against types such as `std::vector` which are not POD and cannot be initialized using `std::mem::zeroed()` commit 222c66b5534bd6a9cf2d5dee040d9f9db7e72c48 Author: Lucian Popescu Date: Sun May 17 14:49:00 2026 +0100 Support implicit conversion in std::vector constructor (#102) Previously, f39 was a hack to support implicit cast in constructing a vector over the more generic f37 rule. I changed f37 to support implicit casts so that can write: ```cpp uint32_t src[3] = {1, 2, 3}; std::vector v2(src, src + 3); ``` commit f5f0e929e59ced0ded143d1144737d4bbdc48c89 Author: Lucian Popescu Date: Fri May 15 13:39:11 2026 +0100 Add doubly linked list test (#100) commit a578fbcea2021a9fab5d99b4019806ef8c395707 Author: Nuno Lopes Date: Thu May 14 18:48:50 2026 +0100 Simplify test CI (#99) commit f1060aabf8fa4e3c8d28dc7cc231d25776aa9c77 Author: Nuno Lopes Date: Thu May 14 18:28:46 2026 +0100 compile rule preprocessor in release mode commit 3858b40a22bc8cf04321562dc62343cce6fd0829 Author: Nuno Lopes Date: Thu May 14 16:41:33 2026 +0100 add --release commit 9c7b55f16dd69615b2675aced8fc56f978b5e0dc Author: Nuno Lopes Date: Thu May 14 16:38:06 2026 +0100 Simplify CI for format checking (#98) commit 6e2c3e8cc9dd8d2aa1157a0dcdc0a8a665295d08 Author: Lucian Popescu Date: Thu May 14 14:09:58 2026 +0100 Add fwrite in libcc2rs to allow taking its address (#95) commit f24ca84f7182595b07aed7f7fed2886524d324cb Author: Nuno Lopes Date: Thu May 14 11:58:37 2026 +0100 Fix cmake Rust concurrency issues (#97) commit 5d13dd85eadc8e53899ee4e7ec502d30e7e28423 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed May 13 21:52:26 2026 +0100 Replace `ptr = ptr.offset(1)` increment patterns with in-place pointer arithmetic (#96) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 832ad2f9ceb972521d0ec0ac12a97a927de0a84a Author: Lucian Popescu Date: Wed May 13 21:14:10 2026 +0100 Run clippy in ninja format (#94) commit b106d2fc761a3b0e6e19e7c729ee1e71f956828d Author: Lucian Popescu Date: Wed May 13 21:13:24 2026 +0100 Delete stale ir_src.json in check-rules (#93) commit e047a98589f88d8e37e542941164133d4d4d0f11 Author: Lucian Popescu Date: Wed May 13 21:11:58 2026 +0100 Support src.cpp and src.c in the same rules dir (#92) For C string functions, C++ defines const overloads. For example, in C, strchr is defined as: ```c char *strchr(const char *s, int c); ``` In C++, it's overloaded as: ```cpp const char *strchr(const char *s, int c); char *strchr(char *s, int c); ``` Writing `char *f5(const char *a0, int a1) { return strchr(a0, a1); }` would result in the following error: ``` error: cannot initialize return object of type 'char *' with an rvalue of type 'const char *' 6 | char *f6(const char *a0, int a1) { return strchr(a0, a1); } ``` For this reason, parse both `src.cpp` and `src.c` in a rules dir. `src.c` keeps C specific rules and `src.cpp` keeps C++ specific rules. The function names in the 2 functions cannot collide, cpp-rule-preprocessor checks against colliding names. commit f1a308ebf9846c3979e019de8cab8d0070627c39 Author: Lucian Popescu Date: Wed May 13 17:35:48 2026 +0100 Add rules for socket_type enum values (#86) This adds support for translating socket_types values: SOCK_STREAM, SOCK_DGRAM, SOCK_CLOEXEC (Linux specific), SOCK_NONBLOCK (Linux specific). The rules for CLOEXEC and NONBLOCK are only visible on Linux using the new IR field called `target_os`. On the C++ side, the rules are guarded with a macro: ```cpp #ifdef __linux__ int f4() { return SOCK_CLOEXEC; } int f5() { return SOCK_NONBLOCK; } #endif ``` On the Rust side, they are guarded using the target_os attribute: ```rs #[cfg(target_os = "linux")] unsafe fn f4() -> i32 { libc::SOCK_CLOEXEC } #[cfg(target_os = "linux")] unsafe fn f5() -> i32 { libc::SOCK_NONBLOCK } ``` `rule-preprocessor` parses the attributes of a function and adds `target_os = linux` in the IR. `translation_rule.cpp` skips the rule if the current OS is not the same as the OS declared by `target_os`. commit 3ccf11aeeec718b7ef009110d2a51b2457a81762 Author: Lucian Popescu Date: Wed May 13 17:33:34 2026 +0100 Add rules for malloc, realloc, and free for unsafe model (#91) commit 1b7c31af7a68d68cc97db888ed2d5da67fff047c Author: Lucian Popescu Date: Wed May 13 17:32:29 2026 +0100 Stop src.cpp parsing if rule is in bad format (#90) The expected format of a C++ rule is: single return statement. commit 8accdc440a63f6a910d78f303996dd8d0651a0bc Author: Lucian Popescu Date: Wed May 13 10:31:37 2026 +0100 Add translation rule for stdin (#85) commit e90e507c5737a475cefe9a60ce2b00a431739709 Author: Lucian Popescu Date: Wed May 13 10:31:05 2026 +0100 Emit zeroed() for libc structs without default (#84) commit 0df0690f34ae5393ec41743eae4c96795a5904a8 Author: Lucian Popescu Date: Wed May 13 10:01:33 2026 +0100 Add generic pointer implementation for VaArg and VaArgGet (#82) This allows passing pointers to user defined structs and to non-primitive objects to variadic functions. commit a42298ca92131ca78d90d6b3a263480272abb17e Author: Lucian Popescu Date: Wed May 13 09:58:18 2026 +0100 Assert when Rust side of rule is missing (#88) commit febb03e7b143574f0cad7b8d55c22d2f361c9ee6 Author: Lucian Popescu Date: Tue May 12 22:00:36 2026 +0100 Translate function pointers passed in variadic arguments (#83) In unsafe, the function pointer is first converted to void* when passed to a variadic function. Then, inside the variadic function, when calling `va_arg(ap, fn_ptr)`, the function is first retrieved as void*, then transmuted to function pointer. The `function pointer -calling variadic function-> void*, void* -va_arg(ap, fn_ptr)-> transmute` is needed so that we don't add the unsafe transmute in libcc2rs. In refcount, this dance is not needed. VaArg and VaArgGet are implemented for FnPtr safely. commit f10a45c1d2a4571e5649ca69056febabdf0ecc03 Author: Lucian Popescu Date: Tue May 12 20:59:17 2026 +0100 Fix test failures on master (#87) commit 08f7f4451992fda6356801fde954bf5fd74dfe52 Author: Lucian Popescu Date: Tue May 12 18:40:50 2026 +0100 Fix args: &[VaArg] name collision (#80) `args` can create a name collision with a local variable named `args`. Rename the `args` argument to `__args` commit 6ec83761999ccda0ed4488bfe868f41a1d595e75 Author: Lucian Popescu Date: Tue May 12 18:39:25 2026 +0100 Use &raw to get address of globals (#77) Globals are translated as `static mut`. Taking the address of globals using `&T as *const T` is unsafe and not allowed in const initializers. `&T` creates a reference to the `static mut`, and the Rust compiler cannot prove the soudness of this operation. To overcome this, use `&raw const T` instead of `&T as &const T`. `&raw` simply computes the address without creating an immutable reference. commit 179fc214d279bb4a7dd51b5f1fcad3bba6610d10 Author: Lucian Popescu Date: Tue May 12 18:38:54 2026 +0100 Don't suppress errors in the original src.cpp file (#79) commit 40b2160d420deeee47d8c96f17b4ffca3b7b68ce Author: Lucian Popescu Date: Tue May 12 15:52:48 2026 +0100 Fix translation of nullptr creation and comparison (#81) This PR translates default pointers using `GetDefaultAsString`, resulting in `Ptr::::null()` being generated instead of `Default::default()`. This is useful for contexts where `Default::default()` cannot be correctly deduced as a `Ptr`. Pointer comparisson is also fixed to allow C NULL comparissons such as `p != NULL` or `p != 0`. commit 6c3eae7f427d8094b3dabbbf0902c3219c7b482c Author: Lucian Popescu Date: Mon May 11 21:58:33 2026 +0100 Translate explicit bool cast (#78) commit f8c68e8cebe9b042e14f5070f1e6b5ea0ded3425 Author: Nuno Lopes Date: Mon May 11 21:16:59 2026 +0100 fix build with LLVM 23 commit c852e411646d36424d353fa7ace91eeda3f16a13 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon May 11 18:55:32 2026 +0100 Replace single-char string literals in stream output with char literals (#74) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit e8265484731b33087e3c3e59b11697386d05764e Author: Lucian Popescu Date: Mon May 11 17:44:52 2026 +0100 Translate globals without inits (#73) commit f0334a4d897c282d07ecc2e16955237681e9e8e7 Author: Lucian Popescu Date: Mon May 11 17:41:31 2026 +0100 Run ruff on Cpp2RustTest.py (#72) commit 2b6b9518ca7c94e964d581f67eeaaadd8bedbf69 Author: Lucian Popescu Date: Mon May 11 16:36:29 2026 +0100 Add new rules for brotli, socket and ip (#59) This PR adds more translated symbols for brotli (enum values + functions), socket (enums + macros), and ip (enums). This is not straight forward because symbols like IPPROTO_TCP are defined as enum on Linux and macro on macOS. To handle this, I modified cpp_rule_preprocessor + mapper to match macros and VisitIntegerLiteral to emit the translated macro. Because on one platform the symbol is an enum and on the other it's a macro, the AST of the translated program is also different. To be able to generate the same code on both platforms, I use `IsCastRedundantInRust` to normalize `(libc::IPPROTO_TCP as i32)` (Linux) and `libc::IPPROTO_TCP` (macOS) to `libc::IPPROTO_TCP`. This uses the fact that `libc::IPPROTO_TCP` is already an i32 in Rust, so the cast is redundant. commit 83950a170348f3214f3a0ef4f5e5db7734e047ce Author: Lucian Popescu Date: Mon May 11 16:27:34 2026 +0100 Don't preprocess C++ rules on every cpp2rust rebuild (#70) commit c5f97b005b9f977dcccb543b8d88b78fa2c69c85 Author: Lucian Popescu Date: Mon May 11 10:11:18 2026 +0100 Setup rust for C++ code analyzer (#68) `ninja all` preprocesses the rules which requires the Rust environment to be set. commit 52ae5b8218a96b83134ef4fa1a9783eb4ec60ea7 Author: Nuno Lopes Date: Sun May 10 21:34:59 2026 +0100 Optimize preprocessed src json file format commit 6fe327981961ebe353f486cdf39f8f07136be964 Author: Nuno Lopes Date: Sun May 10 21:20:58 2026 +0100 cmake: fix rust toolchain dependency commit e986919ba0979f71955c49d52b90b97cc86ec859 Author: Lucian Popescu Date: Sun May 10 21:11:35 2026 +0100 Parse src.cpp rules at build time (#61) Fixes #55 Each src.cpp from the `rules/` dir is now parsed at build time, instead of on every invocation of cpp2rust. This gives a 5-8x speedup in running the unit tests in the Github runner. Translating a simple unit test like `assert.cpp` went down form 6 seconds to 0.1 seconds. The new `cpp-rule-preprocessor` binary from `cpp2rust/` is responsible for parsing one `src.cpp` at a time and to generate a `ir_src.json` in the same dir as the `src.cpp` file. The format of the json file is: `f / t: { "to_string": }`. All `src.cpp` files are parsed in parallel, CMakeLists takes care of that. The `check-rules` target is now responsible for parsing the Rust rules and the C++ rules. The IR for `src.cpp` is not indexed in git. The IR for the Rust rules remains unchanged. I ported the clang AST matcher form translation_rule.cpp into the new file cpp_rule_preprocessor.cpp. As a result, translation_rule.cpp is only responsible for loading IR JSONs. commit 8a3cc6b8d4ee7f6088d1c1d114a05dcacb25eab1 Author: Lucian Popescu Date: Sun May 10 18:24:49 2026 +0100 Always translate global objects as static mut (#67) This is a continuation of https://github.com/Cpp2Rust/cpp2rust/pull/65: > This is incomplete, it only solves part of the problem. > > In Rust, `static` global variables require `Sync` so that multiple threads can take an immutable reference to the `static` storage, `static mut` global variables don't require `Sync` because it's by itself unsafe to access the `static mut` storage. > > This PR solved the problem for `static` global struct objects that contained raw pointers. Raw pointers are not `Sync` in Rust so any struct containing them needs to implement the `Sync` trait. What this PR does not solve is global arrays containing pointers in `static` storage, for example `[*const T; N]`. > > One solution would be to translate the const global variables as `const` instead of `static`. However `const` global variables don't have a stable address in the final binary. Other solution would be to wrap the array in a newtype, but this makes later usages of the array harder in the codegen. > > None of the two above solution is complete. For the moment we can translate all global variables as `static mut` instead of `static` so that we don't have to implement `Sync` for them. By construction, the generated code is guaranteed not to modify the global variable, even if it's declared as `static mut` instead of `static`. > > This only touches the unsafe model. commit cd2b6f845a84ec5d60d4d70974aafe5d843ef645 Author: Lucian Popescu Date: Sun May 10 18:02:18 2026 +0100 Use default literals instead of Default::default() in const initialization (#66) `Default::default()` is not a const function, hence it cannot be used to initialize global objects such as `pub static mut static_outer: Outer = Outer::default()`. To overcome this limitation, emit the default literal for the object, for example: ```rs pub static mut static_outer: Outer = Outer { p1: std::ptr::null_mut(), p2: std::ptr::null(), arr: [std::ptr::null_mut(); 3], cp: std::ptr::null(), pp: std::ptr::null_mut(), inner: Inner { v: 0_i32, name: std::ptr::null(), }, x: 0_i32, fn_: None, }; ``` commit 8e897918d843094361a4b6dc4ff394aad57e534d Author: Lucian Popescu Date: Sun May 10 12:12:00 2026 +0100 Translate default pointers in globals (#63) Previously, we used `Default::default()` for initializing pointers to null. This is not compatible with initializing global variables because they are declared in static context. In static context, only const functions can be used. `Default::default()` is not const, `std::ptr::null()/null_mut()` are const. commit 348daa19ca7a120de1da2d155668ee906473f2bb Author: Lucian Popescu Date: Sun May 10 12:10:52 2026 +0100 Allow initialization of enums in global context (#64) Previously, in C, enums were initialized with the `Enum::from(X as i32)` pattern because enum variants are represented as int and the type of variable is represented as enum. The From trait is not allowed in global context because it's non-const. This PR fixes this by short-circuiting `ConvertIntegerToEnumeralCast` to emit `X` instead of `Enum::from(X as i32)` commit 1565f4078c97a21b2fab8efb6b69d3740d6436f2 Author: Lucian Popescu Date: Mon May 4 21:00:07 2026 +0100 Delete useless rules dependency in lit (#62) commit 85b5c9107a5ac69040cf1d0910a1d28c190d9203 Author: Lucian Popescu Date: Mon May 4 16:48:23 2026 +0100 Avoid implicit autoref in the unsafe model (#60) commit d27585ad9ce7f53d5f8c1f775376e2dc4b594146 Author: Nuno Lopes Date: Sun May 3 17:54:47 2026 +0100 split Rust toolchain data to cmake/rust-toolchain.cmake file commit 805aa7d7270a2659382abb58ca399de44654fce5 Author: Lucian Popescu Date: Sun May 3 16:44:31 2026 +0100 Wrap BinaryOperator in integer cast in C mode (#58) In C, comparison and logical binary operations always produce an int where a bool is expected. For this reason, wrap binary operators in `(... as i32)` if the type of BinaryOperator is int. ConvertIntegralToBooleanCast will later take care to convert the integer back to boolean. commit e824ccb2da34063faaddc26ac49d3c369b69e55e Author: Lucian Popescu Date: Sun May 3 11:57:24 2026 +0100 Disable verbose logging by default (#50) Logging can be re-enabled using the `--verbose` option commit b1319115a3035479545fa27337e6c184ef77c3c3 Author: Lucian Popescu Date: Sun May 3 11:48:48 2026 +0100 Translate implicit casts from integer, pointer, and enum to bool (#56) commit 20461881b3d75a064e150ede5f96d430dbac7b4e Author: Lucian Popescu Date: Sun May 3 09:03:35 2026 +0100 Translate pointer diff (#57) commit 9e186f7d1802f0907da9402f23ef5b6d498b51c7 Author: Lucian Popescu Date: Sat May 2 12:49:54 2026 +0100 Silent reserved keyword and include next warnings (#52) commit 56fe249ff23863061514dcad0d002a26ed71d027 Author: Lucian Popescu Date: Fri May 1 21:13:24 2026 +0100 Translate enums that have integer type instead of enumerator type (#54) In C, as opposed to C++, enums have integral type rather than enumerator type. commit d4a10c419022548fa660596cfecbcaa6f9437bea Author: Nuno Lopes Date: Fri May 1 21:11:21 2026 +0100 Optimize rule loading procedure and scan expr maps sequentually via hashing (#51) commit e9409d86dc1db608654f641d820d46fc27d99014 Author: Lucian Popescu Date: Fri May 1 18:18:00 2026 +0100 Guard void pointer conversion (#53) Only accept `void* -> pointer type` expressions for now. In the future we will add support for `void* -> integer type` as well commit 7d2e7fe36d9a4de346ef6e4f70976bb3ff0e1749 Author: Nuno Lopes Date: Fri May 1 10:37:28 2026 +0100 Code optimizations (#49) commit da6d32409899bb46a17f062ff8d0ff885beb7304 Author: Lucian Popescu Date: Fri May 1 08:08:34 2026 +0100 Translate void casts (#47) commit 794f3e198bcf68809192f7759e0b3d56101ecc70 Author: Nuno Lopes Date: Thu Apr 30 21:13:15 2026 +0100 Merge a double call to Mapper::Contains and ::Maps together (#48) commit a8851c5749b3be3bd114a4197b847d41e4cadba3 Author: Lucian Popescu Date: Thu Apr 30 15:14:03 2026 +0100 Handle typedef'ed anonymous structs (#45) commit 9c20b3125b185f438ad6a983ba7be2c02894e4cc Author: Lucian Popescu Date: Thu Apr 30 15:12:57 2026 +0100 Translate anonymous enums (#43) commit f1db78833715826070b687d1bf7e6bdf1e5b7dea Author: Lucian Popescu Date: Wed Apr 29 21:41:43 2026 +0100 Translate TypeTraitExpr (#44) commit c2df19d97f25df431a69fa82c6027dfbabc11f7b Author: Lucian Popescu Date: Wed Apr 29 20:59:54 2026 +0100 Translate string literals in C and C++ (#36) This PR adds support for declaring and using const/non-const pointers and arrays containing string literals. VisitStringLiteral now always generates a string literal (`[u8; N]` in unsafe, `Box<[u8]>` in refcount). It's the job of `VisitImplicitCast::CK_ArrayToPointerDecay` to convert the string literal to poiner if it's necessary. In C string literals are `char[]` and in C++ they are `const char[]`. `VisitImplicitCast::CK_NoOp` handles the conversion between const and non-const char pointers. Besides always generating a string literal instead of pointer, VisitStringLiteral also pad with null bytes the following code: `char array_bigger_than_string_literal[10] = "1"`, effectively becoming `"1\0\0\0..."`. commit 2cab2a4f3c709b93a36791e682d52cd037b15aa9 Author: Lucian Popescu Date: Wed Apr 29 20:57:25 2026 +0100 Translate StmtExpr (#42) commit 4e526097beac3e6564c16121eda807f9b984b3c5 Author: Nuno Lopes Date: Wed Apr 29 18:45:06 2026 +0100 Avoid double hash lookup in Ptr::from_string_literal commit b6ffa548a7006f9be547be489498f0ed2b113bf9 Author: Lucian Popescu Date: Wed Apr 29 13:25:13 2026 +0100 Translate switches containing fallthrough (#38) All switch tests pass now. This PR translates switches containing fallthrough using the new `switch!` macro defined in `libcc2rs-macros`: ``` switch!(match { [if ] => { /* body; may contain break or continue */ }, ... _ => , }); Desugars to a goto_block! with a synthetic dispatch arm prepended. goto_block! { '__dispatch => { match { => { __s = 1; continue '__sm; } ... _ => break '__sm, } }, '__c1 => { /* body_1 with `break` rewritten to `break '__sm` */ }, ... '__cN => { /* body_N with same rewrite */ }, }; __sm is the inner label used to describe the state machine insinde goto_block. See goto_block! for more info. ``` It's necessary to translate the switch into a goto because later we will add support for jumping between switch arms using goto. Below is an example of how the `switch!` macro expands: ```cpp int fallthrough_one(int x) { int r = 0; switch (x) { case 1: r += 10; case 2: r += 20; break; default: r = -1; break; } return r; } ``` Will be translated as: ```rs switch!(match x { v if v == 1 => { r += 10; } v if v == 2 => { r += 20; break; } _ => { r = -1_i32; break; } }); ``` Which will expand into (see comments attached to each arm): ```rs { let mut __s: u32 = 0; #[allow(unreachable_code, unused_labels)] '__sm: loop { match __s { // First arm is the dispatch arm. It decides which is the first state: [1u32, ...] 0u32 => { #[allow(unreachable_code)] { { #[allow(unreachable_patterns)] match x { v if v == 1 => { __s = 1u32; continue '__sm; } v if v == 2 => { __s = 2u32; continue '__sm; } _ => { __s = 3u32; continue '__sm; } _ => break '__sm, } }; __s = 1u32; continue '__sm; } } 1u32 => { // First real arm, it contains the body of the original arm + fallthrough to the following state, i.e. 2u32 #[allow(unreachable_code)] { { r += 10; }; __s = 2u32; continue '__sm; } } 2u32 => { // Second real arm, inside the body there is a break statement that stops the fallthrough #[allow(unreachable_code)] { { r += 20; break '__sm; }; __s = 3u32; continue '__sm; } } 3u32 => { // Default arm from the original match, the last arm breaks the state machine instea of continuing #[allow(unreachable_code)] { { r = -1_i32; break '__sm; }; break '__sm; } } // This is here only for match exhaustiveness, it's never used _ => break '__sm, } } }; ``` commit 077bee63faa0adad439a9627e55932296d7ed2ad Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Apr 29 09:39:30 2026 +0100 Avoid unnecessary heap allocations for short-lived strings (#41) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 98290092d7cc82bcf64f168788eb73e37ae070e9 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Apr 29 08:21:46 2026 +0100 Eliminate double-lookups in map/set operations (#39) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit b22b435aff74ecc9f1ba971e398bcd5f0aa5c62e Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed Apr 29 08:20:07 2026 +0100 Remove unnecessary uses of regexes (#40) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit b022b56585eb1d97e14dcc4cabbb771c91ee3ef6 Author: Lucian Popescu Date: Tue Apr 28 21:36:57 2026 +0100 Translate unions in unsafe (#32) C/C++ unions are translated as Rust unions in unsafe. They manually implement default with `std::mem::zeroed` since unions cannot derive Default Each struct and union is decorated with the `repr(C)` attribute to make sure that the C layout is preserved. We need this because Rust is free to reorder fields for optimal struct layout commit 5a06f35a92a1638305301ee878d34354c84a5b31 Author: Lucian Popescu Date: Tue Apr 28 21:35:46 2026 +0100 Cancel old runs when a new commit is pushed (#37) This helps not wasting workflow resources commit c8b5585cd722caaaeab60c93d45d65eb0b172477 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue Apr 28 16:29:57 2026 +0100 Eliminate spurious string copies (#35) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 5915411519885c9210549fe22e29844e0ceceed5 Author: Nuno Lopes Date: Tue Apr 28 15:54:25 2026 +0100 don't run CI twice on branches with open PRs commit 6c95279655ee451705ffcb9b80d5d750668448a1 Author: Lucian Popescu Date: Tue Apr 28 15:08:50 2026 +0100 Convert delimiters using RAII semantics (#34) PushBrace, PushParen and PushBracket push the open delimiter in the constructor and the closing delimiter in the destructor. They replace the following very common pattern: ```cpp StrCat(token::kOpenParen); ... StrCat(token::kCloseParen); ``` with: ```cpp PushParen push(*this); ... ``` commit 2fac5a2167758cfe76f6586e757c12232e666cf0 Author: Lucian Popescu Date: Tue Apr 28 15:00:04 2026 +0100 Translate anonymous structs (#33) commit 141b3aacffc022e4071f9d8a1b9afedd571b434c Author: Lucian Popescu Date: Tue Apr 28 14:33:28 2026 +0100 Translate switch with default case in the middle (#30) This PR handles code as: ```cpp // tests/unit/switch_default_middle.cpp int default_middle(int x) { int r = 0; switch (x) { case 1: r = 1; break; default: r = 99; break; case 2: r = 2; break; } return r; } ``` In rust, the default arm has to be the last arm, otherwise, arms following the default one will be ignored, making this assertion fail `assert(default_middle(2) == 2)`. Hence, the rust translation becomes: ```rs match x { v if v == 1 => { r = 1; } v if v == 2 => { r = 2; } _ => { r = 99; } } ``` This is achieved by iterating over all top-level case statements in VisitSwitchStmt and deferring the translation of the default arm until every othter arm is translated. A top-level case statement is defined as: ```cpp switch (x) { case TOP_LEVEL_STMT_1: case NOT_TOP_LEVEL_STMT_1: default: // not top level as well ... break; case TOP_LEVEL_STMT_2: case NOT_TOP_LEVEL_STMT_2: ... break } ``` Furthermore, chains of case statements that contain a default statement are reduced to default, effectively making the above code translate as: ```rs match x { v if v == TOP_LEVEL_STMT_2 || v == NOT_TOP_LEVEL_STMT_2 => {} _ => {} // TOP_LEVEL_STMT_1, NOT_TOP_LEVEL_STMT_1, default was reduced to default } ``` commit 8f2144baaaf19e756c1392c904b8b0ab3a97ffd7 Author: Lucian Popescu Date: Mon Apr 27 19:13:51 2026 +0100 Handle comparisons against translated functions (#3) This PR adds support for translating comparisons against functions that have translation rules `fn_ptr == translated_rule`, for example in `fn1 == fread` in fn_ptr_stdlib_compare.cpp: ```rs fn1 == FnPtr::new) -> u64>( libcc2rs::fread_refcount) ``` commit 4d6bc4239177d232834afd60d64505b312b577e3 Author: Lucian Popescu Date: Mon Apr 27 18:09:40 2026 +0100 Add union tests (#31) commit 3116d544e94a1c378aea98453f04a2cd32b7fdac Author: Lucian Popescu Date: Mon Apr 27 09:19:00 2026 +0100 Track current break target using a stack (#29) commit 61425b074abf4169f383813e1accea16e8f57297 Author: Lucian Popescu Date: Fri Apr 24 09:23:17 2026 +0100 Add switch tests (#28) This adds switch/case tests. Not all tests compile, but that's fine, they will be fixed in future PR's. Some tests are marked with the new ```cpp // translation-fail ``` marker that's aded to tests which break the translator commit e0d97d2d4b85805eae9e5fd8d190ab75d493ff06 Author: Nuno Lopes Date: Thu Apr 23 08:33:33 2026 +0100 minor code simplification commit 1884a7235c605817680ed7c62c214fcde85c5e85 Author: Nuno Lopes Date: Thu Apr 23 08:27:45 2026 +0100 CI: don't attempt to commit formatting fixes to other branches commit f8e7b8da620776728f7be645c0809edbb2b07099 Author: João Gouveia Date: Wed Apr 22 19:45:08 2026 +0100 Make synthetic rule template instantiation compliant with LLVM 23 (#25) commit f90d9230c7256913698f5c5e0a9b11e84ec717e6 Author: Lucian Popescu Date: Wed Apr 22 11:22:35 2026 +0100 Fix random segfaults in Github runner (#24) commit 58cd365556788bdacb3aaab59a14002acc8b7c78 Author: Lucian Popescu Date: Wed Apr 22 09:05:02 2026 +0100 Add support for translating C structs (#10) commit b8adec630b332359af701538f6026dfb04b15270 Author: Nuno Lopes Date: Tue Apr 21 21:33:12 2026 +0100 fix bug in Ptr subtraction involving mixed (non)reinterpreted case commit ab11adaf30b704fcb51c094d21fcbfab68de6951 Author: Nuno Lopes Date: Tue Apr 21 21:12:55 2026 +0100 optimize a few std::vector refcount rules commit e216c0f59e69a138f38a754a6fca183acecb1efb Author: João Gouveia Date: Tue Apr 21 14:45:11 2026 +0100 Add support for non-instantiated template rules (#14) Adds support for writing translation rules without having to manually create placeholder classes to instantiate them. For a rule as follows: ```cpp template void f1(T1 first, T1 last) { return std::sort(first, last); } ``` We must determine what the name `std::sort` refers to. First, the rule's template parameters are inspected, and a set of template arguments is synthesized. Each synthesized argument is either an empty struct or a constexpr with an appropriate type, depending on the kind of the corresponding template parameter. These template arguments are then used to instantiate the rule's function signature, which yields the non-dependent type of each call argument. These types are used to synthesize a set of expressions corresponding to call arguments. Since the synthesized template arguments may not respect all of the constraints of a given template, it's not possible to directly instantiate the body of the rule and simply fetch the returned value. For instance, `std::sort` expects its arguments to be iterator-like, and it cannot be specialized with an empty struct `struct T1{};`. Afterwards, the name is looked up. For each overload, any implicit template arguments are deduced using the previously obtained synthesized call arguments. The deduced template arguments are combined with any explicit template arguments and used to instantiate the overloads' declaration. Finally, overload resolution is performed, and the best match is selected. The only rules that are not yet handled are those that receive a lambda expression as a parameter (e.g. the rule `algorithm/f6`), as it is not possible to explicitly write the type of such expressions. Since rule matching is performed by printing rules and comparing strings, I believe that a possible solution would be to print lambda expressions as if they were regular function pointers. This would eliminate the need to write duplicate rules for functions receiving function pointers and functions receiving lambda expressions, but it would also preclude us from writing rules that handle lambda expressions and function pointers differently. commit b21105585fbc887256747b1ff8005f25fedf848b Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue Apr 21 09:44:25 2026 +0100 Format workflow: auto-fix and commit formatting/lint issues (#22) The format workflow only reported issues without fixing them, and skipped all `.rs` files under `tests/` that aren't part of a Cargo project. -------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 956866e460efaac04ba76c93fe05e3ae1a6a55d6 Author: Nuno Lopes Date: Mon Apr 20 16:44:24 2026 +0100 Upgrade to Rust 1.95, that matches our LLVM 22 dependency (#21) commit 9977de0f45769cc4cf0832a065fa4a0157db4cc2 Author: Nuno Lopes Date: Mon Apr 20 15:11:48 2026 +0100 format.yml CI flow: use a 3rd party action to setup rust commit 153b6aa94b6470f4797e2c6c8007916b2ab171ac Author: Lucian Popescu Date: Mon Apr 20 15:08:48 2026 +0100 Translate function pointers and lambda (#7) Translate function pointers and non-capturing lambdas as Option in unsafe and FnPtr in refcount. * Address is computed inside using the `FnAddr` trait * Record original function pointer + current cast * In unsafe function pointers + casts are translated using: `Option` + `std::mem::transmute` * In refcount function pointers + casts are translated using: `FnPtr` + `FnPtr::cast` commit 8b8295f5ce5064586dbd6c54e4cf99a2b34566cf Author: Lucian Popescu Date: Mon Apr 20 14:39:30 2026 +0100 Fix detection of va_list type (#20) On macOS `va_list` is typedef'ed to `char*`, on Linux is typedefed to `__va_list_tag[1]`. Traverse the TypedefType/AdjustedType in order to check if a type is va_list instead of checking against the Linux specific `__va_list_tag`. commit a798fcae2b99fdb84f5ba6611b8ad6fd1c822689 Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon Apr 20 11:16:04 2026 +0100 Reduce avoidable string allocations in converter helpers and mapper lookups (#19) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit c6cc2d8bf1634b6afb964c5670c8af0e7844b73d Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon Apr 20 09:08:38 2026 +0100 Format `KW` macro keyword invocations onto separate lines in `reserved_keywords` unit test (#18) The `KW` macro in `tests/unit/reserved_keywords.cpp` had multiple `X(...)` invocations compressed onto one line. This updates the definition to use one invocation per line, matching the surrounding style. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit 7f5fff82304921e56c199830c103f506beb526cd Author: Lucian Popescu Date: Mon Apr 20 09:06:42 2026 +0100 Translate C boolean expressions (#12) In C, as opposed to C++, clang does not generate IntegerToBoolean implicit casts. To fix this, create an artificial implicit cast so that C and C++ code can be handled uniformly. As a side effect of this PR, C unit tests are now translated in C instead of C++. commit 42e2986bf318d28cab8558a98881c9a54cce4996 Author: Nuno Lopes Date: Mon Apr 20 09:03:19 2026 +0100 remove str copies commit 7c30e9d7d0e9af2aed17d993695533c1eae109af Author: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon Apr 20 08:23:28 2026 +0100 Reduce converter string overhead in hot paths (#17) This PR applies targeted micro-optimizations in the converter to reduce avoidable allocations/copies and use cheaper character APIs where applicable. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nuno Lopes commit a56b32360ab4f930c816fd2d4a41fc3919822d7a Author: Nuno Lopes Date: Sun Apr 19 17:45:18 2026 +0100 Fix CodeQL workflow (#11) commit c1ef22e84ef0dd151ad90bad728dd28500124bd4 Author: Nuno Lopes Date: Sun Apr 19 14:14:23 2026 +0100 typo commit a81dfa023dcdce3b71e255a2753eb5d053f3af59 Author: Nuno Lopes Date: Sun Apr 19 13:08:34 2026 +0100 fix CI flow name commit 7a1f4309277613ecf31420fb83f48a9d15c62045 Author: Nuno Lopes Date: Sun Apr 19 13:02:39 2026 +0100 CI: make workflow agnostic of OS commit 9abc006c37df160da5c827cd2ca72a29bca1a02c Author: Nuno Lopes Date: Sun Apr 19 12:35:17 2026 +0100 move file commit 690793511b0d24ff2316e788c936fb967286691c Author: Nuno Lopes Date: Sun Apr 19 12:29:36 2026 +0100 add explicit permissions to github workflow files commit 801b6690e6571bdbde2030ba00abf03813a40abe Author: Nuno Lopes Date: Sun Apr 19 12:27:38 2026 +0100 CI: skip tests directory for CodeQL commit b755dc8d15d0457d2c90c2a428b4236c3b6f6f87 Author: Lucian Popescu Date: Sun Apr 19 12:06:00 2026 +0100 ErasedPtr: drop Clone bound and allow cross-type nullptr casts (#4) This PR allows converting between `void*` and non-clonable pointer types (`FILE*`, for example), and fixes cross-type nullptr casts (casting a `nullptr void*` to any other pointer). commit e0c338e38e80ac01d3788f4fa392eaa88815b795 Author: Lucian Popescu Date: Fri Apr 17 21:36:30 2026 +0100 Escape special chars (#9) commit 25e6a56b2d0e2ad04b6fe6fdfd6ed4826fd960dd Author: Lucian Popescu Date: Fri Apr 17 21:34:37 2026 +0100 Escape all Rust reserved keywords (#8) commit 80eb49300ca3c917a248bd2d0b27ef21dd187b50 Author: Lucian Popescu Date: Fri Apr 17 09:53:03 2026 +0100 Speed-up unit tests (#6) First unit test compiles libcc2rs and rules in cargo-target, then all unit-tests use the precompiled libraries from cargo-target instead of building them each time. commit 2e62035417c6dcb8dc0680c15ec8b7ac565d0a35 Author: Lucian Popescu Date: Fri Apr 17 09:30:22 2026 +0100 Allow default initialization in static variables (#5) Use 0 rather than default::default() commit b47731d13787a9aa033384b594fd6f79ca117b8a Author: Nuno Lopes Date: Wed Apr 15 15:02:39 2026 +0100 don't load rules in parallel (not worth it) and simplify code commit f3d0a56d506fed8c69c7654fa6fbe5444ec874a4 Author: Nuno Lopes Date: Wed Apr 15 14:52:09 2026 +0100 cmake: fix discovery of clang-format commit b89c4f4ff6b3216683d6e79fe1cbd8dd81cafe5f Author: Lucian Popescu Date: Mon Apr 13 17:21:12 2026 +0100 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(&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 = ::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`. commit 80766c793aa0246d8b6251d94ff856bbe3df7b86 Author: Nuno Lopes Date: Sun Apr 12 16:51:43 2026 +0100 add ninja check-rules commit be7988801530c60f661a63bd6e2a00a9e02a727c Author: Nuno Lopes Date: Sun Apr 12 16:36:57 2026 +0100 remove old file commit 6dac32517199d96e372ca857d2cb8182f3cf9402 Author: Nuno Lopes Date: Sun Apr 12 16:36:46 2026 +0100 rename commit 5c3f0cf49a4e4a36744b5d69f2e6c9250b0c8ea6 Author: Nuno Lopes Date: Sun Apr 12 16:29:33 2026 +0100 Configure Dependabot for GitHub Actions updates --- cpp2rust/converter/converter_lib.cpp | 8 + cpp2rust/converter/converter_lib.h | 2 + .../converter/models/converter_refcount.cpp | 187 +++++++----------- .../converter/models/converter_refcount.h | 25 +-- docs/src/codegen/types/boxing.md | 13 +- docs/src/codegen/types/mappings.md | 36 ++-- docs/src/codegen/types/traits.md | 9 +- docs/src/rules/rewriting.md | 14 -- docs/src/runtime/rc.md | 13 +- libcc2rs/src/cstr.rs | 4 + libcc2rs/src/field.rs | 3 + libcc2rs/src/rc.rs | 177 ++++++++++++++++- libcc2rs/src/reinterpret.rs | 34 +++- rules/deque/src.cpp | 9 +- rules/deque/tgt_refcount.rs | 10 +- rules/deque/tgt_unsafe.rs | 7 +- rules/deque_cxx11/src.cpp | 5 - rules/deque_cxx11/tgt_unsafe.rs | 5 - rules/vector/src.cpp | 47 ----- rules/vector/tgt_refcount.rs | 46 ----- rules/vector/tgt_unsafe.rs | 40 ---- rules/vector_cxx11/src.cpp | 6 - rules/vector_cxx11/tgt_unsafe.rs | 5 - .../ub/out/refcount/row_ptr_outlives_array.rs | 26 +++ .../out/refcount/row_ptr_outlives_vector.rs | 23 +++ tests/ub/out/refcount/row_ptr_past_row_end.rs | 23 +++ tests/ub/out/unsafe/row_ptr_outlives_array.rs | 23 +++ .../ub/out/unsafe/row_ptr_outlives_vector.rs | 23 +++ tests/ub/out/unsafe/row_ptr_past_row_end.rs | 20 ++ tests/ub/row_ptr_outlives_array.cpp | 10 + tests/ub/row_ptr_outlives_vector.cpp | 12 ++ tests/ub/row_ptr_past_row_end.cpp | 7 + tests/unit/array_reference.cpp | 14 ++ tests/unit/vector_subscript_direct.cpp | 3 + 34 files changed, 527 insertions(+), 362 deletions(-) create mode 100644 tests/ub/out/refcount/row_ptr_outlives_array.rs create mode 100644 tests/ub/out/refcount/row_ptr_outlives_vector.rs create mode 100644 tests/ub/out/refcount/row_ptr_past_row_end.rs create mode 100644 tests/ub/out/unsafe/row_ptr_outlives_array.rs create mode 100644 tests/ub/out/unsafe/row_ptr_outlives_vector.rs create mode 100644 tests/ub/out/unsafe/row_ptr_past_row_end.rs create mode 100644 tests/ub/row_ptr_outlives_array.cpp create mode 100644 tests/ub/row_ptr_outlives_vector.cpp create mode 100644 tests/ub/row_ptr_past_row_end.cpp diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 9f9d10247..c11ea4977 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -214,6 +214,14 @@ bool IsUnionArrayMember(const clang::Expr *base) { return false; } +clang::Expr *SubscriptedArray(clang::Expr *base) { + while (auto *sub = clang::dyn_cast( + base->IgnoreImplicit())) { + base = sub->getBase(); + } + return base; +} + // TODO: this will be gone when the AST matcher lands std::tuple ReplaceNonUniformLibcField(clang::MemberExpr *expr) { diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index 5843ed862..384455d50 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -57,6 +57,8 @@ bool IsInMainFile(const clang::Decl *decl); bool IsUnionArrayMember(const clang::Expr *base); +clang::Expr *SubscriptedArray(clang::Expr *base); + std::tuple ReplaceNonUniformLibcField(clang::MemberExpr *expr); diff --git a/cpp2rust/converter/models/converter_refcount.cpp b/cpp2rust/converter/models/converter_refcount.cpp index 11d44dc09..5c6633068 100644 --- a/cpp2rust/converter/models/converter_refcount.cpp +++ b/cpp2rust/converter/models/converter_refcount.cpp @@ -101,36 +101,14 @@ static bool IsPointerType(clang::ASTContext &ctx, clang::QualType type) { IteratorCategory::Contiguous; } -bool ConverterRefCount::PendingDeref::compute_inner_boxed( - clang::Expr *expr) const { - if (!expr) { - return false; - } - if (!IsBoxedType(ctx, expr->getType().getNonReferenceType())) { - return false; - } - if (auto *ase = clang::dyn_cast(expr)) { - auto base_type = ase->getBase()->IgnoreCasts()->getType(); - if (base_type->isPointerType()) - return IsBoxedType(ctx, base_type->getPointeeType()); - return IsBoxedType(ctx, base_type.getNonReferenceType()); - } - if (auto *oce = clang::dyn_cast(expr)) { - return IsBoxedType(ctx, oce->getArg(0)->getType().getNonReferenceType()); - } - return false; -} - -void ConverterRefCount::PendingDeref::set(std::string str, bool fresh, - clang::Expr *expr) { +void ConverterRefCount::PendingDeref::set(std::string str, bool fresh) { assert_consumed(); - set_unchecked(std::move(str), fresh, expr); + set_unchecked(std::move(str), fresh); } -void ConverterRefCount::PendingDeref::set_unchecked(std::string str, bool fresh, - clang::Expr *expr) { +void ConverterRefCount::PendingDeref::set_unchecked(std::string str, + bool fresh) { value = std::move(str); - pointee_is_boxed = compute_inner_boxed(expr); ptr_is_fresh = fresh; type = ComputedExprType::Pending; } @@ -148,22 +126,6 @@ std::string ConverterRefCount::GetInnerType(clang::QualType type) { return std::move(str).substr(pos + 1, end - pos - 1); } -ConverterRefCount::PushUnboxedIfSimple::PushUnboxedIfSimple( - ConverterRefCount &c, std::string_view outer, clang::QualType inner_type) - : c(c) { - bool unboxed = outer == "Ptr<%>" || outer == "%"; - - // Vectors are boxed until the last element - if (!unboxed && (outer == "Vec<%>" || outer == "Box<%>")) { - if (!IsBoxedType(c.ctx_, inner_type)) { - unboxed = true; - } - } - - c.conversion_kind_.push_back(unboxed ? ConversionKind::Unboxed - : ConversionKind::FullRefCount); -} - std::string ConverterRefCount::GetSafeTypeAsString(clang::QualType qual_type) const { std::string type_as_string; @@ -278,7 +240,9 @@ bool ConverterRefCount::VisitIncompleteArrayType( clang::IncompleteArrayType *type) { std::string str; { - PushUnboxedIfSimple push(*this, "Box<%>", type->getElementType()); + PushConversionKind push(*this, type->getElementType()->isArrayType() + ? ConversionKind::Pointee + : ConversionKind::Unboxed); str = std::format("Box<[{}]>", ToString(type->getElementType())); } StrCat(BoxType(std::move(str))); @@ -298,7 +262,7 @@ bool ConverterRefCount::VisitReferenceType(clang::ReferenceType *type) { auto element_type = pointee_type->getAsArrayTypeUnsafe()->getElementType(); PushConversionKind push1(*this, ConversionKind::Ptr, !element_type->isArrayType()); - PushConversionKind push2(*this, ConversionKind::FullRefCount, + PushConversionKind push2(*this, ConversionKind::Pointee, element_type->isArrayType()); StrCat("Ptr<"); Convert(element_type); @@ -338,8 +302,7 @@ bool ConverterRefCount::VisitPointerType(clang::PointerType *type) { bool pointee_is_array = pointee_type->isArrayType() && !IsVaListType(pointee_type); PushConversionKind push1(*this, ConversionKind::Ptr, !pointee_is_array); - PushConversionKind push2(*this, ConversionKind::FullRefCount, - pointee_is_array); + PushConversionKind push2(*this, ConversionKind::Pointee, pointee_is_array); if (pointee_type->isRecordType() && IsAbstractUserClass(pointee_type->getAsRecordDecl())) { StrCat("PtrDyngetSize()).c_str())); break; case ConversionKind::Ptr: { - PushConversionKind elem(*this, ConversionKind::FullRefCount, + PushConversionKind elem(*this, ConversionKind::Pointee, type->getElementType()->isArrayType()); Convert(type->getElementType()); break; } case ConversionKind::Pointee: case ConversionKind::FullRefCount: { - PushConversionKind elem(*this, ConversionKind::FullRefCount, + PushConversionKind elem(*this, ConversionKind::Pointee, type->getElementType()->isArrayType()); StrCat("Box<["); Convert(type->getElementType()); @@ -403,6 +366,7 @@ std::string ConverterRefCount::ConvertObject(clang::Expr *expr, if (shape == ObjectShape::Element && expr->getType()->isPointerType()) { auto pointee = expr->getType()->getPointeeType(); if (IsBoxedType(ctx_, pointee) || pointee->isArrayType()) { + PushConversionKind push(*this, ConversionKind::Pointee); computed_expr_type_ = ComputedExprType::FreshPointer; return std::format("Ptr::<{}>::decay(&({}))", ToString(pointee), std::move(str)); @@ -532,13 +496,23 @@ std::string ConverterRefCount::ConvertPtrType(clang::QualType type) { return std::format("Ptr<{}>", std::move(str)); } -bool ConverterRefCount::VisitArraySubscriptExpr( - clang::ArraySubscriptExpr *expr) { +bool ConverterRefCount::IsPointerSubscript( + const clang::ArraySubscriptExpr *expr) const { auto *base = expr->getBase(); if (base->IgnoreCasts()->getType()->isPointerType() || IsUnionArrayMember(base) || IsArrayFieldPtr(ctx_, base) || (IsReferenceType(base) && base->IgnoreCasts()->getType()->isArrayType())) { + return true; + } + auto *row = clang::dyn_cast(base->IgnoreCasts()); + return row && IsPointerSubscript(row); +} + +bool ConverterRefCount::VisitArraySubscriptExpr( + clang::ArraySubscriptExpr *expr) { + auto *base = expr->getBase(); + if (IsPointerSubscript(expr)) { ConvertPointerSubscript(expr); } else { if (!base->IgnoreCasts()->getType()->isArrayType()) { @@ -546,7 +520,7 @@ bool ConverterRefCount::VisitArraySubscriptExpr( pending_deref_.assert_consumed(); Buffer buf(*this); ConvertArraySubscript(base, expr->getIdx(), expr->getType()); - pending_deref_.set_unchecked(std::move(buf).str(), isFresh(), expr); + pending_deref_.set_unchecked(std::move(buf).str(), isFresh()); return false; } PushParen paren(*this); @@ -1945,10 +1919,13 @@ bool ConverterRefCount::VisitInitListExpr(clang::InitListExpr *expr) { } auto conv = getConversionKind(); - // 2D arrays are FullRefCount'ed on the second level as well. - PushConversionKind push( - *this, ConversionKind::Unboxed, - !(expr->getNumInits() > 0 && expr->getInit(0)->getType()->isArrayType())); + bool rows = + expr->getNumInits() > 0 && expr->getInit(0)->getType()->isArrayType(); + PushConversionKind push(*this, + rows && conv == ConversionKind::FullRefCount + ? ConversionKind::Pointee + : ConversionKind::Unboxed, + !rows || conv == ConversionKind::FullRefCount); switch (conv) { case ConversionKind::Unboxed: @@ -2301,28 +2278,10 @@ bool ConverterRefCount::VisitCXXForRangeStmtVector( PushBrace brace(*this); - // handle multi-level types such as Vec>> - if (IsBoxedType(ctx_, stmt->getRangeInit()->getType()) && - GetInnerType(stmt->getRangeInit()->getType()).starts_with("Value<")) { - StrCat(keyword::kLet, loop_var_name, token::kColon); - - if (loop_var->getType()->isReferenceType()) { - StrCat(ToString(loop_var->getType()), token::kAssign, loop_var_name, - GetPointerDerefSuffix(loop_var->getType().getNonReferenceType()), - ".as_pointer()"); - } else { - PushConversionKind push(*this, ConversionKind::FullRefCount); - StrCat(ToString(loop_var->getType()), token::kAssign, - "Rc::new(RefCell::new(", loop_var_name, - GetPointerDerefSuffix(loop_var->getType()), ".borrow().clone()))"); - } - StrCat(token::kSemiColon); - } else { - auto type = loop_var->getType(); - bool copy = type.isPODType(ctx_) && !type->isRecordType(); - EmitByValueShadow(loop_var, loop_var_name + GetPointerDerefSuffix(type) + - (copy ? "" : ".clone()")); - } + auto type = loop_var->getType(); + bool copy = type.isPODType(ctx_) && !type->isRecordType(); + EmitByValueShadow(loop_var, loop_var_name + GetPointerDerefSuffix(type) + + (copy ? "" : ".clone()")); ConvertForRangeBody(stmt); @@ -2529,7 +2488,7 @@ ConverterRefCount::GetArrayDefaultAsString(clang::QualType qual_type) { size_as_string.c_str(), default_as_string); } PushConversionKind push(*this, element_type->isArrayType() - ? ConversionKind::FullRefCount + ? ConversionKind::Pointee : ConversionKind::Unboxed); auto element_type_as_string = ToString(element_type); auto default_as_string = GetDefaultAsString(element_type); @@ -2947,7 +2906,7 @@ bool ConverterRefCount::ConvertCXXOperatorCallExpr( ConvertObject(expr->getArg(0), ObjectShape::Element), ConvertPtrType(expr->getArg(0)->getType()), ConvertSubscriptIndex(expr->getArg(1))), - /*fresh=*/true, expr); + /*fresh=*/true); break; } @@ -2971,7 +2930,7 @@ bool ConverterRefCount::ConvertCXXOperatorCallExpr( : std::format("{}.offset({})", ptr, idx)); if (is_inner_boxed) { - StrCat(GetPointerDerefSuffix(expr->getType()), ".as_pointer()"); + StrCat(".as_pointer()"); if (!isObject()) { StrCat(std::format("as Ptr<{}>)", ToString(expr->getType()))); } @@ -3024,9 +2983,8 @@ clang::DeclRefExpr *ConverterRefCount::GetGlobalArrayRValue(clang::Expr *base) { return nullptr; } // Records are accessed in place, as their fields may be written to. - auto elem_ty = arr_ty->getElementType(); - if (!TypeIsCopyable(elem_ty) && !elem_ty->isPointerType() && - !elem_ty->isArrayType()) { + auto elem_ty = ctx_.getBaseElementType(arr_ty->getElementType()); + if (!TypeIsCopyable(elem_ty) && !elem_ty->isPointerType()) { return nullptr; } return ref; @@ -3062,34 +3020,32 @@ void ConverterRefCount::ConvertArraySubscript(clang::Expr *base, } if (is_inner_boxed) { - StrCat(GetPointerDerefSuffix(type), ".as_pointer()"); + StrCat(".as_pointer()"); } } computed_expr_type_ = ComputedExprType::FreshPointer; - } else if (auto *global = GetGlobalArrayRValue(base)) { - // Read just the element instead of cloning the whole global array. - // The index is evaluated first, as it may itself access the global. - StrCat(std::format( - "({{ let __idx = ({}) as usize; {}.with(|rc| rc.borrow()[__idx]{}) }})", - ConvertRValue(idx), ConvertDeclRef(global, global->getDecl()), - TypeIsCopyable(type) ? "" : ".clone()")); + } else if (auto *global = GetGlobalArrayRValue(SubscriptedArray(base))) { + std::vector idxs = {idx}; + for (auto *row = base->IgnoreImplicit(); + auto *sub = clang::dyn_cast(row); + row = sub->getBase()->IgnoreImplicit()) { + idxs.push_back(sub->getIdx()); + } + std::string lets; + std::string path; + for (unsigned i = idxs.size(); i-- > 0;) { + auto n = idxs.size() - 1 - i; + lets += std::format("let __idx{} = ({}) as usize; ", n, + ConvertRValue(idxs[i])); + path += std::format("[__idx{}]", n); + } + StrCat(std::format("({{ {}{}.with(|rc| rc.borrow(){}{}) }})", lets, + ConvertDeclRef(global, global->getDecl()), path, + TypeIsCopyable(type) ? "" : ".clone()")); SetFreshType(type); } else { - if (isLValue() && - clang::isa(base->IgnoreImplicit())) { - PushExprKind push(*this, ExprKind::RValue); - Convert(base->IgnoreImplicit()); - } else { - Convert(base->IgnoreImplicit()); - } - if (clang::isa(base->IgnoreImplicit())) { - if (isRValue()) { - StrCat(".borrow()"); - } else { - StrCat(".borrow_mut()"); - } - } + Convert(base->IgnoreImplicit()); StrCat(std::format("[({}) as usize]", ConvertRValue(idx))); SetValueFreshness(type); } @@ -3110,7 +3066,7 @@ void ConverterRefCount::ConvertPointerSubscript( pending_deref_.assert_consumed(); Buffer buf(*this); ConvertPointerElem(base, idx); - pending_deref_.set_unchecked(std::move(buf).str(), isFresh(), expr); + pending_deref_.set_unchecked(std::move(buf).str(), isFresh()); return; } @@ -3122,7 +3078,7 @@ void ConverterRefCount::ConvertPointerSubscript( } else { ConvertPointerOffset(base, idx); if (expr->getType()->isArrayType()) { - StrCat(GetPointerDerefSuffix(expr->getType()), ".as_pointer()"); + StrCat(".as_pointer()"); } } if (deref) { @@ -3165,8 +3121,10 @@ void ConverterRefCount::ConvertAddrOf(clang::Expr *expr, auto pointee_type = pointer_type->getPointeeType(); if (isObject() && WantsElementPtr() && (IsBoxedType(ctx_, pointee_type) || pointee_type->isArrayType())) { + auto ptr = ConvertPointer(expr); + PushConversionKind push(*this, ConversionKind::Pointee); StrCat(std::format("Ptr::<{}>::decay(&({}))", ToString(pointee_type), - ConvertPointer(expr))); + std::move(ptr))); computed_expr_type_ = ComputedExprType::FreshPointer; return; } @@ -3206,12 +3164,12 @@ void ConverterRefCount::ConvertDeref(clang::Expr *expr) { } if (isAddrOf() && pointee_type->isArrayType()) { - str = std::format("({}){}.as_pointer()", std::move(str), - GetPointerDerefSuffix(pointee_type)); + str = std::format("({}).as_pointer()", std::move(str)); } if (isObject() && WantsElementPtr() && (IsBoxedType(ctx_, pointee_type) || pointee_type->isArrayType())) { + PushConversionKind push(*this, ConversionKind::Pointee); StrCat(std::format("Ptr::<{}>::decay(&({}))", ToString(pointee_type), std::move(str))); computed_expr_type_ = ComputedExprType::FreshPointer; @@ -3354,17 +3312,10 @@ std::string ConverterRefCount::ConvertMappedMethodCall( return receiver + ConvertIRFragment(mc.body, expr, args, num_args, ctx); } - bool is_boxed = pending_deref_.is_boxed(); auto ptr = pending_deref_.take(); auto body = ConvertIRFragment(mc.body, expr, args, num_args, ctx); SetFreshType(expr->getType()); - if (is_boxed) { - return std::format( - "{}.with_mut(|__v: &mut Value<{}>| (*__v.borrow_mut()){})", ptr, - ToString(arg->getType()), body); - } - return std::format("{}.with_mut(|__v: {}| __v{})", ptr, param_type, body); } @@ -3373,7 +3324,7 @@ std::string ConverterRefCount::ConvertPointeeType(clang::QualType ptr_type) { PushConversionKind push(*this, ConversionKind::Unboxed); auto pointee = ptr_type->getPointeeType(); if (pointee->isArrayType()) { - PushConversionKind array(*this, ConversionKind::FullRefCount); + PushConversionKind array(*this, ConversionKind::Pointee); return std::string(Trim(ToString(pointee))); } if (!pointee->isRecordType()) { diff --git a/cpp2rust/converter/models/converter_refcount.h b/cpp2rust/converter/models/converter_refcount.h index e76995a74..b2e54bd63 100644 --- a/cpp2rust/converter/models/converter_refcount.h +++ b/cpp2rust/converter/models/converter_refcount.h @@ -294,6 +294,8 @@ class ConverterRefCount final : public Converter { clang::DeclRefExpr *GetGlobalArrayRValue(clang::Expr *base); void ConvertPointerSubscript(clang::ArraySubscriptExpr *expr) override; + bool IsPointerSubscript(const clang::ArraySubscriptExpr *expr) const; + void ConvertFunctionMain(const clang::FunctionDecl *decl, const std::string_view main_function_name) override; @@ -494,14 +496,6 @@ class ConverterRefCount final : public Converter { } }; - struct PushUnboxedIfSimple { - ConverterRefCount &c; - PushUnboxedIfSimple(ConverterRefCount &c, std::string_view outer_type, - clang::QualType inner_type); - - ~PushUnboxedIfSimple() { c.conversion_kind_.pop_back(); } - }; - std::string BoxType(std::string &&str) const; std::string BoxValue(std::string &&str) const; @@ -514,32 +508,25 @@ class ConverterRefCount final : public Converter { // emit ptr.write(rhs), or by ConvertMappedMethodCall to emit // ptr.with_mut(...). struct PendingDeref { - PendingDeref(ComputedExprType &type, clang::ASTContext &ctx) - : type(type), ctx(ctx) {} - void set(std::string str, bool fresh, clang::Expr *expr = nullptr); - void set_unchecked(std::string str, bool fresh, - clang::Expr *expr = nullptr); + PendingDeref(ComputedExprType &type) : type(type) {} + void set(std::string str, bool fresh); + void set_unchecked(std::string str, bool fresh); std::string take() { auto result = std::move(value); value.clear(); - pointee_is_boxed = false; ptr_is_fresh = false; return result; } bool empty() const { return value.empty(); } - bool is_boxed() const { return pointee_is_boxed; } bool is_fresh() const { return ptr_is_fresh; } void assert_consumed() const { assert(value.empty() && "pending_deref_ not consumed"); } private: - bool compute_inner_boxed(clang::Expr *expr) const; ComputedExprType &type; - clang::ASTContext &ctx; std::string value; - bool pointee_is_boxed = false; bool ptr_is_fresh = false; - } pending_deref_{computed_expr_type_, ctx_}; + } pending_deref_{computed_expr_type_}; }; } // namespace cpp2rust diff --git a/docs/src/codegen/types/boxing.md b/docs/src/codegen/types/boxing.md index 9f055eaef..f0cc94fa2 100644 --- a/docs/src/codegen/types/boxing.md +++ b/docs/src/codegen/types/boxing.md @@ -48,12 +48,13 @@ the copy on entry then lets the body treat parameters exactly like local variables. The preamble skips reference parameters, which are a `Ptr` and never boxed. -Nested containers, library ones and arrays alike, box each level except the -innermost, so that every inner container can be borrowed and mutated on its own, -and a pointer can be taken to it. The boxing is written into the type rules -themselves: `std::vector>` maps to `Vec>>`, and -the `carray` rules map `int a[2][2]` to `Box<[Value>]>`, both before -the outer `Value<...>` of the declaration is added. +Nested containers, library ones and arrays alike, store their inner levels +inline: `std::vector>` maps to `Vec>`, and +`int a[2][2]` to `Box<[Box<[i32]>]>`, both inside the outer `Value<...>` of the +declaration. Copying a nested container with `clone` hence copies every level, +as C++ does. A pointer into an inner container is a `Nested` pointer (see +[Pointer kinds](../../runtime/rc.md#pointer-kinds)), which reaches the inner +container through the outer one. Struct fields are stored inline, so that a whole struct is a single allocation, and a pointer to a field records the struct's allocation and the field's byte diff --git a/docs/src/codegen/types/mappings.md b/docs/src/codegen/types/mappings.md index 02bd111b5..6030d9a33 100644 --- a/docs/src/codegen/types/mappings.md +++ b/docs/src/codegen/types/mappings.md @@ -3,24 +3,24 @@ The table gives the spelling of each C++ type in both models, before any refcount boxing. `T` stands for the translated inner type. -| C++ | Unsafe model | Refcount model | -| ------------------------------ | ----------------------------------------------- | --------------------------------------------------------- | -| `bool` | `bool` | `bool` | -| `int`, `unsigned long`, ... | `i32`, `u64`, ... (host width) | same | -| `float`, `double` | `f32`, `f64` | same | -| `char` | `libc::c_char` | `u8` | -| `size_t` and other typedefs | by type rule (`usize`), else desugared | same | -| `T[N]` | `[T; N]` | `Box<[T]>` | -| `T[]` | `[T]` | `Box<[T]>` | -| `struct S`, `enum E` | `S`, `E` | same | -| `T *`, `T &` | `*mut T`, `*const T` | [`Ptr`](../../runtime/rc.md#values-and-pointers) | -| `Abstract *` | `*mut dyn Abstract` | [`PtrDyn`](../../runtime/ptr-dyn.md) | -| `void *` | `*mut ::libc::c_void` | [`AnyPtr`](../../runtime/void.md) | -| `R (*)(A)` | `Option R>` | [`FnPtr R>`](../../runtime/fn-ptr.md) | -| `va_list` | [`VaList`](../../runtime/va-args.md) | [`VaList`](../../runtime/va-args.md) | -| lambda closure | `impl Fn(A) -> R` as a parameter, `_` elsewhere | same | -| `std::unique_ptr` | by type rule (`Option>`) | by type rule (`Option>`) | -| `std::vector` and other STL | by type rule (`Vec`) | by type rule (`Vec`, `Vec>>` when nested) | +| C++ | Unsafe model | Refcount model | +| ------------------------------ | ----------------------------------------------- | --------------------------------------------------- | +| `bool` | `bool` | `bool` | +| `int`, `unsigned long`, ... | `i32`, `u64`, ... (host width) | same | +| `float`, `double` | `f32`, `f64` | same | +| `char` | `libc::c_char` | `u8` | +| `size_t` and other typedefs | by type rule (`usize`), else desugared | same | +| `T[N]` | `[T; N]` | `Box<[T]>` | +| `T[]` | `[T]` | `Box<[T]>` | +| `struct S`, `enum E` | `S`, `E` | same | +| `T *`, `T &` | `*mut T`, `*const T` | [`Ptr`](../../runtime/rc.md#values-and-pointers) | +| `Abstract *` | `*mut dyn Abstract` | [`PtrDyn`](../../runtime/ptr-dyn.md) | +| `void *` | `*mut ::libc::c_void` | [`AnyPtr`](../../runtime/void.md) | +| `R (*)(A)` | `Option R>` | [`FnPtr R>`](../../runtime/fn-ptr.md) | +| `va_list` | [`VaList`](../../runtime/va-args.md) | [`VaList`](../../runtime/va-args.md) | +| lambda closure | `impl Fn(A) -> R` as a parameter, `_` elsewhere | same | +| `std::unique_ptr` | by type rule (`Option>`) | by type rule (`Option>`) | +| `std::vector` and other STL | by type rule (`Vec`) | by type rule (`Vec`) | Other built-ins (`wchar_t`, `long double`, `char16_t`) are omitted. Rvalue references (`T &&`) have no mapping of their own; they reach the converter only diff --git a/docs/src/codegen/types/traits.md b/docs/src/codegen/types/traits.md index 734cf67d7..d76157c46 100644 --- a/docs/src/codegen/types/traits.md +++ b/docs/src/codegen/types/traits.md @@ -25,11 +25,10 @@ The refcount model skips `Clone` altogether when the copy constructor is deleted, and never derives `Copy`. It derives `Clone` for C structs and for classes with an implicit or defaulted copy constructor, which copy each field with its own `clone`, i.e., its C++ copy constructor. The exceptions are fields -that are or nest a `Value`, such as `std::vector` (`Value>`, see -[Boxing](boxing.md)) or `std::vector>` -(`Value>>>`), whose derived `clone` would share the `Value`s -instead of copying them; the generated `Clone` then translates the implicit copy -constructor, which copies them deeply. +that are a `Value`, such as `std::vector` (`Value>`, see +[Boxing](boxing.md)), whose derived `clone` would share the `Value` instead of +copying it; the generated `Clone` then translates the implicit copy constructor, +which copies them deeply. A user-defined copy constructor takes a `Ptr` to the source, while `clone` only has `&self`. `clone` passes it a pointer to a shallow copy of `self`, built diff --git a/docs/src/rules/rewriting.md b/docs/src/rules/rewriting.md index 7ce8efda4..0d3588001 100644 --- a/docs/src/rules/rewriting.md +++ b/docs/src/rules/rewriting.md @@ -61,20 +61,6 @@ reference usages (`r.push_back(20)` with `std::vector &r = *p`); both are [translated as a `Ptr`](../codegen/pointers.md), and that `Ptr` is what `with_mut` is called on. -When the pointee is itself a boxed value (`Value`, i.e. `Rc>`), -the closure takes `&mut Value` and an extra borrow is inserted. This is the -case for nested containers: the refcount model translates -`std::vector>` as `Vec>>` so that each element -has interior mutability of its own, and a `Ptr` to an inner vector therefore -points at a `Value>`, not a `Vec`: - -```rust -ptr.with_mut(|__v: &mut Value>| (*__v.borrow_mut()).push(20)) -``` - -The closure type is built from the C++ argument's type, not the rule's declared -parameter type. - ## The read-access counterpart For read access the converter does not emit a closure. A pointer receiver whose diff --git a/docs/src/runtime/rc.md b/docs/src/runtime/rc.md index aedb8d9e3..1ea11b348 100644 --- a/docs/src/runtime/rc.md +++ b/docs/src/runtime/rc.md @@ -60,6 +60,11 @@ A `Ptr` knows what it points into: - `StackArray` and `HeapArray`: a fixed-size array. - `StackVec` and `HeapVec`: a growable buffer; `std::vector` contents and string literals live in one. +- `Nested`: an inner vector or array of a nested container, such as a row of + `std::vector>` or of `int[2][3]`. The inner container is + stored inline in the outer one, so the pointer holds the pointer to it, as an + `Rc>`, and reaches it through the outer container on every + access. - `Field`: a field of a struct (see [Pointers to fields](#pointers-to-fields)). - `Reinterpreted`: a byte-level view produced by a cast (see [Type Reinterpretation](./reinterpret.md)). @@ -300,9 +305,11 @@ through every other pointer at once. Where the code generator needs a different view of the same allocation, it does not upgrade at all. `decay` turns a pointer to a whole `Vec` or `Box<[T]>` -into a pointer to its first element by re-tagging the existing weak reference, -and `Ptr::to_dyn` (see [Virtual Classes](./ptr-dyn.md)) does the same for the -upcast to a trait object. +into a pointer to its first element by re-tagging the existing weak reference; +when the `Vec` or `Box<[T]>` is itself an element of another container, it +instead allocates a `Nested` pointer that holds the pointer to it. `Ptr::to_dyn` +(see [Virtual Classes](./ptr-dyn.md)) re-tags the weak reference in the same way +for the upcast to a trait object. ## Arithmetic diff --git a/libcc2rs/src/cstr.rs b/libcc2rs/src/cstr.rs index 500ec7279..62e7086c5 100644 --- a/libcc2rs/src/cstr.rs +++ b/libcc2rs/src/cstr.rs @@ -261,6 +261,10 @@ impl Ptr { let b = rc.borrow(); f(until_nul(&b[self.offset..])) } + PtrKind::Nested(parent) => { + let root = parent.root(); + f(until_nul(&parent.borrow_slice(&*root)[self.offset..])) + } PtrKind::Field(root) => { let root = crate::field::upgrade(root); let b = Ptr::::borrow_field(&*root, self.offset); diff --git a/libcc2rs/src/field.rs b/libcc2rs/src/field.rs index 6fb1ece05..cc76e129e 100644 --- a/libcc2rs/src/field.rs +++ b/libcc2rs/src/field.rs @@ -152,6 +152,9 @@ impl FieldPtr for Ptr { PtrKind::StackArray(w) | PtrKind::HeapArray(w) => (w.clone(), start()), PtrKind::StackVec(w) | PtrKind::HeapVec(w) => (w.clone(), start()), PtrKind::Field(root) => (root.clone(), self.offset), + PtrKind::Nested(_) => { + panic!("pointers to fields of elements of nested containers are not supported") + } PtrKind::Reinterpreted(_) => { let (alloc, byte_offset) = self.original_alloc().unwrap(); return Ptr::from_original_alloc(alloc, byte_offset.wrapping_add(offset)); diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 0a0e37252..a9a3345c8 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -5,7 +5,7 @@ use crate::{CChar, PostfixDec, PostfixInc, PrefixDec, PrefixInc}; use std::any::{Any, TypeId}; use std::{ - cell::{Ref, RefCell}, + cell::{Ref, RefCell, RefMut}, fmt, ops::Sub, rc::{Rc, Weak}, @@ -29,6 +29,71 @@ impl DeepClone for Value { } } +pub trait SliceParent { + fn root(&self) -> Rc; + fn borrow_slice<'a>(&self, root: &'a dyn Any) -> Ref<'a, [T]>; + fn borrow_slice_mut<'a>(&self, root: &'a dyn Any) -> RefMut<'a, [T]>; + fn address(&self) -> usize; +} + +impl SliceParent for Ptr +where + C: AsRef<[T]> + AsMut<[T]> + 'static, +{ + fn root(&self) -> Rc { + match &self.kind { + PtrKind::StackVec(weak) | PtrKind::HeapVec(weak) => { + weak.upgrade().unwrap_or_else(|| dangling()) + } + PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => { + weak.upgrade().unwrap_or_else(|| dangling()) + } + PtrKind::Nested(parent) => parent.root(), + _ => unreachable!(), + } + } + + fn borrow_slice<'a>(&self, root: &'a dyn Any) -> Ref<'a, [T]> { + let offset = self.offset; + match &self.kind { + PtrKind::StackVec(_) | PtrKind::HeapVec(_) => Ref::map( + root.downcast_ref::>>().unwrap().borrow(), + |v| v[offset].as_ref(), + ), + PtrKind::StackArray(_) | PtrKind::HeapArray(_) => Ref::map( + root.downcast_ref::>>().unwrap().borrow(), + |a| a[offset].as_ref(), + ), + PtrKind::Nested(parent) => Ref::map(parent.borrow_slice(root), |s| s[offset].as_ref()), + _ => unreachable!(), + } + } + + fn borrow_slice_mut<'a>(&self, root: &'a dyn Any) -> RefMut<'a, [T]> { + let offset = self.offset; + match &self.kind { + PtrKind::StackVec(_) | PtrKind::HeapVec(_) => RefMut::map( + root.downcast_ref::>>().unwrap().borrow_mut(), + |v| v[offset].as_mut(), + ), + PtrKind::StackArray(_) | PtrKind::HeapArray(_) => RefMut::map( + root.downcast_ref::>>() + .unwrap() + .borrow_mut(), + |a| a[offset].as_mut(), + ), + PtrKind::Nested(parent) => { + RefMut::map(parent.borrow_slice_mut(root), |s| s[offset].as_mut()) + } + _ => unreachable!(), + } + } + + fn address(&self) -> usize { + self.kind.address().wrapping_add(self.byte_offset()) + } +} + pub(crate) struct ReinterpretedView { // Pointer to the source of reinterpret pub(crate) alloc: OriginalAlloc, @@ -48,6 +113,7 @@ pub(crate) enum PtrKind { HeapArray(Weak>>), StackVec(Weak>>), HeapVec(Weak>>), + Nested(Rc>), Field(Weak), Reinterpreted(Rc), } @@ -62,6 +128,11 @@ pub enum StrongPtr { rc: Rc>>, offset: usize, }, + Nested { + root: Rc, + parent: Rc>, + offset: usize, + }, Field { root: Rc, offset: usize, @@ -84,6 +155,11 @@ impl StrongPtr { StrongPtr::StackSingle(rc) => rc.borrow(), StrongPtr::Vec { rc, offset } => Ref::map(rc.borrow(), |v| &v[*offset]), StrongPtr::StackArray { rc, offset } => Ref::map(rc.borrow(), |a| &a[*offset]), + StrongPtr::Nested { + root, + parent, + offset, + } => Ref::map(parent.borrow_slice(&**root), |v| &v[*offset]), StrongPtr::Field { root, offset } => Ptr::borrow_field(&**root, *offset), StrongPtr::Reinterpreted { alloc, @@ -114,6 +190,7 @@ impl fmt::Debug for PtrKind { PtrKind::Null => write!(f, "Null"), PtrKind::StackVec(w) => write!(f, "StackVec({:?})", w.as_ptr()), PtrKind::HeapVec(w) => write!(f, "HeapVec({:?})", w.as_ptr()), + PtrKind::Nested(parent) => write!(f, "Nested(0x{:x})", parent.address()), PtrKind::StackSingle(w) => write!(f, "StackSingle({:?})", w.as_ptr()), PtrKind::HeapSingle(w) => write!(f, "HeapSingle({:?})", w.as_ptr()), PtrKind::StackArray(w) => write!(f, "StackArray({:?})", w.as_ptr()), @@ -133,6 +210,7 @@ impl Clone for PtrKind { PtrKind::Null => PtrKind::Null, PtrKind::StackVec(weak) => PtrKind::StackVec(weak.clone()), PtrKind::HeapVec(weak) => PtrKind::HeapVec(weak.clone()), + PtrKind::Nested(parent) => PtrKind::Nested(Rc::clone(parent)), PtrKind::StackSingle(weak) => PtrKind::StackSingle(weak.clone()), PtrKind::HeapSingle(weak) => PtrKind::HeapSingle(weak.clone()), PtrKind::StackArray(weak) => PtrKind::StackArray(weak.clone()), @@ -149,6 +227,7 @@ impl PtrKind { PtrKind::Null => 0, PtrKind::StackSingle(w) | PtrKind::HeapSingle(w) => w.as_ptr() as usize, PtrKind::StackVec(w) | PtrKind::HeapVec(w) => w.as_ptr() as usize, + PtrKind::Nested(parent) => parent.address(), PtrKind::StackArray(w) | PtrKind::HeapArray(w) => w.as_ptr() as usize, PtrKind::Field(root) => root.as_ptr() as *const () as usize, PtrKind::Reinterpreted(data) => data.alloc.address(), @@ -321,6 +400,7 @@ impl Ptr { PtrKind::StackVec(weak) | PtrKind::HeapVec(weak) => { weak.upgrade().expect("ub: dangling pointer").borrow().len() } + PtrKind::Nested(parent) => parent.borrow_slice(&*parent.root()).len(), PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => { weak.upgrade().expect("ub: dangling pointer").borrow().len() } @@ -338,6 +418,7 @@ impl Ptr { .expect("ub: dangling pointer") .borrow() .is_empty(), + PtrKind::Nested(parent) => parent.borrow_slice(&*parent.root()).is_empty(), PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => weak .upgrade() .expect("ub: dangling pointer") @@ -394,6 +475,11 @@ impl Ptr { rc: weak.upgrade().unwrap_or_else(|| dangling()), offset: self.offset, }, + PtrKind::Nested(parent) => StrongPtr::Nested { + root: parent.root(), + parent: Rc::clone(parent), + offset: self.offset, + }, PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => StrongPtr::StackArray { rc: weak.upgrade().unwrap_or_else(|| dangling()), offset: self.offset, @@ -449,6 +535,10 @@ impl Ptr { OriginalAlloc::slice(weak), self.offset.wrapping_mul(T::byte_size()), ), + PtrKind::Nested(parent) => ( + OriginalAlloc::nested(parent), + self.offset.wrapping_mul(T::byte_size()), + ), PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => ( OriginalAlloc::slice(weak), self.offset.wrapping_mul(T::byte_size()), @@ -532,6 +622,10 @@ impl Ptr { let mut borrow = rc.borrow_mut(); f(&mut borrow[offset]) } + PtrKind::Nested(parent) => { + let root = parent.root(); + f(&mut parent.borrow_slice_mut(&*root)[offset]) + } PtrKind::Field(root) => { let root = upgrade_root(root); f(&mut Self::borrow_field_mut(&*root, offset)) @@ -598,6 +692,10 @@ impl Ptr { let borrow = rc.borrow(); f(&borrow[offset]) } + PtrKind::Nested(parent) => { + let root = parent.root(); + f(&parent.borrow_slice(&*root)[offset]) + } PtrKind::Field(root) => { let root = upgrade_root(root); f(&Self::borrow_field(&*root, offset)) @@ -654,6 +752,10 @@ impl Ptr { let mut b = rc.borrow_mut(); f(&mut b[off..off + len]) } + PtrKind::Nested(parent) => { + let root = parent.root(); + f(&mut parent.borrow_slice_mut(&*root)[off..off + len]) + } PtrKind::Field(root) => { assert!(len <= 1, "ub: with_slice_mut out of bounds"); let root = upgrade_root(root); @@ -689,6 +791,10 @@ impl Ptr { let b = rc.borrow(); f(&b[off..off + len]) } + PtrKind::Nested(parent) => { + let root = parent.root(); + f(&parent.borrow_slice(&*root)[off..off + len]) + } PtrKind::Field(root) => { assert!(len <= 1, "ub: with_slice out of bounds"); let root = upgrade_root(root); @@ -744,6 +850,10 @@ impl Ptr { let strong = weak.upgrade().expect("ub: dangling pointer"); (*strong.borrow_mut())[self.get_offset()..last].sort(); } + PtrKind::Nested(ref parent) => { + let root = parent.root(); + parent.borrow_slice_mut(&*root)[self.get_offset()..last].sort(); + } PtrKind::StackArray(ref weak) | PtrKind::HeapArray(ref weak) => { let strong = weak.upgrade().expect("ub: dangling pointer"); (*strong.borrow_mut())[self.get_offset()..last].sort(); @@ -788,6 +898,11 @@ impl Ptr { let mut borrow = strong.borrow_mut(); sort(&mut borrow, self.get_offset(), last, &mut cmp); } + PtrKind::Nested(ref parent) => { + let root = parent.root(); + let mut borrow = parent.borrow_slice_mut(&*root); + sort(&mut borrow, self.get_offset(), last, &mut cmp); + } PtrKind::StackArray(ref weak) | PtrKind::HeapArray(ref weak) => { let strong = weak.upgrade().expect("ub: dangling pointer"); let mut borrow = strong.borrow_mut(); @@ -1052,7 +1167,21 @@ impl AsPointer for Rc>> { } } -impl Ptr> { +impl AsPointer> for Ptr> { + #[inline] + fn as_pointer(&self) -> Ptr> { + self.clone() + } +} + +impl AsPointer for Ptr> { + #[inline] + fn as_pointer(&self) -> Ptr { + self.decay() + } +} + +impl Ptr> { #[inline] pub fn decay(&self) -> Ptr { match &self.kind { @@ -1065,12 +1194,34 @@ impl Ptr> { offset: self.offset, kind: PtrKind::HeapVec(weak.clone()), }, + PtrKind::StackVec(_) + | PtrKind::HeapVec(_) + | PtrKind::StackArray(_) + | PtrKind::HeapArray(_) + | PtrKind::Nested(_) => Ptr { + offset: 0, + kind: PtrKind::Nested(Rc::new(self.clone())), + }, _ => panic!("ub: invalid decay"), } } } -impl Ptr> { +impl AsPointer> for Ptr> { + #[inline] + fn as_pointer(&self) -> Ptr> { + self.clone() + } +} + +impl AsPointer for Ptr> { + #[inline] + fn as_pointer(&self) -> Ptr { + self.decay() + } +} + +impl Ptr> { #[inline] pub fn decay(&self) -> Ptr { match &self.kind { @@ -1083,6 +1234,14 @@ impl Ptr> { offset: self.offset, kind: PtrKind::HeapArray(weak.clone()), }, + PtrKind::StackVec(_) + | PtrKind::HeapVec(_) + | PtrKind::StackArray(_) + | PtrKind::HeapArray(_) + | PtrKind::Nested(_) => Ptr { + offset: 0, + kind: PtrKind::Nested(Rc::new(self.clone())), + }, _ => panic!("ub: invalid decay"), } } @@ -1111,7 +1270,9 @@ impl ToOwnedOption for Ptr { PtrKind::StackSingle(_) | PtrKind::StackArray(_) => { panic!("Can't own a stack variable") } - PtrKind::StackVec(_) | PtrKind::HeapVec(_) => panic!("Can't own a vector"), + PtrKind::StackVec(_) | PtrKind::HeapVec(_) | PtrKind::Nested(_) => { + panic!("Can't own a vector") + } PtrKind::Field(..) => panic!("Can't own a field"), PtrKind::HeapArray(_) => panic!("Can't own an array variable as single"), PtrKind::Reinterpreted(_) => panic!("Can't own a reinterpreted pointer"), @@ -1138,7 +1299,9 @@ impl ToOwnedOption> for Ptr { PtrKind::StackSingle(_) | PtrKind::StackArray(_) => { panic!("Can't own a stack variable") } - PtrKind::StackVec(_) | PtrKind::HeapVec(_) => panic!("Can't own a vector"), + PtrKind::StackVec(_) | PtrKind::HeapVec(_) | PtrKind::Nested(_) => { + panic!("Can't own a vector") + } PtrKind::Field(..) => panic!("Can't own a field"), PtrKind::HeapSingle(_) => panic!("Can't own a single variable as an array"), PtrKind::Reinterpreted(_) => panic!("Can't own a reinterpreted pointer"), @@ -1159,7 +1322,9 @@ impl fmt::Debug for Ptr { PtrKind::StackVec(w) | PtrKind::HeapVec(w) => { (Weak::as_ptr(w) as usize).wrapping_add(self.byte_offset()) } - PtrKind::Field(..) => self.kind.address().wrapping_add(self.byte_offset()), + PtrKind::Nested(..) | PtrKind::Field(..) => { + self.kind.address().wrapping_add(self.byte_offset()) + } PtrKind::Reinterpreted(data) => data.alloc.address().wrapping_add(self.byte_offset()), }; write!(f, "0x{:x}", addr) diff --git a/libcc2rs/src/reinterpret.rs b/libcc2rs/src/reinterpret.rs index d103561f0..6c7f7e3bb 100644 --- a/libcc2rs/src/reinterpret.rs +++ b/libcc2rs/src/reinterpret.rs @@ -9,7 +9,7 @@ use std::{ }; use crate::field::{Root, upgrade as upgrade_root}; -use crate::rc::Ptr; +use crate::rc::{Ptr, SliceParent}; pub trait ByteRepr: 'static { fn byte_size() -> usize @@ -182,6 +182,7 @@ pub struct OriginalAlloc { enum Storage { Alloc(Weak), Field(Weak, usize), + Nested(Rc, usize), } // The storage passed to the AllocOps of a field. @@ -213,6 +214,13 @@ impl OriginalAlloc { } } + pub(crate) fn nested(parent: &Rc>) -> Self { + Self { + storage: Storage::Nested(Rc::new(Rc::clone(parent)), parent.address()), + ops: &NestedOps::(PhantomData), + } + } + #[inline] fn with_storage(&self, f: impl FnOnce(&dyn Any) -> R) -> R { match &self.storage { @@ -221,6 +229,7 @@ impl OriginalAlloc { root: upgrade_root(root), field: *field, }), + Storage::Nested(parent, _) => f(&**parent), } } @@ -245,6 +254,7 @@ impl OriginalAlloc { Storage::Field(root, field) => { (root.as_ptr() as *const () as usize).wrapping_add(*field) } + Storage::Nested(_, address) => *address, } } @@ -460,3 +470,25 @@ impl AllocOps for FieldOps { T::byte_size() } } + +struct NestedOps(PhantomData T>); + +impl AllocOps for NestedOps { + fn read_bytes(&self, parent: &dyn Any, byte_offset: usize, buf: &mut [u8]) { + let parent = parent.downcast_ref::>>().unwrap(); + let root = parent.root(); + slice_read_bytes(&parent.borrow_slice(&*root), byte_offset, buf); + } + + fn write_bytes(&self, parent: &dyn Any, byte_offset: usize, data: &[u8]) { + let parent = parent.downcast_ref::>>().unwrap(); + let root = parent.root(); + slice_write_bytes(&mut parent.borrow_slice_mut(&*root), byte_offset, data); + } + + fn total_byte_len(&self, parent: &dyn Any) -> usize { + let parent = parent.downcast_ref::>>().unwrap(); + let root = parent.root(); + parent.borrow_slice(&*root).len() * T::byte_size() + } +} diff --git a/rules/deque/src.cpp b/rules/deque/src.cpp index 4850169ff..bbabcdf39 100644 --- a/rules/deque/src.cpp +++ b/rules/deque/src.cpp @@ -22,8 +22,7 @@ template void f4(std::deque &o, T1 &&value) { template void f5(std::deque &o) { return o.pop_front(); } -template -void f7(std::deque> &o, const std::vector &value) { +template void f7(std::deque &o, const T1 &value) { return o.push_back(value); } @@ -50,10 +49,4 @@ T1 &f12(std::deque &o, Init &&...args) { return o.emplace_back(std::forward(args)...); } -template -std::vector &f13(std::deque> &o, - Init, Args> &&...args) { - return o.emplace_back(std::forward(args)...); -} - template std::deque f14() { return std::deque(); } diff --git a/rules/deque/tgt_refcount.rs b/rules/deque/tgt_refcount.rs index b70e9d05d..91f6e7f0d 100644 --- a/rules/deque/tgt_refcount.rs +++ b/rules/deque/tgt_refcount.rs @@ -2,8 +2,6 @@ // Distributed under the MIT license that can be found in the LICENSE file. use libcc2rs::*; -use std::cell::RefCell; -use std::rc::Rc; fn f1(a0: Ptr) -> Ptr { a0.to_last() @@ -13,8 +11,12 @@ fn f2(a0: Ptr) -> Ptr { a0 } -fn f7(a0: Ptr>>>, a1: Value>) { - a0.with_mut(|__v: &mut Vec>>| __v.push(a1)) +fn f10(a0: Ptr>, a1: Vec) { + a0.write(a1) +} + +fn f11(a0: Ptr>, a1: &mut Vec) { + a0.write(std::mem::take(&mut *a1)) } fn f10(a0: Ptr>, a1: Vec) { diff --git a/rules/deque/tgt_unsafe.rs b/rules/deque/tgt_unsafe.rs index bcd63fda9..85a1eafc4 100644 --- a/rules/deque/tgt_unsafe.rs +++ b/rules/deque/tgt_unsafe.rs @@ -33,7 +33,7 @@ unsafe fn f5(a0: &mut Vec) -> T1 { a0.remove(0) } -unsafe fn f7(a0: &mut Vec>, a1: Vec) { +unsafe fn f7(a0: &mut Vec, a1: T1) { a0.push(a1) } @@ -58,11 +58,6 @@ unsafe fn f12(a0: &mut Vec, init: T1) { a0.push(__init) } -unsafe fn f13(a0: &mut Vec>, init: Vec) { - let __init = init; - a0.push(__init) -} - unsafe fn f14() -> Vec { Vec::new() } diff --git a/rules/deque_cxx11/src.cpp b/rules/deque_cxx11/src.cpp index 5df869405..a61657225 100644 --- a/rules/deque_cxx11/src.cpp +++ b/rules/deque_cxx11/src.cpp @@ -10,8 +10,3 @@ template void f1(std::deque &o, Init &&...args) { return o.emplace_back(std::forward(args)...); } - -template -void f2(std::deque> &o, Init, Args> &&...args) { - return o.emplace_back(std::forward(args)...); -} diff --git a/rules/deque_cxx11/tgt_unsafe.rs b/rules/deque_cxx11/tgt_unsafe.rs index b5d238af7..c41e321a0 100644 --- a/rules/deque_cxx11/tgt_unsafe.rs +++ b/rules/deque_cxx11/tgt_unsafe.rs @@ -5,8 +5,3 @@ unsafe fn f1(a0: &mut Vec, init: T1) { let __init = init; a0.push(__init) } - -unsafe fn f2(a0: &mut Vec>, init: Vec) { - let __init = init; - a0.push(__init) -} diff --git a/rules/vector/src.cpp b/rules/vector/src.cpp index 56917b184..d3f7918ed 100644 --- a/rules/vector/src.cpp +++ b/rules/vector/src.cpp @@ -9,7 +9,6 @@ template using t1 = std::vector; template using t2 = typename std::vector::iterator; -template using t3 = std::vector>; template using t4 = typename std::vector::const_iterator; template > @@ -147,25 +146,6 @@ typename std::vector::iterator f28(typename std::vector::iterator a0, return a0.operator++(a1); } -template -std::vector> f29(const std::vector> &&o) { - return std::vector>(std::move(o)); -} - -template std::vector> f30(std::size_t n) { - return std::vector>(n); -} - -template -void f31(std::vector> &o, std::vector &&value) { - return o.push_back(std::move(value)); -} - -template -void f32(std::vector> &o, std::size_t n) { - return o.resize(n); -} - template typename std::vector::iterator::difference_type f33(const typename std::vector::iterator &it1, @@ -236,11 +216,6 @@ template const T1 &f51(const std::vector &o) { return o.back(); } -template -void f52(std::vector> &o, const std::vector &value) { - return o.push_back(value); -} - template typename std::vector::iterator f53(std::vector &o, typename std::vector::const_iterator pos, @@ -259,10 +234,6 @@ std::vector &f55(std::vector &dst, std::vector &&src) { return dst.operator=(std::move(src)); } -template std::vector &f56(std::vector> &o) { - return o.back(); -} - template typename std::vector::const_iterator f57(const std::vector &o) { return o.cend(); @@ -494,12 +465,6 @@ const T1 &f99(const std::vector &o) { return o.back(); } -template > -void f100(std::vector> &o, - const std::vector &value) { - return o.push_back(value); -} - template > typename std::vector::iterator f101(std::vector &o, typename std::vector::const_iterator pos, @@ -553,23 +518,11 @@ std::vector f110(const std::vector &o) { return std::vector(o); } -template -std::vector> &f111(std::vector> &dst, - std::vector> &&src) { - return dst.operator=(std::move(src)); -} - template T1 &f112(std::vector &o, Init &&...args) { return o.emplace_back(std::forward(args)...); } -template -std::vector &f113(std::vector> &o, - Init, Args> &&...args) { - return o.emplace_back(std::forward(args)...); -} - template , typename... Args> T1 &f114(std::vector &o, Init &&...args) { return o.emplace_back(std::forward(args)...); diff --git a/rules/vector/tgt_refcount.rs b/rules/vector/tgt_refcount.rs index d380838ed..30fccec46 100644 --- a/rules/vector/tgt_refcount.rs +++ b/rules/vector/tgt_refcount.rs @@ -2,17 +2,11 @@ // Distributed under the MIT license that can be found in the LICENSE file. use libcc2rs::*; -use std::cell::RefCell; -use std::rc::Rc; fn t2() -> Ptr { Ptr::null() } -fn t3() -> Vec>> { - Vec::new() -} - fn t4() -> Ptr { Ptr::null() } @@ -97,29 +91,6 @@ fn f28(a0: &mut Ptr) -> Ptr { a0.postfix_inc() } -fn f29(a0: Vec>>) -> Vec>> { - a0.iter() - .map(|inner_vec| Rc::new(RefCell::new(inner_vec.borrow().clone()))) - .collect() -} - -fn f30(a0: usize) -> Vec>> { - (0..(a0) as usize) - .map(|_| >>::default()) - .collect::>() -} - -fn f31(a0: Ptr>>>, a1: &mut Vec) { - a0.with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(std::mem::take(&mut *a1)))) - }) -} - -fn f32(a0: Ptr>>>, a1: usize) { - let _a0 = a1 as usize; - a0.with_mut(|__v: &mut Vec>>| __v.resize_with(_a0, >>::default)) -} - fn f33(a0: Ptr, a1: Ptr) -> isize { ((a0.get_offset() as isize) - (a1.get_offset() as isize)) } @@ -181,10 +152,6 @@ fn f51(a0: Ptr) -> Ptr { a0.to_last() } -fn f52(a0: Ptr>>>, a1: Vec) { - a0.with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(a1)))) -} - fn f53( a0: Ptr>, a1: Ptr, @@ -200,10 +167,6 @@ fn f53( a0 + start_idx } -fn f56(a0: &Vec>>) -> Ptr> { - a0[a0.len() - 1].as_pointer() -} - fn f55(a0: Ptr>, a1: &mut Vec) { a0.write(std::mem::take(&mut *a1)) } @@ -365,12 +328,3 @@ fn f104(a0: Ptr) -> Ptr { fn f105(a0: Ptr>, a1: Vec) { a0.write(a1) } - -fn f111(a0: Ptr>>>, a1: &mut Vec>>) { - a0.write(std::mem::take(&mut *a1)) -} - -fn f113(a0: Ptr>>>, init: Vec) { - let __init = init; - a0.with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(__init)))) -} diff --git a/rules/vector/tgt_unsafe.rs b/rules/vector/tgt_unsafe.rs index 64809e678..ad8092f99 100644 --- a/rules/vector/tgt_unsafe.rs +++ b/rules/vector/tgt_unsafe.rs @@ -11,10 +11,6 @@ fn t2() -> *mut T1 { Default::default() } -fn t3() -> Vec> { - Vec::new() -} - fn t4() -> *const T1 { Default::default() } @@ -138,21 +134,6 @@ unsafe fn f27(a0: *const T1, a1: *const T1) -> bool { unsafe fn f28(a0: &mut *mut T1) -> *mut T1 { a0.postfix_inc() } -unsafe fn f29(a0: Vec>) -> Vec> { - a0.clone() -} - -unsafe fn f30(a0: usize) -> Vec> { - (0..(a0) as usize) - .map(|_| >::default()) - .collect::>() -} -unsafe fn f31(a0: &mut Vec>, a1: &mut Vec) { - a0.push(std::mem::take(&mut *a1)) -} -unsafe fn f32(a0: &mut Vec>, a1: usize) { - a0.resize_with(a1 as usize, || >::default()) -} unsafe fn f33(a0: *const T1, a1: *const T1) -> isize { a0.offset_from(a1) @@ -226,10 +207,6 @@ unsafe fn f51(a0: &Vec) -> &T1 { ((a0).last().unwrap()) } -unsafe fn f52(a0: &mut Vec>, a1: Vec) { - a0.push(a1.clone()) -} - unsafe fn f53(a0: &mut Vec, a1: *const T1, a2: *const T1, a3: *const T1) -> *mut T1 { let __off = a1.offset_from(a0.as_ptr()) as usize; let count = a3.offset_from(a2) as usize; @@ -249,10 +226,6 @@ unsafe fn f55(a0: &mut Vec, a1: &mut Vec) { *a0 = std::mem::take(&mut *a1) } -unsafe fn f56(a0: &mut Vec>) -> *mut Vec { - ((a0).last_mut().unwrap()) -} - unsafe fn f57(a0: Vec) -> *const T1 { a0.as_ptr().add(a0.len()) } @@ -447,10 +420,6 @@ unsafe fn f99(a0: &Vec) -> &T1 { ((a0).last().unwrap()) } -unsafe fn f100(a0: &mut Vec>, a1: Vec) { - a0.push(a1.clone()) -} - unsafe fn f101( a0: &mut Vec, a1: *const T1, @@ -503,20 +472,11 @@ unsafe fn f110(a0: Vec) -> Vec { a0.clone() } -unsafe fn f111(a0: &mut Vec>, a1: &mut Vec>) { - *a0 = std::mem::take(&mut *a1) -} - unsafe fn f112(a0: &mut Vec, init: T1) { let __init = init; a0.push(__init) } -unsafe fn f113(a0: &mut Vec>, init: Vec) { - let __init = init; - a0.push(__init) -} - unsafe fn f114(a0: &mut Vec, init: T1) { let __init = init; a0.push(__init) diff --git a/rules/vector_cxx11/src.cpp b/rules/vector_cxx11/src.cpp index 8cf783dce..9262acfba 100644 --- a/rules/vector_cxx11/src.cpp +++ b/rules/vector_cxx11/src.cpp @@ -10,12 +10,6 @@ void f1(std::vector &o, Init &&...args) { return o.emplace_back(std::forward(args)...); } -template -void f2(std::vector> &o, - Init, Args> &&...args) { - return o.emplace_back(std::forward(args)...); -} - template , typename... Args> void f3(std::vector &o, Init &&...args) { return o.emplace_back(std::forward(args)...); diff --git a/rules/vector_cxx11/tgt_unsafe.rs b/rules/vector_cxx11/tgt_unsafe.rs index 34c9cf8b4..57802c699 100644 --- a/rules/vector_cxx11/tgt_unsafe.rs +++ b/rules/vector_cxx11/tgt_unsafe.rs @@ -6,11 +6,6 @@ unsafe fn f1(a0: &mut Vec, init: T1) { a0.push(__init) } -unsafe fn f2(a0: &mut Vec>, init: Vec) { - let __init = init; - a0.push(__init) -} - unsafe fn f3(a0: &mut Vec, init: T1) { let __init = init; a0.push(__init) diff --git a/tests/ub/out/refcount/row_ptr_outlives_array.rs b/tests/ub/out/refcount/row_ptr_outlives_array.rs new file mode 100644 index 000000000..b98f54202 --- /dev/null +++ b/tests/ub/out/refcount/row_ptr_outlives_array.rs @@ -0,0 +1,26 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn second_row_0() -> Ptr { + let rows: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([1, 2, 3]), + Box::new([4, 5, 6]), + ]))); + return (((rows.as_pointer() as Ptr>) + .offset(1) + .as_pointer()) as Ptr); +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let mut p: Ptr = ({ second_row_0() }); + return (elem!(p, 0).read()); +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/refcount/row_ptr_outlives_vector.rs b/tests/ub/out/refcount/row_ptr_outlives_vector.rs new file mode 100644 index 000000000..f3f7de668 --- /dev/null +++ b/tests/ub/out/refcount/row_ptr_outlives_vector.rs @@ -0,0 +1,23 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn second_row_0() -> Ptr { + let rows: Value>> = Rc::new(RefCell::new(vec![vec![1, 2, 3], vec![4, 5, 6]])); + return ((rows.as_pointer() as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr); +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let mut p: Ptr = ({ second_row_0() }); + return (elem!(p, 0).read()); +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/refcount/row_ptr_past_row_end.rs b/tests/ub/out/refcount/row_ptr_past_row_end.rs new file mode 100644 index 000000000..9114480d6 --- /dev/null +++ b/tests/ub/out/refcount/row_ptr_past_row_end.rs @@ -0,0 +1,23 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let rows: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([1, 2, 3]), + Box::new([4, 5, 6]), + ]))); + let mut p: Ptr = (((rows.as_pointer() as Ptr>) + .offset(0) + .as_pointer()) as Ptr); + return (elem!(p, 3).read()); +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/unsafe/row_ptr_outlives_array.rs b/tests/ub/out/unsafe/row_ptr_outlives_array.rs new file mode 100644 index 000000000..624517491 --- /dev/null +++ b/tests/ub/out/unsafe/row_ptr_outlives_array.rs @@ -0,0 +1,23 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn second_row_0() -> *mut i32 { + let mut rows: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; + return rows[(1) as usize].as_mut_ptr(); +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut p: *mut i32 = (unsafe { second_row_0() }); + return (*p.offset((0) as isize)); +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/unsafe/row_ptr_outlives_vector.rs b/tests/ub/out/unsafe/row_ptr_outlives_vector.rs new file mode 100644 index 000000000..51b8fbe4d --- /dev/null +++ b/tests/ub/out/unsafe/row_ptr_outlives_vector.rs @@ -0,0 +1,23 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn second_row_0() -> *mut i32 { + let mut rows: Vec> = vec![vec![1, 2, 3], vec![4, 5, 6]]; + return rows[(1_usize)].as_mut_ptr(); +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut p: *mut i32 = (unsafe { second_row_0() }); + return (*p.offset((0) as isize)); +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/unsafe/row_ptr_past_row_end.rs b/tests/ub/out/unsafe/row_ptr_past_row_end.rs new file mode 100644 index 000000000..63002e23b --- /dev/null +++ b/tests/ub/out/unsafe/row_ptr_past_row_end.rs @@ -0,0 +1,20 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut rows: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; + let mut p: *mut i32 = rows[(0) as usize].as_mut_ptr(); + return (*p.offset((3) as isize)); +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/ub/row_ptr_outlives_array.cpp b/tests/ub/row_ptr_outlives_array.cpp new file mode 100644 index 000000000..1b552f4a6 --- /dev/null +++ b/tests/ub/row_ptr_outlives_array.cpp @@ -0,0 +1,10 @@ +// panic-ub: refcount +// nondet-result: unsafe +int *second_row() { + int rows[2][3] = {{1, 2, 3}, {4, 5, 6}}; + return rows[1]; +} +int main() { + int *p = second_row(); + return p[0]; +} diff --git a/tests/ub/row_ptr_outlives_vector.cpp b/tests/ub/row_ptr_outlives_vector.cpp new file mode 100644 index 000000000..786ba9076 --- /dev/null +++ b/tests/ub/row_ptr_outlives_vector.cpp @@ -0,0 +1,12 @@ +// panic-ub: refcount +// nondet-result: unsafe +#include + +int *second_row() { + std::vector> rows = {{1, 2, 3}, {4, 5, 6}}; + return rows[1].data(); +} +int main() { + int *p = second_row(); + return p[0]; +} diff --git a/tests/ub/row_ptr_past_row_end.cpp b/tests/ub/row_ptr_past_row_end.cpp new file mode 100644 index 000000000..4cb3f2d1d --- /dev/null +++ b/tests/ub/row_ptr_past_row_end.cpp @@ -0,0 +1,7 @@ +// panic-ub: refcount +// nondet-result: unsafe +int main() { + int rows[2][3] = {{1, 2, 3}, {4, 5, 6}}; + int *p = rows[0]; + return p[3]; +} diff --git a/tests/unit/array_reference.cpp b/tests/unit/array_reference.cpp index b86f967fa..1a4af15b3 100644 --- a/tests/unit/array_reference.cpp +++ b/tests/unit/array_reference.cpp @@ -1,4 +1,5 @@ #include +#include int len(const char (&s)[5]) { int n = 0; @@ -59,6 +60,8 @@ int total_len(const char *(&names)[2]) { return len5(names[0]) + len5(names[1]); } +const int kTable[2][3] = {{1, 2, 3}, {4, 5, 6}}; + int main() { assert(len("beta") == 4); char buf[5] = "abcd"; @@ -101,5 +104,16 @@ int main() { int (*q)[3] = rows; assert(sum(q[1]) == 15); assert(q[1] == &rows[1][0]); + q[1][2] = 8; + assert(rows[1][2] == 8 && rows[0][2] == 3); + rows[0][1] = rows[0][2]; + q[1][0] = q[0][1]; + q[1][1] += q[0][2]; + assert(rows[0][1] == 3 && rows[1][0] == 3 && rows[1][1] == 8); + + int key[4] = {0, 4, 5, 6}; + assert(memcmp(&key[1], kTable[1], sizeof(kTable[1])) == 0); + assert(memcmp(&key[1], kTable[0], sizeof(kTable[0])) != 0); + assert(kTable[1][2] == 6 && kTable[0][1] == 2); return 0; } diff --git a/tests/unit/vector_subscript_direct.cpp b/tests/unit/vector_subscript_direct.cpp index 20601121a..1858dbf24 100644 --- a/tests/unit/vector_subscript_direct.cpp +++ b/tests/unit/vector_subscript_direct.cpp @@ -76,6 +76,9 @@ int main() { grid.push_back(std::vector(3, 0)); grid[1][2] = 8; assert(grid[1][2] == 8 && grid[0][2] == 0); + grid[0][1] = grid[1][2]; + grid[1][0] += grid[0][1]; + assert(grid[0][1] == 8 && grid[1][0] == 8); std::array a = {4, 5, 6}; a[1] = a[0] + a[2]; From 297622662e58c4fa0285415127e56c0805780066 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 16:27:07 +0100 Subject: [PATCH 2/7] Update rules --- rules/deque/tgt_refcount.rs | 13 ------------- rules/deque_cxx11/tgt_refcount.rs | 11 ----------- rules/vector_cxx11/tgt_refcount.rs | 11 ----------- 3 files changed, 35 deletions(-) delete mode 100644 rules/deque_cxx11/tgt_refcount.rs delete mode 100644 rules/vector_cxx11/tgt_refcount.rs diff --git a/rules/deque/tgt_refcount.rs b/rules/deque/tgt_refcount.rs index 91f6e7f0d..107fa289c 100644 --- a/rules/deque/tgt_refcount.rs +++ b/rules/deque/tgt_refcount.rs @@ -18,16 +18,3 @@ fn f10(a0: Ptr>, a1: Vec) { fn f11(a0: Ptr>, a1: &mut Vec) { a0.write(std::mem::take(&mut *a1)) } - -fn f10(a0: Ptr>, a1: Vec) { - a0.write(a1) -} - -fn f11(a0: Ptr>, a1: &mut Vec) { - a0.write(std::mem::take(&mut *a1)) -} - -fn f13(a0: Ptr>>>, init: Vec) { - let __init = init; - a0.with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(__init)))) -} diff --git a/rules/deque_cxx11/tgt_refcount.rs b/rules/deque_cxx11/tgt_refcount.rs deleted file mode 100644 index f7ac46cf4..000000000 --- a/rules/deque_cxx11/tgt_refcount.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2022-present INESC-ID. -// Distributed under the MIT license that can be found in the LICENSE file. - -use libcc2rs::*; -use std::cell::RefCell; -use std::rc::Rc; - -fn f2(a0: Ptr>>>, init: Vec) { - let __init = init; - a0.with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(__init)))) -} diff --git a/rules/vector_cxx11/tgt_refcount.rs b/rules/vector_cxx11/tgt_refcount.rs deleted file mode 100644 index 061e768fb..000000000 --- a/rules/vector_cxx11/tgt_refcount.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2022-present INESC-ID. -// Distributed under the MIT license that can be found in the LICENSE file. - -use libcc2rs::*; -use std::cell::RefCell; -use std::rc::Rc; - -fn f2(a0: Ptr>>>, init: Vec) { - let __init = init; - a0.with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(__init)))) -} From 47d27698f624627616495e6820ed75c859e91d14 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 16:29:31 +0100 Subject: [PATCH 3/7] Update tests --- tests/unit/out/refcount/12_test.rs | 15 ++- tests/unit/out/refcount/addr_of_global.rs | 16 ++-- tests/unit/out/refcount/array_2d_default.rs | 30 +++--- tests/unit/out/refcount/array_reference.rs | 92 ++++++++++++++++--- tests/unit/out/refcount/clone_vs_move.rs | 60 ++++-------- .../unit/out/refcount/copy_move_defaulted.rs | 25 ++--- tests/unit/out/refcount/destructor.rs | 12 +-- tests/unit/out/refcount/foreach_double.rs | 32 +++---- .../out/refcount/global_non_const_init.rs | 8 +- tests/unit/out/refcount/global_pointers.rs | 4 +- tests/unit/out/refcount/ignored_rule.rs | 6 +- tests/unit/out/refcount/nested_vector.rs | 11 ++- tests/unit/out/refcount/null_in_statics.rs | 8 +- .../operator_member_pointer_member.rs | 4 +- .../unit/out/refcount/pointer_usize_arith.rs | 11 +-- tests/unit/out/refcount/push_emplace_back.rs | 62 +++++-------- tests/unit/out/refcount/string_escape.rs | 4 +- .../out/refcount/struct_2d_array_field.rs | 53 +++++------ tests/unit/out/refcount/vector3.rs | 40 +++----- .../unit/out/refcount/vector_addr_of_back.rs | 15 +-- .../out/refcount/vector_subscript_direct.rs | 76 +++++++++++---- tests/unit/out/unsafe/12_test.rs | 9 +- tests/unit/out/unsafe/array_reference.rs | 68 +++++++++++++- tests/unit/out/unsafe/clone_vs_move.rs | 9 +- tests/unit/out/unsafe/copy_move_defaulted.rs | 5 +- tests/unit/out/unsafe/foreach_double.rs | 15 ++- tests/unit/out/unsafe/nested_vector.rs | 5 +- tests/unit/out/unsafe/push_emplace_back.rs | 14 ++- tests/unit/out/unsafe/vector3.rs | 5 +- tests/unit/out/unsafe/vector_addr_of_back.rs | 5 +- 30 files changed, 419 insertions(+), 300 deletions(-) diff --git a/tests/unit/out/refcount/12_test.rs b/tests/unit/out/refcount/12_test.rs index a756517e3..492fe1f82 100644 --- a/tests/unit/out/refcount/12_test.rs +++ b/tests/unit/out/refcount/12_test.rs @@ -11,14 +11,13 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let v: Value>>> = Rc::new(RefCell::new(Vec::new())); - (v.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new( - (0..(10_usize) as usize) - .map(|_| ::default()) - .collect::>(), - ))) - }); + let v: Value>> = Rc::new(RefCell::new(Vec::new())); + { + let __a1 = (0..(10_usize) as usize) + .map(|_| ::default()) + .collect::>(); + (*v.borrow_mut()).push(__a1) + }; return 0; } pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/refcount/addr_of_global.rs b/tests/unit/out/refcount/addr_of_global.rs index 854475880..55b55ef76 100644 --- a/tests/unit/out/refcount/addr_of_global.rs +++ b/tests/unit/out/refcount/addr_of_global.rs @@ -46,16 +46,16 @@ pub fn main() { fn main_0() -> i32 { assert!( (({ - let __idx = (0) as usize; - items_3.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (0) as usize; + items_3.with(|rc| rc.borrow()[__idx0].clone()) }) .with(|__s| __s.value) == 1) ); assert!( (({ - let __idx = (1) as usize; - items_3.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (1) as usize; + items_3.with(|rc| rc.borrow()[__idx0].clone()) }) .with(|__s| __s.value) == 2) @@ -63,16 +63,16 @@ fn main_0() -> i32 { assert!(({ (*obj_4.with(Value::clone).borrow()).p.clone() }.with(|__s| __s.value) == 42)); assert!( (({ - let __idx = (0) as usize; - cache_5.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (0) as usize; + cache_5.with(|rc| rc.borrow()[__idx0].clone()) }) .with(|__s| __s.value) == 1) ); assert!( (({ - let __idx = (1) as usize; - cache_5.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (1) as usize; + cache_5.with(|rc| rc.borrow()[__idx0].clone()) }) .with(|__s| __s.value) == 2) diff --git a/tests/unit/out/refcount/array_2d_default.rs b/tests/unit/out/refcount/array_2d_default.rs index 84f0af9dc..44e29720d 100644 --- a/tests/unit/out/refcount/array_2d_default.rs +++ b/tests/unit/out/refcount/array_2d_default.rs @@ -15,44 +15,36 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let grid: Value>]>> = Rc::new(RefCell::new( + let grid: Value]>> = Rc::new(RefCell::new( (0..3) - .map(|_| Rc::new(RefCell::new((0..6).map(|_| 0_i8).collect::>()))) - .collect::>]>>(), + .map(|_| (0..6).map(|_| 0_i8).collect::>()) + .collect::]>>(), )); let mut i: i32 = 0; 'loop_: while (((i < 3) as i32) != 0) { ({ - let _row: Ptr = (((grid.as_pointer() as Ptr>>) - .offset(i) - .read() - .as_pointer()) as Ptr); + let _row: Ptr = + (((grid.as_pointer() as Ptr>).offset(i).as_pointer()) as Ptr); let _c: i8 = ((('a' as i32) + i) as i8); fill_row_0(_row, _c) }); i.postfix_inc(); } assert!( - (((((*grid.borrow())[(0) as usize].borrow()[(0) as usize] as i32) == ('a' as i32)) as i32) - != 0) + (((((*grid.borrow())[(0) as usize][(0) as usize] as i32) == ('a' as i32)) as i32) != 0) ); assert!( - (((((*grid.borrow())[(1) as usize].borrow()[(0) as usize] as i32) == ('b' as i32)) as i32) - != 0) + (((((*grid.borrow())[(1) as usize][(0) as usize] as i32) == ('b' as i32)) as i32) != 0) ); assert!( - (((((*grid.borrow())[(2) as usize].borrow()[(0) as usize] as i32) == ('c' as i32)) as i32) - != 0) + (((((*grid.borrow())[(2) as usize][(0) as usize] as i32) == ('c' as i32)) as i32) != 0) ); assert!( - (((((*grid.borrow())[(1) as usize].borrow()[(1) as usize] as i32) == ('\0' as i32)) - as i32) - != 0) + (((((*grid.borrow())[(1) as usize][(1) as usize] as i32) == ('\0' as i32)) as i32) != 0) ); - (*grid.borrow())[(2) as usize].borrow_mut()[(5) as usize] = (('z' as i32) as i8); + (*grid.borrow_mut())[(2) as usize][(5) as usize] = (('z' as i32) as i8); assert!( - (((((*grid.borrow())[(2) as usize].borrow()[(5) as usize] as i32) == ('z' as i32)) as i32) - != 0) + (((((*grid.borrow())[(2) as usize][(5) as usize] as i32) == ('z' as i32)) as i32) != 0) ); return 0; } diff --git a/tests/unit/out/refcount/array_reference.rs b/tests/unit/out/refcount/array_reference.rs index a7ca6c1f7..dd3bcd693 100644 --- a/tests/unit/out/refcount/array_reference.rs +++ b/tests/unit/out/refcount/array_reference.rs @@ -89,6 +89,12 @@ pub fn shift_points_12(p: Ptr, mut d: i32) { pub fn total_len_13(names: Ptr>) -> i32 { return (({ len5_1((elem!((names), 0).read())) }) + ({ len5_1((elem!((names), 1).read())) })); } +thread_local!( + pub static kTable_14: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([1, 2, 3]), + Box::new([4, 5, 6]), + ]))); +); pub fn main() { __cpp2rust_init_globals(); std::process::exit(main_0()); @@ -136,36 +142,92 @@ fn main_0() -> i32 { Ptr::::from_string_literal(b"cde"), ]))); assert!((({ total_len_13(names.as_pointer(),) }) == 5)); - let rows: Value>]>> = Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(Box::new([1, 2, 3]))), - Rc::new(RefCell::new(Box::new([4, 5, 6]))), + let rows: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([1, 2, 3]), + Box::new([4, 5, 6]), ]))); - let mut p: Ptr>> = (rows.as_pointer() as Ptr>>); + let mut p: Ptr> = (rows.as_pointer() as Ptr>); p.prefix_inc(); assert!( - ({ ((p).read().as_pointer() as Ptr::).clone() } == { - ((((rows.as_pointer() as Ptr>>) + ({ ((p).as_pointer() as Ptr::).clone() } == { + ((((rows.as_pointer() as Ptr>) .offset(1) - .read() .as_pointer()) as Ptr) .offset(0)) }) ); - let r: Ptr = ((p).read().as_pointer()).clone(); + let r: Ptr = ((p).as_pointer()).clone(); assert!(((elem!((r), 0).read()) == 4)); assert!((({ sum_2((r).clone(),) }) == 15)); - assert!((({ sum_2(((p).read().as_pointer()).clone(),) }) == 15)); - let mut q: Ptr>> = (rows.as_pointer() as Ptr>>); - assert!((({ sum_2(q.offset((1) as isize).read().as_pointer(),) }) == 15)); + assert!((({ sum_2(((p).as_pointer()).clone(),) }) == 15)); + let mut q: Ptr> = (rows.as_pointer() as Ptr>); + assert!((({ sum_2(q.offset((1) as isize).as_pointer(),) }) == 15)); assert!( - ({ (q.offset((1) as isize).read().as_pointer() as Ptr::) } == { - ((((rows.as_pointer() as Ptr>>) + ({ (q.offset((1) as isize).as_pointer() as Ptr::) } == { + ((((rows.as_pointer() as Ptr>) .offset(1) - .read() .as_pointer()) as Ptr) .offset(0)) }) ); + elem!((q.offset((1) as isize).as_pointer() as Ptr::), 2).write(8); + assert!( + ((*rows.borrow())[(1) as usize][(2) as usize] == 8) + && ((*rows.borrow())[(0) as usize][(2) as usize] == 3) + ); + (*rows.borrow_mut())[(0) as usize][(1) as usize] = + { (*rows.borrow())[(0) as usize][(2) as usize] }; + elem!((q.offset((1) as isize).as_pointer() as Ptr::), 0) + .write({ (elem!((q.offset((0) as isize).as_pointer() as Ptr::), 1).read()) }); + { + let __rhs = { (elem!((q.offset((0) as isize).as_pointer() as Ptr::), 2).read()) }; + elem!((q.offset((1) as isize).as_pointer() as Ptr::), 1) + .with_mut(|__v| *__v = *__v + __rhs) + }; + assert!( + (((*rows.borrow())[(0) as usize][(1) as usize] == 3) + && ((*rows.borrow())[(1) as usize][(0) as usize] == 3)) + && ((*rows.borrow())[(1) as usize][(1) as usize] == 8) + ); + let key: Value> = Rc::new(RefCell::new(Box::new([0, 4, 5, 6]))); + assert!( + ((((key.as_pointer() as Ptr).offset(1)) as Ptr::) + .to_any() + .memcmp( + &((((kTable_14.with(|v| v.as_pointer()) as Ptr>) + .offset(1) + .as_pointer()) as Ptr::) as Ptr::) + .to_any(), + ::std::mem::size_of::<[i32; 3]>() + ) + == 0) + ); + assert!( + ((((key.as_pointer() as Ptr).offset(1)) as Ptr::) + .to_any() + .memcmp( + &((((kTable_14.with(|v| v.as_pointer()) as Ptr>) + .offset(0) + .as_pointer()) as Ptr::) as Ptr::) + .to_any(), + ::std::mem::size_of::<[i32; 3]>() + ) + != 0) + ); + assert!( + (({ + let __idx0 = (1) as usize; + let __idx1 = (2) as usize; + kTable_14.with(|rc| rc.borrow()[__idx0][__idx1]) + }) == 6) + && (({ + let __idx0 = (0) as usize; + let __idx1 = (1) as usize; + kTable_14.with(|rc| rc.borrow()[__idx0][__idx1]) + }) == 2) + ); return 0; } -pub fn __cpp2rust_init_globals() {} +pub fn __cpp2rust_init_globals() { + let _ = kTable_14.with(|_| ()); +} diff --git a/tests/unit/out/refcount/clone_vs_move.rs b/tests/unit/out/refcount/clone_vs_move.rs index 4909dfc3e..e542b1edf 100644 --- a/tests/unit/out/refcount/clone_vs_move.rs +++ b/tests/unit/out/refcount/clone_vs_move.rs @@ -146,33 +146,23 @@ fn main_0() -> i32 { assert!((v1[(i as usize)] == i)); i.prefix_inc(); } - let m1: Value>>> = Rc::new(RefCell::new(Vec::new())); + let m1: Value>> = Rc::new(RefCell::new(Vec::new())); let mut i: i32 = 0; 'loop_: while (i < N) { - (m1.as_pointer() as Ptr>>>).with_mut( - |__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new( - (0..(10_usize) as usize) - .map(|_| ::default()) - .collect::>(), - ))) - }, - ); + { + let __a1 = (0..(10_usize) as usize) + .map(|_| ::default()) + .collect::>(); + (*m1.borrow_mut()).push(__a1) + }; i.prefix_inc(); } - let m2: Value>>> = Rc::new(RefCell::new( - (*m1.borrow()) - .iter() - .map(|inner_vec| Rc::new(RefCell::new(inner_vec.borrow().clone()))) - .collect(), - )); + let m2: Value>> = Rc::new(RefCell::new((*m1.borrow()).clone())); let mut i: i32 = 0; 'loop_: while (i < N) { assert!( - ((*((m1.as_pointer() as Ptr>>) + ((*((m1.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -180,10 +170,8 @@ fn main_0() -> i32 { == 10_usize) ); assert!( - ((*((m2.as_pointer() as Ptr>>) + ((*((m2.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -194,10 +182,8 @@ fn main_0() -> i32 { 'loop_: while (j < 10) { assert!( ((elem!( - ((m1.as_pointer() as Ptr>>) + ((m1.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr), (j as usize) ) @@ -206,10 +192,8 @@ fn main_0() -> i32 { ); assert!( ((elem!( - ((m2.as_pointer() as Ptr>>) + ((m2.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr), (j as usize) ) @@ -225,10 +209,8 @@ fn main_0() -> i32 { let mut j: i32 = 0; 'loop_: while (j < 10) { elem!( - ((m2.as_pointer() as Ptr>>) + ((m2.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr), (j as usize) ) @@ -240,10 +222,8 @@ fn main_0() -> i32 { let mut i: i32 = 0; 'loop_: while (i < N) { assert!( - ((*((m1.as_pointer() as Ptr>>) + ((*((m1.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -251,10 +231,8 @@ fn main_0() -> i32 { == 10_usize) ); assert!( - ((*((m2.as_pointer() as Ptr>>) + ((*((m2.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -265,10 +243,8 @@ fn main_0() -> i32 { 'loop_: while (j < 10) { assert!( ((elem!( - ((m1.as_pointer() as Ptr>>) + ((m1.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr), (j as usize) ) @@ -277,10 +253,8 @@ fn main_0() -> i32 { ); assert!( ((elem!( - ((m2.as_pointer() as Ptr>>) + ((m2.as_pointer() as Ptr>) .offset((i as usize)) - .upgrade() - .deref() .as_pointer() as Ptr), (j as usize) ) diff --git a/tests/unit/out/refcount/copy_move_defaulted.rs b/tests/unit/out/refcount/copy_move_defaulted.rs index 6841629f9..2bf9f951f 100644 --- a/tests/unit/out/refcount/copy_move_defaulted.rs +++ b/tests/unit/out/refcount/copy_move_defaulted.rs @@ -121,7 +121,7 @@ pub struct Buffer { pub data: Value>, #[offset(24)] #[byte_size(24)] - pub rows: Value>>>, + pub rows: Value>>, #[offset(48)] pub n: i32, #[offset(52)] @@ -137,13 +137,10 @@ impl Buffer { arr: Rc::new(RefCell::new(Box::new([n, (n + 1)]))), })); let this: Ptr = __this.as_pointer(); - (this.with(|__s| __s.rows.as_pointer()) as Ptr>>>).with_mut( - |__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new( - (*this.with(|__s| __s.data.clone()).borrow()).clone(), - ))) - }, - ); + { + let a0_clone = (*this.with(|__s| __s.data.clone()).borrow()).clone(); + (*this.with(|__s| __s.rows.clone()).borrow_mut()).push(a0_clone) + }; Rc::try_unwrap(__this).ok().unwrap().into_inner() } pub fn move_from(_a0: Ptr) -> Self { @@ -166,7 +163,7 @@ impl Default for Buffer { { Buffer { data: Rc::new(RefCell::new(Default::default())), - rows: Rc::new(RefCell::new(Vec::new())), + rows: Rc::new(RefCell::new(Default::default())), n: 0_i32, arr: Rc::new(RefCell::new((0..2).map(|_| 0_i32).collect::>())), } @@ -361,10 +358,8 @@ fn main_0() -> i32 { ); assert!( (((*{ (*r.borrow()).rows.clone() }.borrow()).len() == 1_usize) - && ((*(({ (*r.borrow()).rows.as_pointer() } as Ptr>>) + && ((*(({ (*r.borrow()).rows.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -458,9 +453,9 @@ impl BufferImpl for Ptr { ((*self).with(|__s| __s.data.as_pointer()) as Ptr>).write(std::mem::take( &mut (*{ (*_a0.upgrade().deref()).data.clone() }.borrow_mut()), )); - ((*self).with(|__s| __s.rows.as_pointer()) as Ptr>>>).write( - std::mem::take(&mut (*{ (*_a0.upgrade().deref()).rows.clone() }.borrow_mut())), - ); + ((*self).with(|__s| __s.rows.as_pointer()) as Ptr>>).write(std::mem::take( + &mut (*{ (*_a0.upgrade().deref()).rows.clone() }.borrow_mut()), + )); field!((*self), n).write({ { (*_a0.upgrade().deref()).n } }); { ((array_field_ptr!((*self), arr)) as Ptr) diff --git a/tests/unit/out/refcount/destructor.rs b/tests/unit/out/refcount/destructor.rs index 80d970846..62a6b62c7 100644 --- a/tests/unit/out/refcount/destructor.rs +++ b/tests/unit/out/refcount/destructor.rs @@ -169,20 +169,20 @@ fn main_0() -> i32 { assert!((order_count_2.with(|rc| *rc.borrow()) == 3)); assert!( (({ - let __idx = (0) as usize; - order_1.with(|rc| rc.borrow()[__idx]) + let __idx0 = (0) as usize; + order_1.with(|rc| rc.borrow()[__idx0]) }) == 3) ); assert!( (({ - let __idx = (1) as usize; - order_1.with(|rc| rc.borrow()[__idx]) + let __idx0 = (1) as usize; + order_1.with(|rc| rc.borrow()[__idx0]) }) == 2) ); assert!( (({ - let __idx = (2) as usize; - order_1.with(|rc| rc.borrow()[__idx]) + let __idx0 = (2) as usize; + order_1.with(|rc| rc.borrow()[__idx0]) }) == 1) ); let mut before: i32 = global_0.with(|rc| *rc.borrow()); diff --git a/tests/unit/out/refcount/foreach_double.rs b/tests/unit/out/refcount/foreach_double.rs index f1ea9f2cf..293f327e3 100644 --- a/tests/unit/out/refcount/foreach_double.rs +++ b/tests/unit/out/refcount/foreach_double.rs @@ -47,27 +47,23 @@ fn main_0() -> i32 { square += { ({ (e1.read()) } * { (e2.read()) }) }; } } - let m: Value>>> = Rc::new(RefCell::new(Vec::new())); + let m: Value>> = Rc::new(RefCell::new(Vec::new())); let v1: Value> = Rc::new(RefCell::new(Vec::new())); - (m.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(std::mem::take( - &mut (*v1.borrow_mut()), - )))) - }); + { + let __a1 = std::mem::take(&mut (*v1.borrow_mut())); + (*m.borrow_mut()).push(__a1) + }; let v2: Value> = Rc::new(RefCell::new(Vec::new())); - (m.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(std::mem::take( - &mut (*v2.borrow_mut()), - )))) - }); + { + let __a1 = std::mem::take(&mut (*v2.borrow_mut())); + (*m.borrow_mut()).push(__a1) + }; let v3: Value> = Rc::new(RefCell::new(Vec::new())); - (m.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(std::mem::take( - &mut (*v3.borrow_mut()), - )))) - }); - 'loop_: for mut row in m.as_pointer() as Ptr>> { - let row: Ptr> = row.upgrade().deref().as_pointer(); + { + let __a1 = std::mem::take(&mut (*v3.borrow_mut())); + (*m.borrow_mut()).push(__a1) + }; + 'loop_: for mut row in m.as_pointer() as Ptr> { 'loop_: for mut col in Ptr::>::decay(&(row)) as Ptr { square += { (col.read()) }; } diff --git a/tests/unit/out/refcount/global_non_const_init.rs b/tests/unit/out/refcount/global_non_const_init.rs index 5bb1678fc..08c6f2dda 100644 --- a/tests/unit/out/refcount/global_non_const_init.rs +++ b/tests/unit/out/refcount/global_non_const_init.rs @@ -111,15 +111,15 @@ pub fn main() { fn main_0() -> i32 { assert!( ((({ - let __idx = (0) as usize; - signature_3.with(|rc| rc.borrow()[__idx]) + let __idx0 = (0) as usize; + signature_3.with(|rc| rc.borrow()[__idx0]) }) as i32) == 10) ); assert!( ((({ - let __idx = (1) as usize; - signature_3.with(|rc| rc.borrow()[__idx]) + let __idx0 = (1) as usize; + signature_3.with(|rc| rc.borrow()[__idx0]) }) as i32) == 4) ); diff --git a/tests/unit/out/refcount/global_pointers.rs b/tests/unit/out/refcount/global_pointers.rs index b6c502cb5..1cd06ac95 100644 --- a/tests/unit/out/refcount/global_pointers.rs +++ b/tests/unit/out/refcount/global_pointers.rs @@ -59,8 +59,8 @@ fn main_0() -> i32 { ); assert!( ({ - let __idx = (i) as usize; - arr_of_pointers_2.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (i) as usize; + arr_of_pointers_2.with(|rc| rc.borrow()[__idx0].clone()) }) .is_null() ); diff --git a/tests/unit/out/refcount/ignored_rule.rs b/tests/unit/out/refcount/ignored_rule.rs index b5e5a9f0d..ab24a0d6a 100644 --- a/tests/unit/out/refcount/ignored_rule.rs +++ b/tests/unit/out/refcount/ignored_rule.rs @@ -11,10 +11,8 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let a: Value>]>> = Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(Box::new([1, 2]))), - Rc::new(RefCell::new(Box::new([3, 4]))), - ]))); + let a: Value]>> = + Rc::new(RefCell::new(Box::new([Box::new([1, 2]), Box::new([3, 4])]))); return 0; } pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/refcount/nested_vector.rs b/tests/unit/out/refcount/nested_vector.rs index 649d7eb24..2558959f8 100644 --- a/tests/unit/out/refcount/nested_vector.rs +++ b/tests/unit/out/refcount/nested_vector.rs @@ -11,13 +11,14 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let v1: Value>>> = Rc::new(RefCell::new(Vec::new())); + let v1: Value>> = Rc::new(RefCell::new(Vec::new())); let mut v2: Vec = vec![1]; - (v1.as_pointer() as Ptr>>>) - .with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new((v2).clone())))); + { + let a0_clone = v2.clone(); + (*v1.borrow_mut()).push(a0_clone) + }; assert!( - (((Ptr::>::decay(&((*v1.borrow())[(*v1.borrow()).len() - 1].as_pointer())) - as Ptr) + (((Ptr::>::decay(&((v1.as_pointer() as Ptr>).to_last())) as Ptr) .offset(0_usize as isize) .read()) == 1) diff --git a/tests/unit/out/refcount/null_in_statics.rs b/tests/unit/out/refcount/null_in_statics.rs index 44412bcb6..1b524fdcf 100644 --- a/tests/unit/out/refcount/null_in_statics.rs +++ b/tests/unit/out/refcount/null_in_statics.rs @@ -50,8 +50,8 @@ fn main_0() -> i32 { 'loop_: while (i < 4) { assert!( ({ - let __idx = (i) as usize; - arr_of_ptr_3.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (i) as usize; + arr_of_ptr_3.with(|rc| rc.borrow()[__idx0].clone()) }) .is_null() ); @@ -62,8 +62,8 @@ fn main_0() -> i32 { 'loop_: while (i < 3) { assert!( ({ - let __idx = (i) as usize; - const_arr_of_ptr_5.with(|rc| rc.borrow()[__idx].clone()) + let __idx0 = (i) as usize; + const_arr_of_ptr_5.with(|rc| rc.borrow()[__idx0].clone()) }) .is_null() ); diff --git a/tests/unit/out/refcount/operator_member_pointer_member.rs b/tests/unit/out/refcount/operator_member_pointer_member.rs index 2cd8e505a..4ad2e26fa 100644 --- a/tests/unit/out/refcount/operator_member_pointer_member.rs +++ b/tests/unit/out/refcount/operator_member_pointer_member.rs @@ -71,8 +71,8 @@ fn main_0() -> i32 { ({ Table::operator_index(1) }).write(80); assert!( (({ - let __idx = (1) as usize; - table_0.with(|rc| rc.borrow()[__idx]) + let __idx0 = (1) as usize; + table_0.with(|rc| rc.borrow()[__idx0]) }) == 80) ); return 0; diff --git a/tests/unit/out/refcount/pointer_usize_arith.rs b/tests/unit/out/refcount/pointer_usize_arith.rs index e9bc6a51f..968ed1f3a 100644 --- a/tests/unit/out/refcount/pointer_usize_arith.rs +++ b/tests/unit/out/refcount/pointer_usize_arith.rs @@ -74,14 +74,13 @@ fn main_0() -> i32 { assert!(((q5.read()) == 13)); let mut q6: Ptr = ((arr.as_pointer() as Ptr).offset(n)); assert!(({ (q6).clone() } == { (q5).clone() })); - let matrix: Value>]>> = Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(Box::new([0, 1, 2, 3]))), - Rc::new(RefCell::new(Box::new([4, 5, 6, 7]))), - Rc::new(RefCell::new(Box::new([8, 9, 10, 11]))), + let matrix: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([0, 1, 2, 3]), + Box::new([4, 5, 6, 7]), + Box::new([8, 9, 10, 11]), ]))); - let mut row1: Ptr = ((((matrix.as_pointer() as Ptr>>) + let mut row1: Ptr = ((((matrix.as_pointer() as Ptr>) .offset(1) - .read() .as_pointer()) as Ptr) .offset(0)); assert!(((elem!(row1, 2).read()) == 6)); diff --git a/tests/unit/out/refcount/push_emplace_back.rs b/tests/unit/out/refcount/push_emplace_back.rs index 4b7b125af..6e956759f 100644 --- a/tests/unit/out/refcount/push_emplace_back.rs +++ b/tests/unit/out/refcount/push_emplace_back.rs @@ -27,25 +27,27 @@ pub struct Writer { pub struct JPEGData { #[offset(0)] #[byte_size(24)] - pub com_data: Value>>>, + pub com_data: Value>>, #[offset(24)] #[byte_size(24)] - pub app_data: Value>>>, + pub app_data: Value>>, } -pub fn push_param_0(mut dest: Ptr>>>) { - ((dest).clone() as Ptr>>>) - .with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(Vec::new())))); +pub fn push_param_0(mut dest: Ptr>>) { + { + let __a1 = Vec::new(); + dest.with_mut(|__v: &mut Vec>| __v.push(__a1)) + }; } pub fn push_local_from_field_1(mut jpg: Ptr, mut cond: bool) { let head: Value> = Rc::new(RefCell::new(Box::new([1_u8, 2_u8, 3_u8]))); - let mut dest: Ptr>>> = Ptr::>>>::null(); + let mut dest: Ptr>> = Ptr::>>::null(); if cond { dest = (jpg.with(|__s| __s.com_data.as_pointer())); } else { dest = (jpg.with(|__s| __s.app_data.as_pointer())); } - ((dest).clone() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new({ + { + let __a1 = { let __count = (head.as_pointer() as Ptr) .offset((3) as isize) .get_offset() @@ -53,8 +55,9 @@ pub fn push_local_from_field_1(mut jpg: Ptr, mut cond: bool) { PtrValueIter::new(&(head.as_pointer() as Ptr), __count) .map(|item| u8::try_from(item).ok().unwrap()) .collect::>() - }))) - }); + }; + dest.with_mut(|__v: &mut Vec>| __v.push(__a1)) + }; } pub fn shrink_through_ptr_2(mut comps: Ptr>) { comps.with_mut(|__v: &mut Vec| __v.shrink_to_fit()); @@ -68,7 +71,7 @@ pub fn nested_push_move_3(mut bw: Ptr) { } pub fn emplace_local_from_field_4(mut jpg: Ptr, mut cond: bool) { let head: Value> = Rc::new(RefCell::new(Box::new([1_u8, 2_u8, 3_u8]))); - let mut dest: Ptr>>> = Ptr::>>>::null(); + let mut dest: Ptr>> = Ptr::>>::null(); if cond { dest = (jpg.with(|__s| __s.com_data.as_pointer())); } else { @@ -84,8 +87,7 @@ pub fn emplace_local_from_field_4(mut jpg: Ptr, mut cond: bool) { .map(|item| u8::try_from(item).ok().unwrap()) .collect::>() }; - ((dest).clone() as Ptr>>>) - .with_mut(|__v: &mut Vec>>| __v.push(Rc::new(RefCell::new(__init)))) + dest.with_mut(|__v: &mut Vec>| __v.push(__init)) }; } pub fn nested_emplace_move_5(mut bw: Ptr) { @@ -177,14 +179,12 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let vecs: Value>>> = Rc::new(RefCell::new(Vec::new())); + let vecs: Value>> = Rc::new(RefCell::new(Vec::new())); ({ push_param_0((vecs.as_pointer())) }); assert!(((*vecs.borrow()).len() == 1_usize)); assert!( - (*((vecs.as_pointer() as Ptr>>) + (*((vecs.as_pointer() as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -194,10 +194,8 @@ fn main_0() -> i32 { ({ push_local_from_field_1((jpg.as_pointer()), true) }); assert!(((*{ (*jpg.borrow()).com_data.clone() }.borrow()).len() == 1_usize)); assert!( - ((*(({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>>) + ((*(({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -206,10 +204,8 @@ fn main_0() -> i32 { ); assert!( (((elem!( - (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>>) + (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 0_usize ) @@ -218,10 +214,8 @@ fn main_0() -> i32 { ); assert!( (((elem!( - (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>>) + (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 1_usize ) @@ -230,10 +224,8 @@ fn main_0() -> i32 { ); assert!( (((elem!( - (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>>) + (({ (*jpg.borrow()).com_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 2_usize ) @@ -253,10 +245,8 @@ fn main_0() -> i32 { ({ emplace_local_from_field_4((jpg.as_pointer()), false) }); assert!(((*{ (*jpg.borrow()).app_data.clone() }.borrow()).len() == 1_usize)); assert!( - ((*(({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>>) + ((*(({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr>) .upgrade() .deref()) @@ -265,10 +255,8 @@ fn main_0() -> i32 { ); assert!( (((elem!( - (({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>>) + (({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 0_usize ) @@ -277,10 +265,8 @@ fn main_0() -> i32 { ); assert!( (((elem!( - (({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>>) + (({ (*jpg.borrow()).app_data.as_pointer() } as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 2_usize ) diff --git a/tests/unit/out/refcount/string_escape.rs b/tests/unit/out/refcount/string_escape.rs index 9a00b7d5c..bc76ecd2c 100644 --- a/tests/unit/out/refcount/string_escape.rs +++ b/tests/unit/out/refcount/string_escape.rs @@ -22,8 +22,8 @@ fn main_0() -> i32 { assert!( ({ ((elem!(special, i).read()) as i32) } == { (({ - let __idx = (i) as usize; - expected_0.with(|rc| rc.borrow()[__idx]) + let __idx0 = (i) as usize; + expected_0.with(|rc| rc.borrow()[__idx0]) }) as i32) }) ); diff --git a/tests/unit/out/refcount/struct_2d_array_field.rs b/tests/unit/out/refcount/struct_2d_array_field.rs index 52f5b18e2..d6de67636 100644 --- a/tests/unit/out/refcount/struct_2d_array_field.rs +++ b/tests/unit/out/refcount/struct_2d_array_field.rs @@ -11,7 +11,7 @@ use std::rc::{Rc, Weak}; pub struct table { #[offset(0)] #[byte_size(30)] - pub rows: Value>]>>, + pub rows: Value]>>, #[offset(32)] pub count: usize, } @@ -21,10 +21,8 @@ impl Default for table { table { rows: Rc::new(RefCell::new( (0..3) - .map(|_| { - Rc::new(RefCell::new((0..10).map(|_| 0_i8).collect::>())) - }) - .collect::>]>>(), + .map(|_| (0..10).map(|_| 0_i8).collect::>()) + .collect::]>>(), )), count: 0_usize, } @@ -34,9 +32,9 @@ impl Default for table { thread_local!( pub static T1_0: Value = Rc::new(RefCell::new(table { rows: Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(i8::array_from_literal(b"alpha\0\0\0\0\0"))), - Rc::new(RefCell::new(Box::new([0; 10]))), - Rc::new(RefCell::new(Box::new([0; 10]))), + i8::array_from_literal(b"alpha\0\0\0\0\0"), + Box::new([0; 10]), + Box::new([0; 10]), ]))), count: 1_usize, })); @@ -44,9 +42,9 @@ thread_local!( thread_local!( pub static T2_1: Value
= Rc::new(RefCell::new(table { rows: Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(i8::array_from_literal(b"alpha\0\0\0\0\0"))), - Rc::new(RefCell::new(i8::array_from_literal(b"beta\0\0\0\0\0\0"))), - Rc::new(RefCell::new(Box::new([0; 10]))), + i8::array_from_literal(b"alpha\0\0\0\0\0"), + i8::array_from_literal(b"beta\0\0\0\0\0\0"), + Box::new([0; 10]), ]))), count: 2_usize, })); @@ -60,9 +58,8 @@ fn main_0() -> i32 { assert!( ((({ let mut __it1 = ((({ (*T1_0.with(Value::clone).borrow()).rows.as_pointer() } - as Ptr>>) + as Ptr>) .offset(0) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"alpha").to_c_string_iterator(); @@ -80,7 +77,7 @@ fn main_0() -> i32 { != 0) ); assert!( - (((((*{ (*T1_0.with(Value::clone).borrow()).rows.clone() }.borrow())[(1) as usize].borrow() + (((((*{ (*T1_0.with(Value::clone).borrow()).rows.clone() }.borrow())[(1) as usize] [(0) as usize] as i32) == ('\0' as i32)) as i32) != 0) @@ -89,9 +86,8 @@ fn main_0() -> i32 { assert!( ((({ let mut __it1 = ((({ (*T2_1.with(Value::clone).borrow()).rows.as_pointer() } - as Ptr>>) + as Ptr>) .offset(0) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"alpha").to_c_string_iterator(); @@ -111,9 +107,8 @@ fn main_0() -> i32 { assert!( ((({ let mut __it1 = ((({ (*T2_1.with(Value::clone).borrow()).rows.as_pointer() } - as Ptr>>) + as Ptr>) .offset(1) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"beta").to_c_string_iterator(); @@ -131,24 +126,23 @@ fn main_0() -> i32 { != 0) ); assert!( - (((((*{ (*T2_1.with(Value::clone).borrow()).rows.clone() }.borrow())[(2) as usize].borrow() + (((((*{ (*T2_1.with(Value::clone).borrow()).rows.clone() }.borrow())[(2) as usize] [(0) as usize] as i32) == ('\0' as i32)) as i32) != 0) ); let local: Value
= Rc::new(RefCell::new(table { rows: Rc::new(RefCell::new(Box::new([ - Rc::new(RefCell::new(i8::array_from_literal(b"one\0\0\0\0\0\0\0"))), - Rc::new(RefCell::new(i8::array_from_literal(b"two\0\0\0\0\0\0\0"))), - Rc::new(RefCell::new(i8::array_from_literal(b"three\0\0\0\0\0"))), + i8::array_from_literal(b"one\0\0\0\0\0\0\0"), + i8::array_from_literal(b"two\0\0\0\0\0\0\0"), + i8::array_from_literal(b"three\0\0\0\0\0"), ]))), count: 3_usize, })); assert!( ((({ - let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>>) + let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>) .offset(2) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"three").to_c_string_iterator(); @@ -165,13 +159,12 @@ fn main_0() -> i32 { } == 0) as i32) != 0) ); - (*{ (*local.borrow()).rows.clone() }.borrow())[(1) as usize].borrow_mut()[(0) as usize] = + (*{ (*local.borrow()).rows.clone() }.borrow_mut())[(1) as usize][(0) as usize] = (('T' as i32) as i8); assert!( ((({ - let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>>) + let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>) .offset(1) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"Two").to_c_string_iterator(); @@ -190,9 +183,8 @@ fn main_0() -> i32 { ); assert!( ((({ - let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>>) + let mut __it1 = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>) .offset(0) - .read() .as_pointer()) as Ptr) .to_c_string_iterator(); let mut __it2 = Ptr::::from_string_literal(b"one").to_c_string_iterator(); @@ -209,9 +201,8 @@ fn main_0() -> i32 { } == 0) as i32) != 0) ); - let mut p: Ptr = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>>) + let mut p: Ptr = ((({ (*local.borrow()).rows.as_pointer() } as Ptr>) .offset(2) - .read() .as_pointer()) as Ptr); assert!((((((elem!(p, 0).read()) as i32) == ('t' as i32)) as i32) != 0)); assert!(((({ (*local.borrow()).count } == 3_usize) as i32) != 0)); diff --git a/tests/unit/out/refcount/vector3.rs b/tests/unit/out/refcount/vector3.rs index 0f6c15101..34d9098d1 100644 --- a/tests/unit/out/refcount/vector3.rs +++ b/tests/unit/out/refcount/vector3.rs @@ -11,67 +11,55 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let v: Value>>> = Rc::new(RefCell::new(Vec::new())); + let v: Value>> = Rc::new(RefCell::new(Vec::new())); { - let _a0 = 2_usize as usize; - (v.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.resize_with(_a0, >>::default) - }) + let __a0 = 2_usize as usize; + (*v.borrow_mut()).resize_with(__a0, || >::default()) }; { let __a0 = 2_usize as usize; - elem!((v.as_pointer() as Ptr>>), 0_usize).with_mut( - |__v: &mut Value>| (*__v.borrow_mut()).resize_with(__a0, || ::default()), - ) + elem!((v.as_pointer() as Ptr>), 0_usize) + .with_mut(|__v: &mut Vec| __v.resize_with(__a0, || ::default())) }; { let __a0 = 1_usize as usize; - elem!((v.as_pointer() as Ptr>>), 1_usize).with_mut( - |__v: &mut Value>| (*__v.borrow_mut()).resize_with(__a0, || ::default()), - ) + elem!((v.as_pointer() as Ptr>), 1_usize) + .with_mut(|__v: &mut Vec| __v.resize_with(__a0, || ::default())) }; elem!( - ((v.as_pointer() as Ptr>>) + ((v.as_pointer() as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 0_usize ) .write(1); elem!( - ((v.as_pointer() as Ptr>>) + ((v.as_pointer() as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 1_usize ) .write(5); elem!( - ((v.as_pointer() as Ptr>>) + ((v.as_pointer() as Ptr>) .offset(1_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 0_usize ) .write(6); - 'loop_: for mut v2 in v.as_pointer() as Ptr>> { - let v2: Ptr> = v2.upgrade().deref().as_pointer(); + 'loop_: for mut v2 in v.as_pointer() as Ptr> { 'loop_: for mut i in Ptr::>::decay(&(v2)) as Ptr { i.with_mut(|__v| __v.prefix_inc()); } } - 'loop_: for mut v2 in v.as_pointer() as Ptr>> { - let v2: Ptr> = v2.upgrade().deref().as_pointer(); + 'loop_: for mut v2 in v.as_pointer() as Ptr> { 'loop_: for mut i in Ptr::>::decay(&(v2)) as Ptr { let mut i: i32 = i.read(); println!("{}", (i + 3)); } } - 'loop_: for mut v2 in v.as_pointer() as Ptr>> { - let v2: Value> = Rc::new(RefCell::new(v2.upgrade().deref().borrow().clone())); + 'loop_: for mut v2 in v.as_pointer() as Ptr> { + let v2: Value> = Rc::new(RefCell::new(v2.upgrade().deref().clone())); 'loop_: for mut i in v2.as_pointer() as Ptr { let mut i: i32 = i.read(); println!("{}", i); diff --git a/tests/unit/out/refcount/vector_addr_of_back.rs b/tests/unit/out/refcount/vector_addr_of_back.rs index 8911a04c6..3a8c6b25a 100644 --- a/tests/unit/out/refcount/vector_addr_of_back.rs +++ b/tests/unit/out/refcount/vector_addr_of_back.rs @@ -11,15 +11,16 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let outer: Value>>> = Rc::new(RefCell::new(Vec::new())); + let outer: Value>> = Rc::new(RefCell::new(Vec::new())); let mut inner: Vec = Vec::new(); - (outer.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new((inner).clone()))) - }); - let mut sink: Ptr> = ((*outer.borrow())[(*outer.borrow()).len() - 1].as_pointer()); + { + let a0_clone = inner.clone(); + (*outer.borrow_mut()).push(a0_clone) + }; + let mut sink: Ptr> = ((outer.as_pointer() as Ptr>).to_last()); assert!(((*sink.upgrade().deref()).len() == 0_usize)); - let mut p: Ptr>>> = (outer.as_pointer()); - sink = ((*p.upgrade().deref())[(*p.upgrade().deref()).len() - 1].as_pointer()); + let mut p: Ptr>> = (outer.as_pointer()); + sink = ((Ptr::>>::decay(&(p)) as Ptr>).to_last()); assert!(((*sink.upgrade().deref()).len() == 0_usize)); return 0; } diff --git a/tests/unit/out/refcount/vector_subscript_direct.rs b/tests/unit/out/refcount/vector_subscript_direct.rs index b87d91254..1522be315 100644 --- a/tests/unit/out/refcount/vector_subscript_direct.rs +++ b/tests/unit/out/refcount/vector_subscript_direct.rs @@ -164,44 +164,88 @@ fn main_0() -> i32 { .x } == 7) ); - let grid: Value>>> = Rc::new(RefCell::new(Vec::new())); - (grid.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(vec![0; 3_usize as usize]))) - }); - (grid.as_pointer() as Ptr>>>).with_mut(|__v: &mut Vec>>| { - __v.push(Rc::new(RefCell::new(vec![0; 3_usize as usize]))) - }); + let grid: Value>> = Rc::new(RefCell::new(Vec::new())); + { + let __a1 = vec![0; 3_usize as usize]; + (*grid.borrow_mut()).push(__a1) + }; + { + let __a1 = vec![0; 3_usize as usize]; + (*grid.borrow_mut()).push(__a1) + }; elem!( - ((grid.as_pointer() as Ptr>>) + ((grid.as_pointer() as Ptr>) .offset(1_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 2_usize ) .write(8); assert!( ((elem!( - ((grid.as_pointer() as Ptr>>) + ((grid.as_pointer() as Ptr>) .offset(1_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 2_usize ) .read()) == 8) && ((elem!( - ((grid.as_pointer() as Ptr>>) + ((grid.as_pointer() as Ptr>) .offset(0_usize) - .upgrade() - .deref() .as_pointer() as Ptr), 2_usize ) .read()) == 0) ); + let __rhs = (elem!( + ((grid.as_pointer() as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr), + 2_usize + ) + .read()); + elem!( + ((grid.as_pointer() as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr), + 1_usize + ) + .write(__rhs); + let __rhs = (elem!( + ((grid.as_pointer() as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr), + 1_usize + ) + .read()); + { + elem!( + ((grid.as_pointer() as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr), + 0_usize + ) + .with_mut(|__v| *__v = *__v + __rhs) + }; + assert!( + ((elem!( + ((grid.as_pointer() as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr), + 1_usize + ) + .read()) + == 8) + && ((elem!( + ((grid.as_pointer() as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr), + 0_usize + ) + .read()) + == 8) + ); let a: Value> = Rc::new(RefCell::new(vec![4, 5, 6])); let __rhs = ({ (*a.borrow())[0_usize] } + { (*a.borrow())[2_usize] }); (*a.borrow_mut())[1_usize] = __rhs; diff --git a/tests/unit/out/unsafe/12_test.rs b/tests/unit/out/unsafe/12_test.rs index 4980993e8..9b3a0bebf 100644 --- a/tests/unit/out/unsafe/12_test.rs +++ b/tests/unit/out/unsafe/12_test.rs @@ -14,11 +14,12 @@ pub fn main() { } unsafe fn main_0() -> i32 { let mut v: Vec> = Vec::new(); - v.push( - (0..(10_usize) as usize) + { + let __a1 = (0..(10_usize) as usize) .map(|_| ::default()) - .collect::>(), - ); + .collect::>(); + v.push(__a1) + }; return 0; } pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/unsafe/array_reference.rs b/tests/unit/out/unsafe/array_reference.rs index 56eccd5bb..2e2ee9127 100644 --- a/tests/unit/out/unsafe/array_reference.rs +++ b/tests/unit/out/unsafe/array_reference.rs @@ -74,6 +74,8 @@ pub unsafe fn total_len_13(names: *mut [*const libc::c_char; 2]) -> i32 { return ((unsafe { len5_1((*names)[(0) as usize]) }) + (unsafe { len5_1((*names)[(1) as usize]) })); } +pub static mut kTable_14: std::cell::LazyCell<[[i32; 3]; 2]> = + std::cell::LazyCell::new(|| unsafe { [[1, 2, 3], [4, 5, 6]] }); pub fn main() { unsafe { __cpp2rust_init_globals(); @@ -131,6 +133,70 @@ unsafe fn main_0() -> i32 { (((*q.offset((1) as isize)).as_mut_ptr()) == (&mut rows[(1) as usize][(0) as usize] as *mut i32)) ); + (*q.offset((1) as isize))[(2) as usize] = 8; + assert!( + ((rows[(1) as usize][(2) as usize]) == (8)) && ((rows[(0) as usize][(2) as usize]) == (3)) + ); + rows[(0) as usize][(1) as usize] = rows[(0) as usize][(2) as usize]; + (*q.offset((1) as isize))[(0) as usize] = (*q.offset((0) as isize))[(1) as usize]; + (*q.offset((1) as isize))[(1) as usize] += (*q.offset((0) as isize))[(2) as usize]; + assert!( + (((rows[(0) as usize][(1) as usize]) == (3)) + && ((rows[(1) as usize][(0) as usize]) == (3))) + && ((rows[(1) as usize][(1) as usize]) == (8)) + ); + let mut key: [i32; 4] = [0, 4, 5, 6]; + assert!( + (({ + let sa = core::slice::from_raw_parts( + ((&mut key[(1) as usize] as *mut i32) as *const ::libc::c_void) as *const u8, + ::std::mem::size_of::<[i32; 3]>() as usize, + ); + let sb = core::slice::from_raw_parts( + ((*std::cell::LazyCell::force_mut(&mut *&raw mut kTable_14))[(1) as usize].as_ptr() + as *const ::libc::c_void) as *const u8, + ::std::mem::size_of::<[i32; 3]>() as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) == (0)) + ); + assert!( + (({ + let sa = core::slice::from_raw_parts( + ((&mut key[(1) as usize] as *mut i32) as *const ::libc::c_void) as *const u8, + ::std::mem::size_of::<[i32; 3]>() as usize, + ); + let sb = core::slice::from_raw_parts( + ((*std::cell::LazyCell::force_mut(&mut *&raw mut kTable_14))[(0) as usize].as_ptr() + as *const ::libc::c_void) as *const u8, + ::std::mem::size_of::<[i32; 3]>() as usize, + ); + let mut diff = 0_i32; + for (x, y) in sa.iter().zip(sb.iter()) { + if x != y { + diff = (*x as i32) - (*y as i32); + break; + } + } + diff + }) != (0)) + ); + assert!( + (((*std::cell::LazyCell::force_mut(&mut *&raw mut kTable_14))[(1) as usize][(2) as usize]) + == (6)) + && (((*std::cell::LazyCell::force_mut(&mut *&raw mut kTable_14))[(0) as usize] + [(1) as usize]) + == (2)) + ); return 0; } -pub unsafe fn __cpp2rust_init_globals() {} +pub unsafe fn __cpp2rust_init_globals() { + std::cell::LazyCell::force(&*&raw const kTable_14); +} diff --git a/tests/unit/out/unsafe/clone_vs_move.rs b/tests/unit/out/unsafe/clone_vs_move.rs index 7398992ff..a744210f7 100644 --- a/tests/unit/out/unsafe/clone_vs_move.rs +++ b/tests/unit/out/unsafe/clone_vs_move.rs @@ -124,11 +124,12 @@ unsafe fn main_0() -> i32 { let mut m1: Vec> = Vec::new(); let mut i: i32 = 0; 'loop_: while ((i) < (N)) { - m1.push( - (0..(10_usize) as usize) + { + let __a1 = (0..(10_usize) as usize) .map(|_| ::default()) - .collect::>(), - ); + .collect::>(); + m1.push(__a1) + }; i.prefix_inc(); } let mut m2: Vec> = m1.clone(); diff --git a/tests/unit/out/unsafe/copy_move_defaulted.rs b/tests/unit/out/unsafe/copy_move_defaulted.rs index 4f9f5272b..b64ddf832 100644 --- a/tests/unit/out/unsafe/copy_move_defaulted.rs +++ b/tests/unit/out/unsafe/copy_move_defaulted.rs @@ -113,7 +113,10 @@ impl Buffer { n: n, arr: [n, ((n) + (1))], }; - this.rows.push(this.data.clone()); + { + let a0_clone = this.data.clone(); + this.rows.push(a0_clone) + }; this } pub unsafe fn move_from(_a0: *mut Buffer) -> Self { diff --git a/tests/unit/out/unsafe/foreach_double.rs b/tests/unit/out/unsafe/foreach_double.rs index 8035a31b5..7c6e3f33a 100644 --- a/tests/unit/out/unsafe/foreach_double.rs +++ b/tests/unit/out/unsafe/foreach_double.rs @@ -57,11 +57,20 @@ unsafe fn main_0() -> i32 { } let mut m: Vec> = Vec::new(); let mut v1: Vec = Vec::new(); - m.push(std::mem::take(&mut v1)); + { + let __a1 = std::mem::take(&mut v1); + m.push(__a1) + }; let mut v2: Vec = Vec::new(); - m.push(std::mem::take(&mut v2)); + { + let __a1 = std::mem::take(&mut v2); + m.push(__a1) + }; let mut v3: Vec = Vec::new(); - m.push(std::mem::take(&mut v3)); + { + let __a1 = std::mem::take(&mut v3); + m.push(__a1) + }; 'loop_: for row in 0..(m.len()) { let mut row = m.as_mut_ptr().add(row); 'loop_: for col in 0..((*row).len()) { diff --git a/tests/unit/out/unsafe/nested_vector.rs b/tests/unit/out/unsafe/nested_vector.rs index 7468956df..eb1173d97 100644 --- a/tests/unit/out/unsafe/nested_vector.rs +++ b/tests/unit/out/unsafe/nested_vector.rs @@ -15,7 +15,10 @@ pub fn main() { unsafe fn main_0() -> i32 { let mut v1: Vec> = Vec::new(); let mut v2: Vec = vec![1]; - v1.push(v2.clone()); + { + let a0_clone = v2.clone(); + v1.push(a0_clone) + }; assert!(((*(&mut (*((v1).last_mut().unwrap()))[0_usize as usize] as *mut i32)) == (1))); return 0; } diff --git a/tests/unit/out/unsafe/push_emplace_back.rs b/tests/unit/out/unsafe/push_emplace_back.rs index 07cc382fb..22ed160a4 100644 --- a/tests/unit/out/unsafe/push_emplace_back.rs +++ b/tests/unit/out/unsafe/push_emplace_back.rs @@ -24,7 +24,10 @@ pub struct JPEGData { pub app_data: Vec>, } pub unsafe fn push_param_0(mut dest: *mut Vec>) { - (*dest).push(Vec::new()); + { + let __a1 = Vec::new(); + (*dest).push(__a1) + }; } pub unsafe fn push_local_from_field_1(mut jpg: *mut JPEGData, mut cond: bool) { let mut head: [u8; 3] = [1_u8, 2_u8, 3_u8]; @@ -34,15 +37,16 @@ pub unsafe fn push_local_from_field_1(mut jpg: *mut JPEGData, mut cond: bool) { } else { dest = (&mut (*jpg).app_data as *mut Vec>); } - (*dest).push( - core::slice::from_raw_parts( + { + let __a1 = core::slice::from_raw_parts( head.as_mut_ptr(), (head.as_mut_ptr().offset((3) as isize)).offset_from(head.as_mut_ptr()) as usize, ) .iter() .map(|x| u8::try_from(x.clone()).ok().unwrap()) - .collect(), - ); + .collect(); + (*dest).push(__a1) + }; } pub unsafe fn shrink_through_ptr_2(mut comps: *mut Vec) { (*comps).shrink_to_fit(); diff --git a/tests/unit/out/unsafe/vector3.rs b/tests/unit/out/unsafe/vector3.rs index 116740ed0..7bb74550e 100644 --- a/tests/unit/out/unsafe/vector3.rs +++ b/tests/unit/out/unsafe/vector3.rs @@ -14,7 +14,10 @@ pub fn main() { } unsafe fn main_0() -> i32 { let mut v: Vec> = Vec::new(); - v.resize_with(2_usize as usize, || >::default()); + { + let __a0 = 2_usize as usize; + v.resize_with(__a0, || >::default()) + }; { let __a0 = 2_usize as usize; v[(0_usize)].resize_with(__a0, || ::default()) diff --git a/tests/unit/out/unsafe/vector_addr_of_back.rs b/tests/unit/out/unsafe/vector_addr_of_back.rs index b07ee1ae0..0f8f2bab4 100644 --- a/tests/unit/out/unsafe/vector_addr_of_back.rs +++ b/tests/unit/out/unsafe/vector_addr_of_back.rs @@ -15,7 +15,10 @@ pub fn main() { unsafe fn main_0() -> i32 { let mut outer: Vec> = Vec::new(); let mut inner: Vec = Vec::new(); - outer.push(inner.clone()); + { + let a0_clone = inner.clone(); + outer.push(a0_clone) + }; let mut sink: *mut Vec = ((outer).last_mut().unwrap()); assert!((((*(sink).cast_const()).len()) == (0_usize))); let mut p: *mut Vec> = (&mut outer as *mut Vec>); From da79b0ce374ec21d8cf3b750d423ae455569f85f Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 16:53:01 +0100 Subject: [PATCH 4/7] Trigger CI From 42a71d446e3157b90097d2ed0b35a090c0d88fb9 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 19:49:23 +0100 Subject: [PATCH 5/7] Add address identity tests --- .../out/refcount/row_ptr_sub_across_rows.rs | 27 +++++++++++++++++++ .../ub/out/unsafe/row_ptr_sub_across_rows.rs | 21 +++++++++++++++ tests/ub/row_ptr_sub_across_rows.cpp | 6 +++++ tests/unit/array_reference.cpp | 1 + tests/unit/out/refcount/array_reference.rs | 11 ++++++++ tests/unit/out/unsafe/array_reference.rs | 6 +++++ 6 files changed, 72 insertions(+) create mode 100644 tests/ub/out/refcount/row_ptr_sub_across_rows.rs create mode 100644 tests/ub/out/unsafe/row_ptr_sub_across_rows.rs create mode 100644 tests/ub/row_ptr_sub_across_rows.cpp diff --git a/tests/ub/out/refcount/row_ptr_sub_across_rows.rs b/tests/ub/out/refcount/row_ptr_sub_across_rows.rs new file mode 100644 index 000000000..5bbe0d2e2 --- /dev/null +++ b/tests/ub/out/refcount/row_ptr_sub_across_rows.rs @@ -0,0 +1,27 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let rows: Value]>> = Rc::new(RefCell::new(Box::new([ + Box::new([1, 2, 3]), + Box::new([4, 5, 6]), + ]))); + return ((((((rows.as_pointer() as Ptr>) + .offset(1) + .as_pointer()) as Ptr) + - (((((rows.as_pointer() as Ptr>) + .offset(0) + .as_pointer()) as Ptr) + .offset(0)) + .offset((2) as isize))) as i64) as i32); +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/unsafe/row_ptr_sub_across_rows.rs b/tests/ub/out/unsafe/row_ptr_sub_across_rows.rs new file mode 100644 index 000000000..b027bd5d4 --- /dev/null +++ b/tests/ub/out/unsafe/row_ptr_sub_across_rows.rs @@ -0,0 +1,21 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut rows: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]]; + return ((((rows[(1) as usize].as_mut_ptr() as usize + - ((&mut rows[(0) as usize][(0) as usize] as *mut i32).offset((2) as isize)) as usize) + / ::std::mem::size_of::()) as i64) as i32); +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/ub/row_ptr_sub_across_rows.cpp b/tests/ub/row_ptr_sub_across_rows.cpp new file mode 100644 index 000000000..07abfa859 --- /dev/null +++ b/tests/ub/row_ptr_sub_across_rows.cpp @@ -0,0 +1,6 @@ +// panic-ub: refcount +// nondet-result: unsafe +int main() { + int rows[2][3] = {{1, 2, 3}, {4, 5, 6}}; + return rows[1] - (&rows[0][0] + 2); +} diff --git a/tests/unit/array_reference.cpp b/tests/unit/array_reference.cpp index 1a4af15b3..35037357a 100644 --- a/tests/unit/array_reference.cpp +++ b/tests/unit/array_reference.cpp @@ -110,6 +110,7 @@ int main() { q[1][0] = q[0][1]; q[1][1] += q[0][2]; assert(rows[0][1] == 3 && rows[1][0] == 3 && rows[1][1] == 8); + assert(&rows[1][2] - &rows[1][0] == 2); int key[4] = {0, 4, 5, 6}; assert(memcmp(&key[1], kTable[1], sizeof(kTable[1])) == 0); diff --git a/tests/unit/out/refcount/array_reference.rs b/tests/unit/out/refcount/array_reference.rs index dd3bcd693..ed34c37e0 100644 --- a/tests/unit/out/refcount/array_reference.rs +++ b/tests/unit/out/refcount/array_reference.rs @@ -189,6 +189,17 @@ fn main_0() -> i32 { && ((*rows.borrow())[(1) as usize][(0) as usize] == 3)) && ((*rows.borrow())[(1) as usize][(1) as usize] == 8) ); + assert!( + ((((((rows.as_pointer() as Ptr>) + .offset(1) + .as_pointer()) as Ptr) + .offset(2)) + - ((((rows.as_pointer() as Ptr>) + .offset(1) + .as_pointer()) as Ptr) + .offset(0))) as i64 + == 2_i64) + ); let key: Value> = Rc::new(RefCell::new(Box::new([0, 4, 5, 6]))); assert!( ((((key.as_pointer() as Ptr).offset(1)) as Ptr::) diff --git a/tests/unit/out/unsafe/array_reference.rs b/tests/unit/out/unsafe/array_reference.rs index 2e2ee9127..f1edac698 100644 --- a/tests/unit/out/unsafe/array_reference.rs +++ b/tests/unit/out/unsafe/array_reference.rs @@ -145,6 +145,12 @@ unsafe fn main_0() -> i32 { && ((rows[(1) as usize][(0) as usize]) == (3))) && ((rows[(1) as usize][(1) as usize]) == (8)) ); + assert!( + (((((&mut rows[(1) as usize][(2) as usize] as *mut i32) as usize + - (&mut rows[(1) as usize][(0) as usize] as *mut i32) as usize) + / ::std::mem::size_of::()) as i64) + == (2_i64)) + ); let mut key: [i32; 4] = [0, 4, 5, 6]; assert!( (({ From bc4f12b05167c9f3b78653ad6c519de5e9e4bc4a Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 20:20:52 +0100 Subject: [PATCH 6/7] Add stress test for nested containers --- .../refcount/vector_deque_vector_string.rs | 309 ++++++++++++++++++ .../out/unsafe/vector_deque_vector_string.rs | 131 ++++++++ tests/unit/vector_deque_vector_string.cpp | 35 ++ 3 files changed, 475 insertions(+) create mode 100644 tests/unit/out/refcount/vector_deque_vector_string.rs create mode 100644 tests/unit/out/unsafe/vector_deque_vector_string.rs create mode 100644 tests/unit/vector_deque_vector_string.cpp diff --git a/tests/unit/out/refcount/vector_deque_vector_string.rs b/tests/unit/out/refcount/vector_deque_vector_string.rs new file mode 100644 index 000000000..ee5cda203 --- /dev/null +++ b/tests/unit/out/refcount/vector_deque_vector_string.rs @@ -0,0 +1,309 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let v: Value>>>> = Rc::new(RefCell::new(Vec::new())); + { + let __init = Vec::new(); + (*v.borrow_mut()).push(__init) + }; + { + let __init = Vec::new(); + (*v.borrow_mut()).push(__init) + }; + let row: Value>> = Rc::new(RefCell::new(Vec::new())); + { + let __a1 = { + let mut __bytes = Ptr::::from_string_literal(b"a").to_c_bytes(); + __bytes.push(0); + __bytes + }; + (*row.borrow_mut()).push(__a1) + }; + { + let __a1 = { + let mut __bytes = Ptr::::from_string_literal(b"bc").to_c_bytes(); + __bytes.push(0); + __bytes + }; + (*row.borrow_mut()).push(__a1) + }; + elem!((v.as_pointer() as Ptr>>>), 0_usize) + .with_mut(|__v: &mut Vec>>| __v.push((*row.borrow()).clone())); + { + let __a1 = { + let mut __bytes = Ptr::::from_string_literal(b"def").to_c_bytes(); + __bytes.push(0); + __bytes + }; + (*row.borrow_mut()).push(__a1) + }; + elem!((v.as_pointer() as Ptr>>>), 1_usize) + .with_mut(|__v: &mut Vec>>| __v.push((*row.borrow()).clone())); + { + let __init = Vec::new(); + elem!((v.as_pointer() as Ptr>>>), 1_usize) + .with_mut(|__v: &mut Vec>>| __v.push(__init)) + }; + { + let __a1 = { + let mut r = (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .clone(); + r.pop(); + Ptr::::from_string_literal(b"x").with_c_str(|__s| r.extend_from_slice(__s)); + r.push(0); + r + }; + ((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>) + .to_last() + .with_mut(|__v: &mut Vec>| __v.push(__a1)) + }; + assert!( + ((*((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>) + .upgrade() + .deref()) + .len() + == 2_usize) + ); + assert!( + ((*((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>) + .upgrade() + .deref()) + .len() + == 3_usize) + && (Ptr::::from_string_literal(b"def").with_c_str(|__s| { + (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(2_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(2_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s + })) + ); + assert!(Ptr::::from_string_literal(b"bcx").with_c_str(|__s| { + (*((Ptr::>>::decay( + &(((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>) + .to_last()), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &(((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>) + .to_last()), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s + })); + let copy: Value>>>> = Rc::new(RefCell::new((*v.borrow()).clone())); + ((Ptr::>>::decay( + &((copy.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .write({ + let mut __bytes = Ptr::::from_string_literal(b"z").to_c_bytes(); + __bytes.push(0); + __bytes + }); + if 0_usize as usize + >= (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1) + { + panic!("out of bounds access") + } else { + ((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .decay() + .offset(0_usize as isize) + } + .write(('B' as i8)); + assert!( + (Ptr::::from_string_literal(b"a").with_c_str(|__s| (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>) + ) as Ptr>) + .offset(0_usize) + .as_pointer() + as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>) + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s)) + && (Ptr::::from_string_literal(b"z").with_c_str(|__s| { + (*((Ptr::>>::decay( + &((copy.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((copy.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s + })) + ); + assert!( + (Ptr::::from_string_literal(b"Bc").with_c_str(|__s| (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>) + ) as Ptr>) + .offset(1_usize) + .as_pointer() + as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>) + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s)) + && (Ptr::::from_string_literal(b"bc").with_c_str(|__s| { + (*((Ptr::>>::decay( + &((copy.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((copy.as_pointer() as Ptr>>>) + .offset(0_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(1_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s + })) + ); + elem!((v.as_pointer() as Ptr>>>), 1_usize) + .with_mut(|__v: &mut Vec>>| __v.remove(0)); + assert!(Ptr::::from_string_literal(b"bcx").with_c_str(|__s| { + (*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref())[..(*((Ptr::>>::decay( + &((v.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>), + ) as Ptr>) + .offset(0_usize) + .as_pointer() as Ptr>) + .upgrade() + .deref()) + .len() + .saturating_sub(1)] + == *__s + })); + assert!( + ((*((copy.as_pointer() as Ptr>>>) + .offset(1_usize) + .as_pointer() as Ptr>>) + .upgrade() + .deref()) + .len() + == 3_usize) + ); + assert!(((*row.borrow()).len() == 3_usize)); + return 0; +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/unsafe/vector_deque_vector_string.rs b/tests/unit/out/unsafe/vector_deque_vector_string.rs new file mode 100644 index 000000000..50428bec7 --- /dev/null +++ b/tests/unit/out/unsafe/vector_deque_vector_string.rs @@ -0,0 +1,131 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut v: Vec>>> = Vec::new(); + { + let __init = Vec::new(); + v.push(__init) + }; + { + let __init = Vec::new(); + v.push(__init) + }; + let mut row: Vec> = Vec::new(); + { + let __a1 = { + let s = c"a".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }; + row.push(__a1) + }; + { + let __a1 = { + let s = c"bc".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }; + row.push(__a1) + }; + v[(0_usize)].push((row).clone()); + { + let __a1 = { + let s = c"def".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }; + row.push(__a1) + }; + v[(1_usize)].push((row).clone()); + { + let __init = Vec::new(); + v[(1_usize)].push(__init) + }; + { + let __a1 = { + let mut __tmp2 = (*((v[(0_usize)]).first_mut().unwrap()))[(1_usize)].clone(); + __tmp2.pop(); + let __from = c"x".as_ptr(); + __tmp2.extend_from_slice(::std::slice::from_raw_parts( + __from, + (0..).position(|i| *__from.add(i) == 0).unwrap(), + )); + __tmp2.push(0); + __tmp2 + }; + (*(v[(1_usize)].last_mut().unwrap())).push(__a1) + }; + assert!((((*((v[(0_usize)]).first_mut().unwrap())).len()) == (2_usize))); + assert!( + (((*((v[(1_usize)]).first_mut().unwrap())).len()) == (3_usize)) + && ((*((v[(1_usize)]).first_mut().unwrap()))[(2_usize)] == { + let s = c"def".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }) + ); + assert!( + (*(v[(1_usize)].last_mut().unwrap()))[(0_usize)] == { + let s = c"bcx".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + } + ); + let mut copy: Vec>>> = v.clone(); + (*((copy[(0_usize)]).first_mut().unwrap()))[(0_usize)] = { + let s = c"z".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1).to_vec() + }; + (*if 0_usize as usize >= (*((v[(0_usize)]).first_mut().unwrap()))[(1_usize)].len() - 1 { + panic!("out of bounds access") + } else { + (&mut (*((v[(0_usize)]).first_mut().unwrap()))[(1_usize)][0_usize as usize] + as *mut libc::c_char) + }) = ('B' as libc::c_char); + assert!( + ((*((v[(0_usize)]).first_mut().unwrap()))[(0_usize)] == { + let s = c"a".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }) && ((*((copy[(0_usize)]).first_mut().unwrap()))[(0_usize)] == { + let s = c"z".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }) + ); + assert!( + ((*((v[(0_usize)]).first_mut().unwrap()))[(1_usize)] == { + let s = c"Bc".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }) && ((*((copy[(0_usize)]).first_mut().unwrap()))[(1_usize)] == { + let s = c"bc".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + }) + ); + v[(1_usize)].remove(0); + assert!( + (*((v[(1_usize)]).first_mut().unwrap()))[(0_usize)] == { + let s = c"bcx".as_ptr(); + std::slice::from_raw_parts(s, (0..).take_while(|&i| *s.add(i) != 0).count() + 1) + .to_vec() + } + ); + assert!((((*((copy[(1_usize)]).first_mut().unwrap())).len()) == (3_usize))); + assert!(((row.len()) == (3_usize))); + return 0; +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/unit/vector_deque_vector_string.cpp b/tests/unit/vector_deque_vector_string.cpp new file mode 100644 index 000000000..3dca5dd8a --- /dev/null +++ b/tests/unit/vector_deque_vector_string.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +int main() { + std::vector>> v; + v.emplace_back(); + v.emplace_back(); + + std::vector row; + row.push_back("a"); + row.push_back("bc"); + v[0].push_back(row); + row.push_back("def"); + v[1].push_back(row); + v[1].emplace_back(); + v[1].back().push_back(v[0].front()[1] + "x"); + + assert(v[0].front().size() == 2); + assert(v[1].front().size() == 3 && v[1].front()[2] == "def"); + assert(v[1].back()[0] == "bcx"); + + auto copy = v; + copy[0].front()[0] = std::string("z"); + v[0].front()[1].at(0) = 'B'; + assert(v[0].front()[0] == "a" && copy[0].front()[0] == "z"); + assert(v[0].front()[1] == "Bc" && copy[0].front()[1] == "bc"); + + v[1].pop_front(); + assert(v[1].front()[0] == "bcx"); + assert(copy[1].front().size() == 3); + assert(row.size() == 3); + return 0; +} From cdff909cabff561272767edd284ef17b2dee082a Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 13 Oct 2026 20:44:08 +0100 Subject: [PATCH 7/7] Fix new[n][n] --- cpp2rust/converter/models/converter_refcount.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp2rust/converter/models/converter_refcount.cpp b/cpp2rust/converter/models/converter_refcount.cpp index 5c6633068..5edffdeb3 100644 --- a/cpp2rust/converter/models/converter_refcount.cpp +++ b/cpp2rust/converter/models/converter_refcount.cpp @@ -2187,7 +2187,9 @@ bool ConverterRefCount::VisitCXXNewExpr(clang::CXXNewExpr *expr) { } else { auto array_size_as_string = ToString(*expr->getArraySize()); auto alloc_type = expr->getAllocatedType(); - PushConversionKind push(*this, ConversionKind::Unboxed); + PushConversionKind push(*this, alloc_type->isArrayType() + ? ConversionKind::Pointee + : ConversionKind::Unboxed); auto alloc_type_as_string = ToString(alloc_type); auto default_alloc_type_as_string = GetDefaultAsString(alloc_type);