From dc88e51321e0a4f281406c265a91308a06aa517f Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Tue, 7 Jun 2016 10:45:57 -0700 Subject: [PATCH] Nuke entry-point-based process args Provides a new fallback for Process arguments for those instances where we do not own main (e.g. Frameworks, Objective-C owns main.m or main.c, etc.). This includes a number of platform-specific specializations of argument grabbing logic and a new thread-safe interface to Process.unsafeArgv. main() | _NSGetArgc/_NSGetArgv | /proc/self/cmdline | __argc/__argv --------|--------------------------|------------------------|--------------- Scripts | OS X, iOS, tvOS, watchOS | Linux, FreeBSD, Cygwin | Windows For interpreted Swift where we must filter out the arguments we now do so by loading the standard library and calling into new SPI to override the arguments that would have been grabbed by the runtime. This implementation completely subsumes the use of the entry point '_stdlib_didEnterMain' and it will be removed in a future commit. --- lib/Immediate/Immediate.cpp | 40 ++++-- lib/Immediate/ImmediateImpl.h | 4 +- stdlib/public/SwiftShims/GlobalObjects.h | 3 - stdlib/public/SwiftShims/RuntimeStubs.h | 8 ++ stdlib/public/core/Process.swift | 61 +++------ stdlib/public/runtime/Leaks.h | 2 +- stdlib/public/stubs/CMakeLists.txt | 1 + stdlib/public/stubs/CommandLine.cpp | 120 ++++++++++++++++++ stdlib/public/stubs/GlobalObjects.cpp | 3 - .../ProcessStressTest/ProcessStressTest.c | 6 + .../ProcessStressTest/ProcessStressTest.h | 2 + .../ProcessStressTest/ProcessStressTest.swift | 53 ++++++++ test/1_stdlib/Process.swift | 12 ++ 13 files changed, 258 insertions(+), 57 deletions(-) create mode 100644 stdlib/public/stubs/CommandLine.cpp create mode 100644 test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.c create mode 100644 test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.h create mode 100644 test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.swift create mode 100644 test/1_stdlib/Process.swift diff --git a/lib/Immediate/Immediate.cpp b/lib/Immediate/Immediate.cpp index e2832712d5a26..b1e7a6f66580f 100644 --- a/lib/Immediate/Immediate.cpp +++ b/lib/Immediate/Immediate.cpp @@ -37,15 +37,17 @@ #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/IPO/PassManagerBuilder.h" #include "llvm/Support/Path.h" + #if defined(_MSC_VER) #include "Windows.h" #else #include #endif + using namespace swift; using namespace swift::immediate; -static bool loadRuntimeLib(StringRef runtimeLibPathWithName) { +static void *loadRuntimeLib(StringRef runtimeLibPathWithName) { #if defined(_MSC_VER) return LoadLibrary(runtimeLibPathWithName.str().c_str()); #else @@ -53,14 +55,14 @@ static bool loadRuntimeLib(StringRef runtimeLibPathWithName) { #endif } -static bool loadRuntimeLib(StringRef sharedLibName, StringRef runtimeLibPath) { +static void *loadRuntimeLib(StringRef sharedLibName, StringRef runtimeLibPath) { // FIXME: Need error-checking. llvm::SmallString<128> Path = runtimeLibPath; llvm::sys::path::append(Path, sharedLibName); return loadRuntimeLib(Path); } -bool swift::immediate::loadSwiftRuntime(StringRef runtimeLibPath) { +void *swift::immediate::loadSwiftRuntime(StringRef runtimeLibPath) { return loadRuntimeLib("libswiftCore" LTDL_SHLIB_EXT, runtimeLibPath); } @@ -303,6 +305,32 @@ int swift::RunImmediately(CompilerInstance &CI, const ProcessCmdLine &CmdLine, if (Context.hadError()) return -1; + // Load libSwiftCore to setup process arguments. + // + // This must be done here, before any library loading has been done, to avoid + // racing with the static initializers in user code. + auto stdlib = loadSwiftRuntime(Context.SearchPathOpts.RuntimeLibraryPath); + if (!stdlib) { + CI.getDiags().diagnose(SourceLoc(), + diag::error_immediate_mode_missing_stdlib); + return -1; + } + + // Setup interpreted process arguments. + using ArgOverride = void (*)(const char **, int); + auto emplaceProcessArgs + = (ArgOverride)dlsym(stdlib, "_swift_stdlib_overrideUnsafeArgvArgc"); + if (dlerror()) + return -1; + + SmallVector argBuf; + for (size_t i = 0; i < CmdLine.size(); ++i) { + argBuf.push_back(CmdLine[i].c_str()); + } + argBuf.push_back(nullptr); + + (*emplaceProcessArgs)(argBuf.data(), CmdLine.size()); + SmallVector InitFns; llvm::SmallPtrSet ImportedModules; if (IRGenImportedModules(CI, *Module, ImportedModules, InitFns, @@ -313,12 +341,6 @@ int swift::RunImmediately(CompilerInstance &CI, const ProcessCmdLine &CmdLine, PMBuilder.OptLevel = 2; PMBuilder.Inliner = llvm::createFunctionInliningPass(200); - if (!loadSwiftRuntime(Context.SearchPathOpts.RuntimeLibraryPath)) { - CI.getDiags().diagnose(SourceLoc(), - diag::error_immediate_mode_missing_stdlib); - return -1; - } - // Build the ExecutionEngine. llvm::EngineBuilder builder(std::move(ModuleOwner)); std::string ErrorMsg; diff --git a/lib/Immediate/ImmediateImpl.h b/lib/Immediate/ImmediateImpl.h index 3b0b979eb6386..b3be672433fcd 100644 --- a/lib/Immediate/ImmediateImpl.h +++ b/lib/Immediate/ImmediateImpl.h @@ -34,7 +34,9 @@ namespace swift { namespace immediate { -bool loadSwiftRuntime(StringRef runtimeLibPath); +// Returns a handle to the runtime suitable for other 'dlsym' or 'dlclose' +// calls or 'NULL' if an error occured. +void *loadSwiftRuntime(StringRef runtimeLibPath); bool tryLoadLibraries(ArrayRef LinkLibraries, SearchPathOptions SearchPathOpts, DiagnosticEngine &Diags); diff --git a/stdlib/public/SwiftShims/GlobalObjects.h b/stdlib/public/SwiftShims/GlobalObjects.h index 3e20f2e622496..7c9f21f49cf3e 100644 --- a/stdlib/public/SwiftShims/GlobalObjects.h +++ b/stdlib/public/SwiftShims/GlobalObjects.h @@ -42,9 +42,6 @@ struct _SwiftEmptyArrayStorage _swiftEmptyArrayStorage; extern SWIFT_RUNTIME_STDLIB_INTERFACE __swift_uint64_t _swift_stdlib_HashingDetail_fixedSeedOverride; -extern SWIFT_RUNTIME_STDLIB_INTERFACE -void *_swift_stdlib_ProcessArguments; - #ifdef __cplusplus }} // extern "C", namespace swift #endif diff --git a/stdlib/public/SwiftShims/RuntimeStubs.h b/stdlib/public/SwiftShims/RuntimeStubs.h index 6b520a066de29..f96c9d93cddda 100644 --- a/stdlib/public/SwiftShims/RuntimeStubs.h +++ b/stdlib/public/SwiftShims/RuntimeStubs.h @@ -31,6 +31,14 @@ SWIFT_RUNTIME_STDLIB_INTERFACE __swift_ssize_t swift_stdlib_readLine_stdin(char * _Nullable * _Nonnull LinePtr); +SWIFT_RUNTIME_STDLIB_INTERFACE +char * _Nullable * _Nonnull +_swift_stdlib_getUnsafeArgvArgc(int * _Nonnull outArgLen); + +SWIFT_RUNTIME_STDLIB_INTERFACE +void +_swift_stdlib_overrideUnsafeArgvArgc(char * _Nullable * _Nonnull argv, int argc); + SWIFT_END_NULLABILITY_ANNOTATIONS #ifdef __cplusplus diff --git a/stdlib/public/core/Process.swift b/stdlib/public/core/Process.swift index 64f64ed7f670d..0d54c8fa5a381 100644 --- a/stdlib/public/core/Process.swift +++ b/stdlib/public/core/Process.swift @@ -12,36 +12,26 @@ import SwiftShims -internal class _Box { - internal var _value: Wrapped - internal init(_ value: Wrapped) { self._value = value } -} - /// Command-line arguments for the current process. public enum Process { - /// Return an array of string containing the list of command-line arguments - /// with which the current process was invoked. - internal static func _computeArguments() -> [String] { - var result: [String] = [] - let argv = unsafeArgv - for i in 0..?>? - = nil + UnsafeMutablePointer?> + = _swift_stdlib_getUnsafeArgvArgc(&_argc) /// Access to the raw argc value from C. public static var argc: Int32 { + _ = Process.unsafeArgv // Force evaluation of argv. return _argc } @@ -49,32 +39,16 @@ public enum Process { /// through this pointer is unsafe. public static var unsafeArgv: UnsafeMutablePointer?> { - return _unsafeArgv! + return _unsafeArgv } /// Access to the swift arguments, also use lazy initialization of static /// properties to safely initialize the swift arguments. - /// - /// NOTE: we can not use static lazy let initializer as they can be moved - /// around by the optimizer which will break the data dependence on argc - /// and argv. - public static var arguments: [String] { - let argumentsPtr = UnsafeMutablePointer( - Builtin.addressof(&_swift_stdlib_ProcessArguments)) - - // Check whether argument has been initialized. - if let arguments = _stdlib_atomicLoadARCRef(object: argumentsPtr) { - return (arguments as! _Box<[String]>)._value - } - - let arguments = _Box<[String]>(_computeArguments()) - _stdlib_atomicInitializeARCRef(object: argumentsPtr, desired: arguments) - - return arguments._value - } + public static var arguments: [String] + = (0.. { + internal var _value: Wrapped + internal init(_ value: Wrapped) { self._value = value } +} + diff --git a/stdlib/public/runtime/Leaks.h b/stdlib/public/runtime/Leaks.h index 27b2f17d87823..fa233e61dae52 100644 --- a/stdlib/public/runtime/Leaks.h +++ b/stdlib/public/runtime/Leaks.h @@ -12,7 +12,7 @@ // // This is a very simple leak detector implementation that detects objects that // are allocated but not deallocated in a region. It is purposefully behind a -// flag since it is not meant to be used in +// flag since it is not meant to be used in production yet. // //===----------------------------------------------------------------------===// diff --git a/stdlib/public/stubs/CMakeLists.txt b/stdlib/public/stubs/CMakeLists.txt index 954657352f82d..1f4f15134fe4a 100644 --- a/stdlib/public/stubs/CMakeLists.txt +++ b/stdlib/public/stubs/CMakeLists.txt @@ -16,6 +16,7 @@ endif() add_swift_library(swiftStdlibStubs OBJECT_LIBRARY TARGET_LIBRARY Assert.cpp + CommandLine.cpp GlobalObjects.cpp LibcShims.cpp Stubs.cpp diff --git a/stdlib/public/stubs/CommandLine.cpp b/stdlib/public/stubs/CommandLine.cpp new file mode 100644 index 0000000000000..9c1b67c6f4c56 --- /dev/null +++ b/stdlib/public/stubs/CommandLine.cpp @@ -0,0 +1,120 @@ +//===--- CommandLine.cpp - OS-specific command line arguments -------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// OS-specific command line argument handling is defined here. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "swift/Runtime/Debug.h" + +#include "../SwiftShims/RuntimeStubs.h" +#include "../SwiftShims/GlobalObjects.h" + +// Backing storage for overrides of `Swift.Process.arguments`. +static char **_swift_stdlib_ProcessOverrideUnsafeArgv = nullptr; +static int _swift_stdlib_ProcessOverrideUnsafeArgc = 0; + +SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE +extern "C" void _swift_stdlib_overrideUnsafeArgvArgc(char **argv, int argc) { + _swift_stdlib_ProcessOverrideUnsafeArgv = argv; + _swift_stdlib_ProcessOverrideUnsafeArgc = argc; +} + +#if defined(__APPLE__) +// NOTE: forward declare this rather than including crt_externs.h as not all +// SDKs provide it +extern "C" char ***_NSGetArgv(void); +extern "C" int *_NSGetArgc(void); + +SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE +extern "C" char ** _swift_stdlib_getUnsafeArgvArgc(int *outArgLen) { + assert(outArgLen != nullptr); + + if (_swift_stdlib_ProcessOverrideUnsafeArgv) { + *outArgLen = _swift_stdlib_ProcessOverrideUnsafeArgc; + return _swift_stdlib_ProcessOverrideUnsafeArgv; + } + + *outArgLen = *_NSGetArgc(); + return *_NSGetArgv(); +} +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__FreeBSD__) +SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE +extern "C" char ** _swift_stdlib_getUnsafeArgvArgc(int *outArgLen) { + assert(outArgLen != nullptr); + + if (_swift_stdlib_ProcessOverrideUnsafeArgv) { + *outArgLen = _swift_stdlib_ProcessOverrideUnsafeArgc; + return _swift_stdlib_ProcessOverrideUnsafeArgv; + } + + FILE *cmdline = fopen("/proc/self/cmdline", "rb"); + if (!cmdline) { + swift::fatalError(0, + "fatal error: Unable to open interface to '/proc/self/cmdline'.\n"); + } + char *arg = nullptr; + size_t size = 0; + std::vector argvec; + while (getdelim(&arg, &size, 0, cmdline) != -1) { + argvec.push_back(strdup(arg)); + } + if (arg) { + free(arg); + } + fclose(cmdline); + *outArgLen = argvec.size(); + char **outBuf = (char **)calloc(argvec.size() + 1, sizeof(char *)); + std::copy(argvec.begin(), argvec.end(), outBuf); + outBuf[argvec.size()] = nullptr; + + return outBuf; +} +#elif defined (_MSC_VER) +extern int *__argc; +extern char **__argv; + +SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE +extern "C" char ** _swift_stdlib_getUnsafeArgvArgc(int *outArgLen) { + assert(outArgLen != nullptr); + + if (_swift_stdlib_ProcessOverrideUnsafeArgv) { + *outArgLen = _swift_stdlib_ProcessOverrideUnsafeArgc; + return _swift_stdlib_ProcessOverrideUnsafeArgv; + } + + *outArgLen = __argc; + return __argv; +} +#else // __ANDROID__; Add your favorite arch's command line arg grabber here. +SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE +extern "C" char ** _swift_stdlib_getUnsafeArgvArgc(int *outArgLen) { + if (_swift_stdlib_ProcessOverrideUnsafeArgv) { + *outArgLen = _swift_stdlib_ProcessOverrideUnsafeArgc; + return _swift_stdlib_ProcessOverrideUnsafeArgv; + } + + swift::fatalError(0, + "fatal error: Command line arguments not supported on this platform.\n"); +} +#endif + diff --git a/stdlib/public/stubs/GlobalObjects.cpp b/stdlib/public/stubs/GlobalObjects.cpp index 11a5d7ee6d2a1..f2d463f03d2be 100644 --- a/stdlib/public/stubs/GlobalObjects.cpp +++ b/stdlib/public/stubs/GlobalObjects.cpp @@ -41,9 +41,6 @@ swift::_SwiftEmptyArrayStorage swift::_swiftEmptyArrayStorage = { __swift_uint64_t swift::_swift_stdlib_HashingDetail_fixedSeedOverride = 0; -/// Backing storage for Swift.Process.arguments. -void *swift::_swift_stdlib_ProcessArguments = nullptr; - namespace llvm { namespace hashing { namespace detail { // An extern variable expected by LLVM's hashing templates. We don't link any // LLVM libs into the runtime, so define this here. diff --git a/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.c b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.c new file mode 100644 index 0000000000000..a4ce77a0ecaec --- /dev/null +++ b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.c @@ -0,0 +1,6 @@ +#include "ProcessStressTest.h" + +int main(int argc, char **argv) { + swift_process_test_getProcessArgs(); + return 0; +} diff --git a/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.h b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.h new file mode 100644 index 0000000000000..fbdf898b19e6e --- /dev/null +++ b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.h @@ -0,0 +1,2 @@ +// Declared in ProcessStressTest.swift. +extern void swift_process_test_getProcessArgs(void); diff --git a/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.swift b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.swift new file mode 100644 index 0000000000000..7afac49544a77 --- /dev/null +++ b/test/1_stdlib/Inputs/ProcessStressTest/ProcessStressTest.swift @@ -0,0 +1,53 @@ +// Do not change the SIL name for this without also changing ProcessStressTest.c +@_silgen_name("swift_process_test_getProcessArgs") +public func runTest() { + let ProcessRaceTestSuite = TestSuite("Process Race") + + ProcessRaceTestSuite.test("passes") { + runRaceTest(ProcessRace.self, trials: 1) + } + + runAllTests() +} + +import StdlibUnittest + +struct ProcessRace : RaceTestWithPerTrialData { + class ProcessRaceData { + init() {} + } + + typealias ThreadLocalData = Void + typealias Observation = Observation1UInt + + func makeRaceData() -> ProcessRaceData { + return ProcessRaceData() + } + + func makeThreadLocalData() -> Void { + return Void() + } + + func thread1( + _ raceData: ProcessRaceData, _ threadLocalData: inout ThreadLocalData + ) -> Observation { + let argptr = Process.unsafeArgv + return Observation(unsafeBitCast(argptr, to: UInt.self)) + } + + func evaluateObservations( + _ observations: [Observation], + _ sink: (RaceTestObservationEvaluation) -> Void + ) { + guard let fstObs = observations.first?.data1 else { + return + } + for observation in observations { + if observation.data1 == fstObs { + sink(.pass) + } else { + sink(.failure) + } + } + } +} diff --git a/test/1_stdlib/Process.swift b/test/1_stdlib/Process.swift new file mode 100644 index 0000000000000..1205c3ac22aec --- /dev/null +++ b/test/1_stdlib/Process.swift @@ -0,0 +1,12 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// +// +// RUN: %target-build-swift %S/Inputs/ProcessStressTest/ProcessStressTest.swift -parse-as-library -force-single-frontend-invocation -module-name ProcessStressTestSwift -emit-object -o %t/ProcessStressTestSwift.o +// RUN: %clang -arch %target-cpu -c -o %t/ProcessStressTest.o -x c %S/Inputs/ProcessStressTest/ProcessStressTest.c +// RUN: %target-build-swift %t/ProcessStressTest.o %t/ProcessStressTestSwift.o -o %t/ProcessStressTest +// RUN: %target-run %t/ProcessStressTest foo bar baz qux quux corge grault garply waldo fred plugh xyzzy and thud +// REQUIRES: executable_test + +// This file is an empty stub to call into the process stress test which +// houses `main`.