Skip to content

[ffigen] Cpp unique ptr ownership - #3513

Open
Hassnaa9 wants to merge 5 commits into
dart-lang:mainfrom
Hassnaa9:cpp-unique-ptr-ownership
Open

[ffigen] Cpp unique ptr ownership#3513
Hassnaa9 wants to merge 5 commits into
dart-lang:mainfrom
Hassnaa9:cpp-unique-ptr-ownership

Conversation

@Hassnaa9

@Hassnaa9 Hassnaa9 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@liamappelbe
liamappelbe self-requested a review August 2, 2026 23:59
Comment thread pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart Outdated
Comment thread pkgs/ffigen/test/native_cpp_test/memory_edge_cases_bindings.dart Outdated
if (method.isConstructor) {
returnTypeString = '$originalName*';
params = method.parameters.map(paramDecl).join(', ');
final callArgs = method.parameters.map((p) => p.name).join(', ');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A constructor can take a std::unique_ptr too. Can you add a test for this case?


final callArgs = method.parameters
.map((p) {
if (p.type is CppClassPointerType &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is complicated enough that it should probably be a separate private util function, for readability.


// Check if any method uses unique_ptr (owned ownership) so we can emit
// #include <memory> conditionally rather than always.
final needsMemoryHeader = methods.any(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no real harm in unconditionally including <memory>. I'd rather keep things simple.

params = ['$selfType* self', ...otherParams].join(', ');
body = '${returnPrefix}self->${method.originalName}($callArgs);';
final isUniquePtrReturn =
method.returnType is CppClassPointerType &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. You're using this pattern of checking whether something is a unique_ptr often enough that it should be a util. Code duplication adds a maintenance burden over time.

final m = method.originalName;
body = '${returnPrefix}self->$m($callArgs).release();';
} else {
body =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. These two cases are almost identical. A cleaner approach would be something like:

final methodName = method.originalName;
final suffix = isUniquePtrReturn ? 'release()' : '';
body = '${returnPrefix}self->$methodName($callArgs)$suffix;'

Comment thread pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart Outdated
Comment thread pkgs/ffigen/lib/src/code_generator/pointer.dart Outdated
Comment thread pkgs/ffigen/lib/src/code_generator/pointer.dart Outdated
@Hassnaa9
Hassnaa9 requested a review from liamappelbe August 5, 2026 12:47
final spelling = clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
if (spelling.contains('unique_ptr<')) {
final declCursor = clang.clang_getTypeDeclaration(cxtype);
final usr = clang.clang_getCursorUSR(declCursor).toStringAndDispose();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have extension methods for this. You can use declCursor.usr().

if (spelling.contains('unique_ptr<')) {
final declCursor = clang.clang_getTypeDeclaration(cxtype);
final usr = clang.clang_getCursorUSR(declCursor).toStringAndDispose();
final isStdUniquePtr = usr.contains('std@') && usr.contains('unique_ptr');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show me what one of these USRs looks like? Doing .contains here is a little odd. I would have thought you could just do final isStdUniquePtr = usr == "some string literal";. Is there a reason that won't work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libclang produces USRs in the following format:

c:@N@std@S@unique_ptr>#$@S@Node#$@N@std@S@default_delete>#S0_
So i think libclang appends the template argument types (such as >#$@s@Node...) directly into the USR string for each template instantiation, the full USR varies depending on the type parameter T.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Can you do usr.startsWith("c:@N@std@S@unique_ptr>#")?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

final usr = clang.clang_getCursorUSR(declCursor).toStringAndDispose();
final isStdUniquePtr = usr.contains('std@') && usr.contains('unique_ptr');
if (isStdUniquePtr) {
final spelling = clang

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cxtype.spelling()


final rawPtrVars = <String, String>{};
for (final p in ownedParams) {
rawPtrVars[p.name] = '_raw_${p.name}';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating these sorts of variables is exactly what LocalVariables is for. It would be better to use that. But actually, now that you've got the detachPointer method, you can probably inline it, and get rid of these local variables.

}

/// A pointer to a C++ class wrapper object.
/// Returned pointers are always unowned by default. The developer must call

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore this deleted comment?

final ownershipChecks = StringBuffer();
for (final p in ownedParams) {
final raw = rawPtrVars[p.name]!;
ownershipChecks.write(' final $raw = ${p.name}.detachPointer();\n');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detachPointer() call should happen in CppUniquePtrType.convertDartTypeToFfiDartType

if (method.isStatic) {
final callLine = hasReturn
? 'return $returnExpr;'
: '$glue($callArgs);';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can $glue($callArgs) be replaced with returnExpr?

} else {
final callLine = hasReturn
? 'return $returnExpr;'
: '$glue($callArgs);';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can $glue($callArgs) be replaced with returnExpr?

}
''');
} else {
final callLine = hasReturn

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This callLine variable is identical between both branches of this if statement, so you could deduplicate it by just moving it up next to the definition of hasReturn.

params = ['$selfType* self', ...otherParams].join(', ');
body = '${returnPrefix}self->${method.originalName}($callArgs);';
final methodName = method.originalName;
final suffix = method.returnType is CppUniquePtrType

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now, but you're probably going to run into more places where you need to reuse this return type conversion (and the _cppCallArg util), eg when adding support for top-level functions with C++ signatures.

I filed a bug to clean this up later, don't worry about it at the moment: #3523

@Hassnaa9
Hassnaa9 requested a review from liamappelbe August 6, 2026 01:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants