Skip to content

Commit 6017634

Browse files
atilaneveswilzbach
authored andcommitted
Fix issue 19058 - __traits(getUnitTests) works again with separate compilation
1 parent 989c6e1 commit 6017634

4 files changed

Lines changed: 170 additions & 24 deletions

File tree

src/dmd/identifier.d

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import dmd.root.stringtable;
2323
import dmd.tokens;
2424
import dmd.utf;
2525

26+
2627
/***********************************************************
2728
*/
2829
extern (C++) final class Identifier : RootObject
@@ -124,6 +125,13 @@ nothrow:
124125

125126
extern (C++) static __gshared StringTable stringtable;
126127

128+
/**
129+
A secondary string table is used to guarantee that we generate unique
130+
identifiers per module. See generateIdWithLoc and issues
131+
16995, 18097, 18111, 18880, 18868, 19058
132+
*/
133+
private extern (C++) static __gshared StringTable fullPathStringTable;
134+
127135
static Identifier generateId(const(char)* prefix)
128136
{
129137
static __gshared size_t i;
@@ -153,23 +161,67 @@ nothrow:
153161
*/
154162
extern (D) static Identifier generateIdWithLoc(string prefix, const ref Loc loc)
155163
{
156-
OutBuffer buf;
157-
buf.writestring(prefix);
158-
buf.writestring("_L");
159-
buf.print(loc.linnum);
160-
buf.writestring("_C");
161-
buf.print(loc.charnum);
162-
auto basesize = buf.peekSlice().length;
163-
uint counter = 1;
164-
while (stringtable.lookup(buf.peekSlice().ptr, buf.peekSlice().length) !is null)
164+
import dmd.root.filename: absPathThen;
165+
166+
// see below for why we use absPathThen
167+
return loc.filename.absPathThen!((absPath)
165168
{
166-
// Strip the extra suffix
167-
buf.setsize(basesize);
168-
// Add new suffix with increased counter
169-
buf.writestring("_");
170-
buf.print(counter++);
171-
}
172-
return idPool(buf.peekSlice());
169+
170+
// this block generates the "regular" identifier, i.e. if there are no collisions
171+
OutBuffer idBuf;
172+
idBuf.writestring(prefix);
173+
idBuf.writestring("_L");
174+
idBuf.print(loc.linnum);
175+
idBuf.writestring("_C");
176+
idBuf.print(loc.charnum);
177+
178+
// This block generates an identifier that is prefixed by the absolute path of the file
179+
// being compiled. The reason this is necessary is that we want unique identifiers per
180+
// module, but the identifiers are generated before the module information is available.
181+
// To guarantee that each generated identifier is unique without modules, we make them
182+
// unique to each absolute file path. This also makes it consistent even if the files
183+
// are compiled separately. See issues
184+
// 16995, 18097, 18111, 18880, 18868, 19058.
185+
OutBuffer fullPathIdBuf;
186+
if(absPath)
187+
{
188+
// replace characters that demangle can't handle
189+
for(auto ptr = absPath; *ptr != '\0'; ++ptr)
190+
{
191+
if(*ptr == '/' || *ptr == '\\' || *ptr == '.' || *ptr == '?' || *ptr == ':')
192+
*ptr = '_';
193+
}
194+
195+
fullPathIdBuf.writestring(absPath);
196+
fullPathIdBuf.writestring("_");
197+
}
198+
199+
fullPathIdBuf.writestring(idBuf.peekSlice());
200+
const fullPathIdLength = fullPathIdBuf.peekSlice().length;
201+
uint counter = 1;
202+
203+
// loop until we can't find the absolute path ~ identifier, adding a counter suffix each time
204+
while (fullPathStringTable.lookup(fullPathIdBuf.peekSlice().ptr, fullPathIdBuf.peekSlice().length) !is null)
205+
{
206+
// Strip the counter suffix if any
207+
fullPathIdBuf.setsize(fullPathIdLength);
208+
// Add new counter suffix
209+
fullPathIdBuf.writestring("_");
210+
fullPathIdBuf.print(counter++);
211+
}
212+
213+
// `idStartIndex` is the start of the "true" identifier. We don't actually use the absolute
214+
// file path in the generated identifier since the module system makes sure that the fully
215+
// qualified name is unique.
216+
const idStartIndex = fullPathIdLength - idBuf.peekSlice().length;
217+
218+
// Remember the full path identifier to avoid possible future collisions
219+
fullPathStringTable.insert(fullPathIdBuf.peekString(),
220+
fullPathIdBuf.peekSlice().length,
221+
null);
222+
223+
return idPool(fullPathIdBuf.peekSlice()[idStartIndex .. $]);
224+
});
173225
}
174226

