1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03
10 
11 // These tests require locale for non-char paths
12 // UNSUPPORTED: no-localization
13 
14 // <filesystem>
15 
16 // class path
17 
18 // path& operator/=(path const&)
19 // template <class Source>
20 //      path& operator/=(Source const&);
21 // template <class Source>
22 //      path& append(Source const&);
23 // template <class InputIterator>
24 //      path& append(InputIterator first, InputIterator last);
25 
26 
27 #include "filesystem_include.h"
28 #include <type_traits>
29 #include <string_view>
30 #include <cassert>
31 
32 // On Windows, the append function converts all inputs (pointers, iterators)
33 // to an intermediate path object, causing allocations in cases where no
34 // allocations are done on other platforms.
35 
36 #include "test_macros.h"
37 #include "test_iterators.h"
38 #include "count_new.h"
39 #include "filesystem_test_helper.h"
40 
41 
42 struct AppendOperatorTestcase {
43   MultiStringType lhs;
44   MultiStringType rhs;
45   MultiStringType expect_posix;
46   MultiStringType expect_windows;
47 
expected_resultAppendOperatorTestcase48   MultiStringType const& expected_result() const {
49 #ifdef _WIN32
50     return expect_windows;
51 #else
52     return expect_posix;
53 #endif
54   }
55 };
56 
57 #define S(Str) MKSTR(Str)
58 const AppendOperatorTestcase Cases[] =
59     {
60         {S(""),        S(""),         S(""),              S("")}
61       , {S("p1"),      S("p2"),       S("p1/p2"),         S("p1\\p2")}
62       , {S("p1/"),     S("p2"),       S("p1/p2"),         S("p1/p2")}
63       , {S("p1"),      S("/p2"),      S("/p2"),           S("/p2")}
64       , {S("p1/"),     S("/p2"),      S("/p2"),           S("/p2")}
65       , {S("p1"),      S("\\p2"),     S("p1/\\p2"),       S("\\p2")}
66       , {S("p1\\"),    S("p2"),       S("p1\\/p2"),       S("p1\\p2")}
67       , {S("p1\\"),    S("\\p2"),     S("p1\\/\\p2"),     S("\\p2")}
68       , {S(""),        S("p2"),       S("p2"),            S("p2")}
69       , {S("/p1"),     S("p2"),       S("/p1/p2"),        S("/p1\\p2")}
70       , {S("/p1"),     S("/p2"),      S("/p2"),           S("/p2")}
71       , {S("/p1/p3"),  S("p2"),       S("/p1/p3/p2"),     S("/p1/p3\\p2")}
72       , {S("/p1/p3/"), S("p2"),       S("/p1/p3/p2"),     S("/p1/p3/p2")}
73       , {S("/p1/"),    S("p2"),       S("/p1/p2"),        S("/p1/p2")}
74       , {S("/p1/p3/"), S("/p2/p4"),   S("/p2/p4"),        S("/p2/p4")}
75       , {S("/"),       S(""),         S("/"),             S("/")}
76       , {S("/p1"),     S("/p2/"),     S("/p2/"),          S("/p2/")}
77       , {S("p1"),      S(""),         S("p1/"),           S("p1\\")}
78       , {S("p1/"),     S(""),         S("p1/"),           S("p1/")}
79 
80       , {S("//host"),  S("foo"),      S("//host/foo"),    S("//host\\foo")}
81       , {S("//host/"), S("foo"),      S("//host/foo"),    S("//host/foo")}
82       , {S("//host"),  S(""),         S("//host/"),       S("//host\\")}
83 
84       , {S("foo"),     S("C:/bar"),   S("foo/C:/bar"),    S("C:/bar")}
85       , {S("foo"),     S("C:"),       S("foo/C:"),        S("C:")}
86 
87       , {S("C:"),      S(""),         S("C:/"),           S("C:")}
88       , {S("C:foo"),   S("/bar"),     S("/bar"),          S("C:/bar")}
89       , {S("C:foo"),   S("bar"),      S("C:foo/bar"),     S("C:foo\\bar")}
90       , {S("C:/foo"),  S("bar"),      S("C:/foo/bar"),    S("C:/foo\\bar")}
91       , {S("C:/foo"),  S("/bar"),     S("/bar"),          S("C:/bar")}
92 
93       , {S("C:foo"),   S("C:/bar"),   S("C:foo/C:/bar"),  S("C:/bar")}
94       , {S("C:foo"),   S("C:bar"),    S("C:foo/C:bar"),   S("C:foo\\bar")}
95       , {S("C:/foo"),  S("C:/bar"),   S("C:/foo/C:/bar"), S("C:/bar")}
96       , {S("C:/foo"),  S("C:bar"),    S("C:/foo/C:bar"),  S("C:/foo\\bar")}
97 
98       , {S("C:foo"),   S("c:/bar"),   S("C:foo/c:/bar"),  S("c:/bar")}
99       , {S("C:foo"),   S("c:bar"),    S("C:foo/c:bar"),   S("c:bar")}
100       , {S("C:/foo"),  S("c:/bar"),   S("C:/foo/c:/bar"), S("c:/bar")}
101       , {S("C:/foo"),  S("c:bar"),    S("C:/foo/c:bar"),  S("c:bar")}
102 
103       , {S("C:/foo"),  S("D:bar"),    S("C:/foo/D:bar"),  S("D:bar")}
104     };
105 
106 
107 const AppendOperatorTestcase LongLHSCases[] =
108     {
109         {S("p1"),   S("p2"),    S("p1/p2"),  S("p1\\p2")}
110       , {S("p1/"),  S("p2"),    S("p1/p2"),  S("p1/p2")}
111       , {S("p1"),   S("/p2"),   S("/p2"),    S("/p2")}
112       , {S("/p1"),  S("p2"),    S("/p1/p2"), S("/p1\\p2")}
113     };
114 #undef S
115 
116 
117 // The append operator may need to allocate a temporary buffer before a code_cvt
118 // conversion. Test if this allocation occurs by:
119 //   1. Create a path, `LHS`, and reserve enough space to append `RHS`.
120 //      This prevents `LHS` from allocating during the actual appending.
121 //   2. Create a `Source` object `RHS`, which represents a "large" string.
122 //      (The string must not trigger the SSO)
123 //   3. Append `RHS` to `LHS` and check for the expected allocation behavior.
124 template <class CharT>
doAppendSourceAllocTest(AppendOperatorTestcase const & TC)125 void doAppendSourceAllocTest(AppendOperatorTestcase const& TC)
126 {
127   using namespace fs;
128   using Ptr = CharT const*;
129   using Str = std::basic_string<CharT>;
130   using StrView = std::basic_string_view<CharT>;
131   using InputIter = cpp17_input_iterator<Ptr>;
132 
133   const Ptr L = TC.lhs;
134   Str RShort = (Ptr)TC.rhs;
135   Str EShort = (Ptr)TC.expected_result();
136   assert(RShort.size() >= 2);
137   CharT c = RShort.back();
138   RShort.append(100, c);
139   EShort.append(100, c);
140   const Ptr R = RShort.data();
141   const Str& E = EShort;
142   std::size_t ReserveSize = E.size() + 3;
143   // basic_string
144   {
145     path LHS(L); PathReserve(LHS, ReserveSize);
146     Str  RHS(R);
147     {
148       TEST_NOT_WIN32(DisableAllocationGuard g);
149       LHS /= RHS;
150     }
151     assert(PathEq(LHS, E));
152   }
153   // basic_string_view
154   {
155     path LHS(L); PathReserve(LHS, ReserveSize);
156     StrView  RHS(R);
157     {
158       TEST_NOT_WIN32(DisableAllocationGuard g);
159       LHS /= RHS;
160     }
161     assert(PathEq(LHS, E));
162   }
163   // CharT*
164   {
165     path LHS(L); PathReserve(LHS, ReserveSize);
166     Ptr RHS(R);
167     {
168       TEST_NOT_WIN32(DisableAllocationGuard g);
169       LHS /= RHS;
170     }
171     assert(PathEq(LHS, E));
172   }
173   {
174     path LHS(L); PathReserve(LHS, ReserveSize);
175     Ptr RHS(R);
176     {
177       TEST_NOT_WIN32(DisableAllocationGuard g);
178       LHS.append(RHS, StrEnd(RHS));
179     }
180     assert(PathEq(LHS, E));
181   }
182   {
183     path LHS(L); PathReserve(LHS, ReserveSize);
184     path RHS(R);
185     {
186       DisableAllocationGuard g;
187       LHS /= RHS;
188     }
189     assert(PathEq(LHS, E));
190   }
191   // input iterator - For non-native char types, appends needs to copy the
192   // iterator range into a contiguous block of memory before it can perform the
193   // code_cvt conversions.
194   // For "char" no allocations will be performed because no conversion is
195   // required.
196   // On Windows, the append method is more complex and uses intermediate
197   // path objects, which causes extra allocations. This is checked by comparing
198   // path::value_type with "char" - on Windows, it's wchar_t.
199 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
200   // Only check allocations if we can pick up allocations done within the
201   // library implementation.
202   bool ExpectNoAllocations = std::is_same<CharT, char>::value &&
203                              std::is_same<path::value_type, char>::value;
204 #endif
205   {
206     path LHS(L); PathReserve(LHS, ReserveSize);
207     InputIter RHS(R);
208     {
209       RequireAllocationGuard g(0); // require "at least zero" allocations by default
210 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
211       if (ExpectNoAllocations)
212         g.requireExactly(0);
213 #endif
214       LHS /= RHS;
215     }
216     assert(PathEq(LHS, E));
217   }
218   {
219     path LHS(L); PathReserve(LHS, ReserveSize);
220     InputIter RHS(R);
221     InputIter REnd(StrEnd(R));
222     {
223       RequireAllocationGuard g(0); // require "at least zero" allocations by default
224 #if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
225       if (ExpectNoAllocations)
226         g.requireExactly(0);
227 #endif
228       LHS.append(RHS, REnd);
229     }
230     assert(PathEq(LHS, E));
231   }
232 }
233 
234 template <class CharT>
doAppendSourceTest(AppendOperatorTestcase const & TC)235 void doAppendSourceTest(AppendOperatorTestcase const& TC)
236 {
237   using namespace fs;
238   using Ptr = CharT const*;
239   using Str = std::basic_string<CharT>;
240   using StrView = std::basic_string_view<CharT>;
241   using InputIter = cpp17_input_iterator<Ptr>;
242   const Ptr L = TC.lhs;
243   const Ptr R = TC.rhs;
244   const Ptr E = TC.expected_result();
245   // basic_string
246   {
247     path Result(L);
248     Str RHS(R);
249     path& Ref = (Result /= RHS);
250     assert(Result == E);
251     assert(&Ref == &Result);
252   }
253   {
254     path LHS(L);
255     Str RHS(R);
256     path& Ref = LHS.append(RHS);
257     assert(PathEq(LHS, E));
258     assert(&Ref == &LHS);
259   }
260   // basic_string_view
261   {
262     path LHS(L);
263     StrView RHS(R);
264     path& Ref = (LHS /= RHS);
265     assert(PathEq(LHS, E));
266     assert(&Ref == &LHS);
267   }
268   {
269     path LHS(L);
270     StrView RHS(R);
271     path& Ref = LHS.append(RHS);
272     assert(PathEq(LHS, E));
273     assert(&Ref == &LHS);
274   }
275   // Char*
276   {
277     path LHS(L);
278     Str RHS(R);
279     path& Ref = (LHS /= RHS);
280     assert(PathEq(LHS, E));
281     assert(&Ref == &LHS);
282   }
283   {
284     path LHS(L);
285     Ptr RHS(R);
286     path& Ref = LHS.append(RHS);
287     assert(PathEq(LHS, E));
288     assert(&Ref == &LHS);
289   }
290   {
291     path LHS(L);
292     Ptr RHS(R);
293     path& Ref = LHS.append(RHS, StrEnd(RHS));
294     assert(PathEq(LHS, E));
295     assert(&Ref == &LHS);
296   }
297   // iterators
298   {
299     path LHS(L);
300     InputIter RHS(R);
301     path& Ref = (LHS /= RHS);
302     assert(PathEq(LHS, E));
303     assert(&Ref == &LHS);
304   }
305   {
306     path LHS(L); InputIter RHS(R);
307     path& Ref = LHS.append(RHS);
308     assert(PathEq(LHS, E));
309     assert(&Ref == &LHS);
310   }
311   {
312     path LHS(L);
313     InputIter RHS(R);
314     InputIter REnd(StrEnd(R));
315     path& Ref = LHS.append(RHS, REnd);
316     assert(PathEq(LHS, E));
317     assert(&Ref == &LHS);
318   }
319 }
320 
321 
322 
323 template <class It, class = decltype(fs::path{}.append(std::declval<It>()))>
has_append(int)324 constexpr bool has_append(int) { return true; }
325 template <class It>
has_append(long)326 constexpr bool has_append(long) { return false; }
327 
328 template <class It, class = decltype(fs::path{}.operator/=(std::declval<It>()))>
has_append_op(int)329 constexpr bool has_append_op(int) { return true; }
330 template <class It>
has_append_op(long)331 constexpr bool has_append_op(long) { return false; }
332 
333 template <class It>
has_append()334 constexpr bool has_append() {
335   static_assert(has_append<It>(0) == has_append_op<It>(0), "must be same");
336   return has_append<It>(0) && has_append_op<It>(0);
337 }
338 
test_sfinae()339 void test_sfinae()
340 {
341   using namespace fs;
342   {
343     using It = const char* const;
344     static_assert(has_append<It>(), "");
345   }
346   {
347     using It = cpp17_input_iterator<const char*>;
348     static_assert(has_append<It>(), "");
349   }
350   {
351     struct Traits {
352       using iterator_category = std::input_iterator_tag;
353       using value_type = const char;
354       using pointer = const char*;
355       using reference = const char&;
356       using difference_type = std::ptrdiff_t;
357     };
358     using It = cpp17_input_iterator<const char*, Traits>;
359     static_assert(has_append<It>(), "");
360   }
361   {
362     using It = cpp17_output_iterator<const char*>;
363     static_assert(!has_append<It>(), "");
364 
365   }
366   {
367     static_assert(!has_append<int*>(), "");
368   }
369   {
370     static_assert(!has_append<char>(), "");
371     static_assert(!has_append<const char>(), "");
372   }
373 }
374 
main(int,char **)375 int main(int, char**)
376 {
377   using namespace fs;
378   for (auto const & TC : Cases) {
379     {
380       const char* LHS_In = TC.lhs;
381       const char* RHS_In = TC.rhs;
382       path LHS(LHS_In);
383       path RHS(RHS_In);
384       path& Res = (LHS /= RHS);
385       assert(PathEq(Res, (const char*)TC.expected_result()));
386       assert(&Res == &LHS);
387     }
388     doAppendSourceTest<char>    (TC);
389 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
390     doAppendSourceTest<wchar_t> (TC);
391 #endif
392     doAppendSourceTest<char16_t>(TC);
393     doAppendSourceTest<char32_t>(TC);
394   }
395   for (auto const & TC : LongLHSCases) {
396     (void)TC;
397     LIBCPP_ONLY(doAppendSourceAllocTest<char>(TC));
398 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
399     LIBCPP_ONLY(doAppendSourceAllocTest<wchar_t>(TC));
400 #endif
401   }
402   test_sfinae();
403 
404   return 0;
405 }
406