Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grabbing Mac computer name through C++ Interop hangs #69870

Open
efroemling opened this issue Nov 14, 2023 · 5 comments
Open

Grabbing Mac computer name through C++ Interop hangs #69870

efroemling opened this issue Nov 14, 2023 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. c++ interop Feature: Interoperability with C++ triage needed This issue needs more specific labels

Comments

@efroemling
Copy link

efroemling commented Nov 14, 2023

Description
I've found that defining a simple function in Swift to grab the name of the local Mac and calling that from C++ can hang if the Mac name contains particular characters.

Steps to reproduce
The following steps reproduce the hang for me using XCode 15.0.1 (15A507) on an M1 Max MBP running Sonoma 14.1.1:

  • Create a new ‘Command Line Tool’ project named “TestCommand”.
  • Create a new C++ file named ‘test’ (with header). Click ‘Create Bridging Header’ when it asks.
  • In TestCommand-Bridging-Header.h, add:
    #include "test.hpp"
  • In test.hpp, add:
    void doTest();
  • In test.cpp, add:
    #include <TestCommand-Swift.h>
    
    void doTest() {
      printf("Getting Mac name via Swift...\n");
      std::string val = TestCommand::getMacName();
      printf("Got Mac name: %s\n", val.c_str());
    }
  • In main.swift, add:
    // Expose a call for C++ to use.
    public func getMacName() -> String {
      return Host.current().localizedName ?? "Unknown"
    }
    
    // Call our C++ function that runs the test.
    doTest()
  • Under build settings for the TestCommand target, set C++ and Objective-C Interoperability to “C++/Objective C++”
  • You should now be able to build and run the project.
  • Now, depending on the name of the Mac (as per System Settings -> General -> About), running should either print the name or hang indefinitely. In my case, if my Mac is named "Eric’s MacBook Pro" I get a hang, but if I rename my Mac in System Settings and remove the apostrophe then the run completes.
  • Interestingly, hard-coding the Swift function to return the exact string "Eric’s MacBook Pro" succeeds, whatever that might imply.

Environment

  • Swift compiler version info: swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)

  • Xcode version info: Xcode 15.0.1 Build version 15A507

  • Deployment target: macOS Sonoma 14.1.1 (23B81). Also repro'd on Monterey 12.7.1 (21G920)

@efroemling efroemling added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Nov 14, 2023
@egorzhdan egorzhdan added the c++ interop Feature: Interoperability with C++ label Dec 11, 2023
@egorzhdan
Copy link
Collaborator

Looks like the conversion from swift::String to std::string is problematic for certain strings.
Since the code runs fine with an identical hardcoded string, I would assume that the issue is with Swift strings that are bridged from Objective-C.

@efroemling is Host.current().localizedName something that comes from Objective-C? Would you be able to share its definition if possible?

@efroemling
Copy link
Author

efroemling commented Dec 11, 2023

@efroemling is Host.current().localizedName something that comes from Objective-C? Would you be able to share its definition if possible?

That's just a standard Apple call from Foundation (albeit a deprecated one).
https://developer.apple.com/documentation/foundation/host/1409624-localizedname

I'm assuming it's Objective-C under the hood since it's been around for a while, but you'd have to check on your end to be sure.

That repro case should be fully self contained if you want to give it a try. I'll go through it now and make sure it's still causing the same problem on my end. One note is to make sure to use a curly quote in the machine name (’) to trigger the problem; I'm not sure if straight quotes (') will do it.

@efroemling
Copy link
Author

Just went through the steps on Xcode 15.0.1 on the macOS 14.2 RC and it's still exhibiting the hang. I also checked and found that adding a straight quote (') to the machine name does not trigger the problem; only a curly quote does (’).

@egorzhdan
Copy link
Collaborator

Thanks @efroemling, I can reproduce this on my end.

Looks like we're getting into an infinite loop while converting the Swift String into a std::string, and using more and more memory on every iteration.

@ludwwwig
Copy link
Contributor

ludwwwig commented Apr 25, 2024

Edit: I realized that I'm quite late to the party. Hopefully my answer below can contribute to getting this issue fixed. I have the ambition of becoming an active contributor to the Swift project, and I find the c++ interop feature to be especially interesting.

This issue seems to happen when we are dealing with non-contiguous strings which is defined in the above link as:

  • Lazily-bridged Cocoa strings that don’t provide a pointer to contiguous ASCII (even if they do have contiguous UTF-16 code units)
  • Any “foreign string” concept we may add in the future

A temporary workaround is to modify main.swift into

// Expose a call for C++ to use.
public func getMacName() -> String {
  var s = Host.current().localizedName ?? "Unknown"
  s.makeContiguousUTF8()
  return s
}

// Call our C++ function that runs the test.
doTest()

If we do not make the String contiguous, we end up in an endless loop

SWIFT_INLINE_THUNK String::operator std::string() const {
  auto u = getUtf8();
  std::string result;
  result.reserve(u.getCount() + 1);
  using IndexType = decltype(u.getStartIndex());
  for (auto s = u.getStartIndex().getEncodedOffset(),
            e = u.getEndIndex().getEncodedOffset();
       s != e; s = u.indexOffsetBy(IndexType::init(s), 1).getEncodedOffset()) {
    result.push_back(u[IndexType::init(s)]);
  }
  return result;
}

defined in

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h

and

swift-project/swift/lib/PrintAsClang/_SwiftStdlibCxxOverlay.h.

Original post:

I have been experimenting a bit with this, below are my findings.

First of all, as @efroemling identified, the problem appears to happen when using an acute accent (´) but not '. I continued testing a bit and found the problem to occur specifically when using non-ASCII characters. Hence, both ' and ` work, but neither ´ nor 🧑‍💻.

I modified the getMacName() function so that the string is created using Swift's String type:

// Expose a call for C++ to use.
public func getMacName() -> String {
    return String("Ludwig´s-MacBook-Pro")
}

And the problem appears to be gone. If we now instead try using NSString:

// Expose a call for C++ to use.
public func getMacName() -> String {
        let a = NSString("Ludwig´s-MacBook-Pro")
        return String(a)
}

And the problem is back. It appears as if once we create an NSString there is no way to avoid the issue, even after converting to String. I guess in the original example:

return Host.current().localizedName ?? "Unknown"

We implicitly convert from NSString to String. Perhaps this conversion is the real issue, leading to a faulty String that can't later be converted to an std::string, just as @egorzhdan found?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. c++ interop Feature: Interoperability with C++ triage needed This issue needs more specific labels
Projects
None yet
Development

No branches or pull requests

3 participants