175227
/********************************************
@@ -240,6 +292,8 @@ nothrow:
240292

241293
static void initTable()
242294
{
243-
stringtable._init(28000);
295+
enum size = 28_000;
296+
stringtable._init(size);
297+
fullPathStringTable._init(size);
244298
}
245299
}

src/dmd/root/filename.d

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@ version(Windows)
793793
*/
794794
private int _mkdir(const(char)* path) nothrow
795795
{
796+
import core.sys.windows.winbase: CreateDirectoryW;
796797
const createRet = path.extendedPathThen!(p => CreateDirectoryW(p,
797798
null /*securityAttributes*/));
798799
// different conventions for CreateDirectory and mkdir
@@ -810,8 +811,10 @@ version(Windows)
810811
* References:
811812
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
812813
*/
813-
package auto extendedPathThen(alias F)(const(char*) path)
814+
auto extendedPathThen(alias F)(const(char*) path)
814815
{
816+
import core.sys.windows.winbase: GetFullPathNameW;
817+
815818
return path.toWStringzThen!((wpath)
816819
{
817820
// GetFullPathNameW expects a sized buffer to store the result in. Since we don't
@@ -823,7 +826,7 @@ version(Windows)
823826
null /*filePartBuffer*/);
824827
if (pathLength == 0)
825828
{
826-
return F(""w.ptr);
829+
return F(cast(wchar*) null);
827830
}
828831

829832
// wpath is the UTF16 version of path, but to be able to use
@@ -846,7 +849,7 @@ version(Windows)
846849

847850
if (absPathRet == 0 || absPathRet > absPath.length - prefix.length)
848851
{
849-
return F(""w.ptr);
852+
return F(cast(wchar*) null);
850853
}
851854

852855
return F(absPath.ptr);
@@ -867,13 +870,14 @@ version(Windows)
867870
{
868871
import core.stdc.string: strlen;
869872
import core.stdc.stdlib: malloc, free;
873+
import core.sys.windows.winnls: MultiByteToWideChar;
870874

871875
wchar[1024] buf;
872876
// cache this for efficiency
873877
const int strLength = cast(int)(strlen(str) + 1);
874878
// first find out how long the buffer must be to store the result
875879
const length = MultiByteToWideChar(0 /*codepage*/, 0 /*flags*/, str, strLength, null, 0);
876-
if (!length) return F(""w.ptr);
880+
if (!length) return F(cast(wchar*) null);
877881

878882
auto ret = length > buf.length
879883
? (cast(wchar*)malloc(length * wchar.sizeof))
@@ -889,4 +893,77 @@ version(Windows)
889893

890894
return F(ret);
891895
}
896+
897+
}
898+
899+
version(Posix)
900+
{
901+
902+
/**************************
903+
* Takes a callable F and applies it to the result of converting
904+
* `fileName` to an absolute file path (char*)
905+
*/
906+
auto absPathThen(alias F)(const(char)* fileName)
907+
{
908+
char[1024] realPathBuffer;
909+
auto absPath = realpath(fileName, &realPathBuffer[0]);
910+
return F(absPath);
911+
}
912+
913+
extern(C) char* realpath(const(char)* file_name, char* resolved_name) @nogc nothrow;
914+
}
915+
else
916+
{
917+
/**************************
918+
* Takes a callable F and applies it to the result of converting
919+
* `fileName` to an absolute file path (char*)
920+
*/
921+
auto absPathThen(alias F)(const(char)* fileName)
922+
{
923+
import core.sys.windows.winnls: WideCharToMultiByte;
924+
import core.stdc.stdlib: malloc, free;
925+
import std.stdio: writeln;
926+
import std.conv: text;
927+
928+
return fileName.extendedPathThen!((wpath) {
929+
// first find out how long the buffer must be to store the result
930+
const length = WideCharToMultiByte(0, // code page
931+
0, // flags
932+
wpath,
933+
-1, // wpath len, -1 is null terminated
934+
null, // multibyte output ptr
935+
0, // multibyte output length
936+
null, // default char
937+
null, // if used default char
938+
);
939+
940+
if(!length) return F(cast(char*) null);
941+
942+
char[1024] buf;
943+
944+
auto multibyteBuf = length > buf.length
945+
? (cast(char*)malloc(length * char.sizeof))
946+
: &buf[0];
947+
scope (exit)
948+
{
949+
if (multibyteBuf != &buf[0])
950+
free(multibyteBuf);
951+
}
952+
953+
// first find out how long the buffer must be to store the result
954+
const length2 = WideCharToMultiByte(0, // code page
955+
0, // flags
956+
wpath,
957+
-1, // wpath len, -1 is null terminated
958+
multibyteBuf,
959+
length,
960+
null, // default char
961+
null, // if used default char
962+
);
963+
964+
assert(length == length2);
965+
966+
return F(multibyteBuf);
967+
});
968+
}
892969
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
unittest {}
2+
unittest {}
3+
unittest { assert(false); }

test/runnable/issue16995.d

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// REQUIRED_ARGS: -unittest
22
// COMPILE_SEPARATELY
3-
// EXTRA_SOURCES: imports/module_with_tests.d
3+
// EXTRA_SOURCES: imports/module_with_tests.d imports/another_module_with_tests.d
44

55
import imports.module_with_tests;
6+
import imports.another_module_with_tests;
67
import core.exception: AssertError;
78

89
shared static this()
@@ -13,8 +14,7 @@ shared static this()
1314

1415
void main()
1516
{
16-
import module_with_tests;
17-
foreach(i, ut; __traits(getUnitTests, module_with_tests)) {
17+
foreach(i, ut; __traits(getUnitTests, imports.module_with_tests)) {
1818
try
1919
{
2020
ut();
@@ -25,4 +25,16 @@ void main()
2525
assert(i == 1, "Only 2nd unittest should fail");
2626
}
2727
}
28+
29+
foreach(i, ut; __traits(getUnitTests, imports.another_module_with_tests)) {
30+
try
31+
{
32+
ut();
33+
assert(i == 0 || i == 1, "3rd unittest should fail");
34+
}
35+
catch(AssertError e)
36+
{
37+
assert(i == 2, "Only 3rd unittest should fail");
38+
}
39+
}
2840
}

0 commit comments

Comments
 (0)