Skip to content

[SR-7526] Foundation.NSPathUtilities.resolvingSymlinksInPath crashes for "/" #3715

@swift-ci

Description

@swift-ci
Previous ID SR-7526
Radar None
Original Reporter felix91gr (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Linux, Elementary OS 0.4 Loki

Ubuntu Upstream: 16.04 LTS

Additional Detail from JIRA
Votes 0
Component/s Foundation
Labels Bug, Linux, RunTimeCrash
Assignee felix91gr (JIRA)
Priority Medium

md5: aaf24b7256119e57a9c2578dec9efffb

Issue Description:

Hi. Open the `REPL` on linux and try this:

  1> import Foundation

  2> NSString(string: "/").resolvingSymlinksInPath

  3> NSString(string: "/lib/").resolvingSymlinksInPath

It crashes on the first attempt but not the second one. Prying open the implementation on the `corelibs` repo:

public var resolvingSymlinksInPath: String {
    var components = pathComponents
    guard !components.isEmpty else {
        return _swiftObject
    }
    
    // TODO: pathComponents keeps final path separator if any. Check that logic.
    if components.last == "/" {
        components.removeLast()
    }
    
    let isAbsolutePath = components.first == "/"
    
    var resolvedPath = components.removeFirst()
    for component in components {
        switch component {
            
        case "", ".":
            break
            
        case ".." where isAbsolutePath:
            resolvedPath = resolvedPath._bridgeToObjectiveC().deletingLastPathComponent
            
        default:
            resolvedPath = resolvedPath._bridgeToObjectiveC().appendingPathComponent(component)
            if let destination = FileManager.default._tryToResolveTrailingSymlinkInPath(resolvedPath) {
                resolvedPath = destination
            }
        }
    }
    
    let privatePrefix = "/private"
    resolvedPath = resolvedPath._tryToRemovePathPrefix(privatePrefix) ?? resolvedPath
    
    return resolvedPath
}

And then running it on the `REPL`:

Welcome to Swift version 4.1 (swift-4.1-RELEASE). Type :help for assistance.
  1> import Foundation
  2> let source = NSString(string: "/")
source: Foundation.NSString = {
  Foundation.NSObject = {}
  _cfinfo = {
    info = 1920
    pad = 0
  }
  _storage = "/"
}
  3> var components = source.pathComponents
components: [String] = 1 value {
  [0] = "/"
}
  4> components.last
$R0: String? = "/"
  5> components.removeLast()
$R1: String = "/"
  6> components.first == "/"
$R2: Bool = false
  7> components.removeFirst()
Fatal error: Can't remove first element from an empty collection
Current stack trace:
0    libswiftCore.so                    0x00007ffff7ce2750 _swift_stdlib_reportFatalError + 171
1    libswiftCore.so                    0x00007ffff7a50ad6 <unavailable> + 1366742
2    libswiftCore.so                    0x00007ffff7c8b383 <unavailable> + 3703683
3    libswiftCore.so                    0x00007ffff7a50ad6 <unavailable> + 1366742
4    libswiftCore.so                    0x00007ffff7bbcd80 <unavailable> + 2858368
5    libswiftCore.so                    0x00007ffff7a3e000 RangeReplaceableCollection.removeFirst() + 398
Execution interrupted. Enter code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
Process 15909 stopped
* thread #&#8203;1, name = 'repl_swift', stop reason = signal SIGILL: illegal instruction operand
    frame #&#8203;0: 0x00007ffff7bbcd80 libswiftCore.so`function signature specialization <Arg[2] = Dead, Arg[3] = Dead> of Swift._fatalErrorMessage(Swift.StaticString, Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 96
libswiftCore.so`function signature specialization <Arg[2] = Dead, Arg[3] = Dead> of Swift._fatalErrorMessage(Swift.StaticString, Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never:
->  0x7ffff7bbcd80 <+96>: ud2    
    0x7ffff7bbcd82:       nopw   %cs:(%rax,%rax)
libswiftCore.so`function signature specialization <Arg[0] = Owned To Guaranteed and Exploded, Arg[1] = Exploded> of Swift.String._compareDeterministicUnicodeCollation(Swift.String) -> Swift.Int:
    0x7ffff7bbcd90 <+0>:  pushq  %rbp
    0x7ffff7bbcd91 <+1>:  movq   %rsp, %rbp
Target 0: (repl_swift) stopped.
  8>  

Clearly this part is the problem:

if components.last == "/" {
    components.removeLast()
}

For a path like "/" which has only one character, "resolvingSymlinksInPath" should return "/" or whatever the symlink for it might be (can "/" have symlinks? Anyway...). Therefore the code should look something like this:

if components.last == "/" && components.count > 1 {
    components.removeLast()
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions