/** Helper functions for working with $(I C strings). This module is intended to provide fast, safe and garbage free way to work with $(I C strings). Copyright: Denis Shelomovskij 2013-2014 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: Denis Shelomovskij Macros: COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, `core.$1.$2`) */ module std.internal.cstring; /// @safe unittest { version (Posix) { import core.stdc.stdlib : free; import core.sys.posix.stdlib : setenv; import std.exception : enforce; void setEnvironment(scope const(char)[] name, scope const(char)[] value) { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); } } version (Windows) { import core.sys.windows.winbase : SetEnvironmentVariableW; import std.exception : enforce; void setEnvironment(scope const(char)[] name, scope const(char)[] value) { enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); } } } import std.range; import std.traits; /** Creates temporary 0-terminated $(I C string) with copy of passed text. Params: To = character type of returned C string str = string or input range to be converted Returns: The value returned is implicitly convertible to $(D const To*) and has two properties: `ptr` to access $(I C string) as $(D const To*) and `buffPtr` to access it as `To*`. The value returned can be indexed by [] to access it as an array. The temporary $(I C string) is valid unless returned object is destroyed. Thus if returned object is assigned to a variable the temporary is valid unless the variable goes out of scope. If returned object isn't assigned to a variable it will be destroyed at the end of creating primary expression. Implementation_note: For small strings tempCString will use stack allocated buffer, for large strings (approximately 250 characters and more) it will allocate temporary one using C's `malloc`. Note: This function is intended to be used in function call expression (like `strlen(str.tempCString())`). Incorrect usage of this function may lead to memory corruption. See $(RED WARNING) in $(B Examples) section. */ auto tempCString(To = char, From)(scope From str) if (isSomeChar!To && (isInputRange!From || isSomeString!From) && isSomeChar!(ElementEncodingType!From)) { alias CF = Unqual!(ElementEncodingType!From); auto res = TempCStringBuffer!To.trustedVoidInit(); // expensive to fill _buff[] // Note: res._ptr can't point to res._buff as structs are movable. // https://issues.dlang.org/show_bug.cgi?id=14980 static if (isSomeString!From) { if (str is null) { res._length = 0; res._ptr = null; return res; } } // Use slice assignment if available. static if (To.sizeof == CF.sizeof && is(typeof(res._buff[0 .. str.length] = str[]))) { if (str.length < res._buff.length) { res._buff[0 .. str.length] = str[]; res._buff[str.length] = 0; res._ptr = res.useStack; } else { import std.internal.memory : enforceMalloc; if (false) { // This code is removed by the compiler but causes `@safe`ty // to be inferred correctly. CF[0] x; x[] = str[0 .. 0]; } res._ptr = () @trusted { auto p = cast(CF*) enforceMalloc((str.length + 1) * CF.sizeof); p[0 .. str.length] = str[]; p[str.length] = 0; return cast(To*) p; }(); } res._length = str.length; return res; } else { static assert(!(isSomeString!From && CF.sizeof == To.sizeof), "Should be using slice assignment."); To[] p = res._buff; size_t i; size_t strLength; static if (hasLength!From) { strLength = str.length; } import std.utf : byUTF; static if (isSomeString!From) auto r = cast(const(CF)[])str; // because inout(CF) causes problems with byUTF else alias r = str; To[] heapBuffer; foreach (const c; byUTF!(Unqual!To)(r)) { if (i + 1 == p.length) { if (heapBuffer is null) heapBuffer = trustedReallocStack(p, strLength); else heapBuffer = trustedRealloc(heapBuffer); p = heapBuffer; } p[i++] = c; } p[i] = 0; res._length = i; res._ptr = (heapBuffer is null ? res.useStack : &heapBuffer[0]); return res; } } /// nothrow @nogc @system unittest { import core.stdc.string; string str = "abc"; // Intended usage assert(strlen(str.tempCString()) == 3); // Correct usage auto tmp = str.tempCString(); assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr` // $(RED WARNING): $(RED Incorrect usage) auto pInvalid1 = str.tempCString().ptr; const char* pInvalid2 = str.tempCString(); // Both pointers refer to invalid memory here as // returned values aren't assigned to a variable and // both primary expressions are ended. } @safe pure nothrow @nogc unittest { static inout(C)[] arrayFor(C)(inout(C)* cstr) pure nothrow @nogc @trusted { assert(cstr); size_t length = 0; while (cstr[length]) ++length; return cstr[0 .. length]; } assert(arrayFor("abc".tempCString()) == "abc"); assert(arrayFor("abc"d.tempCString().ptr) == "abc"); assert(arrayFor("abc".tempCString!wchar().buffPtr) == "abc"w); import std.utf : byChar, byWchar; char[300] abc = 'a'; assert(arrayFor(tempCString(abc[].byChar).buffPtr) == abc); assert(arrayFor(tempCString(abc[].byWchar).buffPtr) == abc); assert(tempCString(abc[].byChar)[] == abc); } // https://issues.dlang.org/show_bug.cgi?id=14980 pure nothrow @nogc @safe unittest { const(char[]) str = null; auto res = tempCString(str); const char* ptr = res; assert(ptr is null); } version (Windows) { import core.sys.windows.winnt : WCHAR; alias tempCStringW = tempCString!(WCHAR, const(char)[]); } private struct TempCStringBuffer(To = char) { @trusted pure nothrow @nogc: @disable this(); @disable this(this); alias ptr this; /// implicitly covert to raw pointer @property inout(To)* buffPtr() return inout { return _ptr == useStack ? _buff.ptr : _ptr; } @property const(To)* ptr() const { return buffPtr; } const(To)[] opIndex() const pure { return buffPtr[0 .. _length]; } ~this() { if (_ptr != useStack) { import core.memory : pureFree; pureFree(_ptr); } } private: enum To* useStack = () @trusted { return cast(To*) size_t.max; }(); To* _ptr; size_t _length; // length of the string version (StdUnittest) // the 'small string optimization' { // smaller size to trigger reallocations. Padding is to account for // unittest/non-unittest cross-compilation (to avoid corruption) To[16 / To.sizeof] _buff; To[(256 - 16) / To.sizeof] _unittest_pad; } else { To[256 / To.sizeof] _buff; // production size } static TempCStringBuffer trustedVoidInit() { TempCStringBuffer res = void; return res; } } private To[] trustedRealloc(To)(return scope To[] buf) @trusted @nogc pure nothrow { pragma(inline, false); // because it's rarely called import std.internal.memory : enforceRealloc; const size_t newlen = buf.length * 3 / 2; if (buf.length >= size_t.max / (2 * To.sizeof)) { version (D_Exceptions) { import core.exception : onOutOfMemoryError; onOutOfMemoryError(); } else { assert(0, "Memory allocation failed"); } } auto ptr = cast(To*) enforceRealloc(buf.ptr, newlen * To.sizeof); return ptr[0 .. newlen]; } private To[] trustedReallocStack(To)(scope To[] buf, size_t strLength) @trusted @nogc pure nothrow { pragma(inline, false); // because it's rarely called import std.internal.memory : enforceMalloc; size_t newlen = buf.length * 3 / 2; if (newlen <= strLength) newlen = strLength + 1; // +1 for terminating 0 auto ptr = cast(To*) enforceMalloc(newlen * To.sizeof); ptr[0 .. buf.length] = buf[]; return ptr[0 .. newlen]; }