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: libcpp-has-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 
48   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>
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.
198   // In DLL builds on Windows, the overridden operator new won't pick up
199   // allocations done within the DLL, so the RequireAllocationGuard below
200   // won't necessarily see allocations in the cases where they're expected.
201 #ifdef _WIN32
202   bool DisableAllocations = false;
203 #else
204   bool DisableAllocations = std::is_same<CharT, char>::value;
205 #endif
206   {
207     path LHS(L); PathReserve(LHS, ReserveSize);
208     InputIter RHS(R);
209     {
210       RequireAllocationGuard  g; // requires 1 or more allocations occur by default
211       if (DisableAllocations) g.requireExactly(0);
212       else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
213       LHS /= RHS;
214     }
215     assert(PathEq(LHS, E));
216   }
217   {
218     path LHS(L); PathReserve(LHS, ReserveSize);
219     InputIter RHS(R);
220     InputIter REnd(StrEnd(R));
221     {
222       RequireAllocationGuard g;
223       if (DisableAllocations) g.requireExactly(0);
224       else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
225       LHS.append(RHS, REnd);
226     }
227     assert(PathEq(LHS, E));
228   }
229 }
230 
231 template <class CharT>
232 void doAppendSourceTest(AppendOperatorTestcase const& TC)
233 {
234   using namespace fs;
235   using Ptr = CharT const*;
236   using Str = std::basic_string<CharT>;
237   using StrView = std::basic_string_view<CharT>;
238   using InputIter = cpp17_input_iterator<Ptr>;
239   const Ptr L = TC.lhs;
240   const Ptr R = TC.rhs;
241   const Ptr E = TC.expected_result();
242   // basic_string
243   {
244     path Result(L);
245     Str RHS(R);
246     path& Ref = (Result /= RHS);
247     assert(Result == E);
248     assert(&Ref == &Result);
249   }
250   {
251     path LHS(L);
252     Str RHS(R);
253     path& Ref = LHS.append(RHS);
254     assert(PathEq(LHS, E));
255     assert(&Ref == &LHS);
256   }
257   // basic_string_view
258   {
259     path LHS(L);
260     StrView RHS(R);
261     path& Ref = (LHS /= RHS);
262     assert(PathEq(LHS, E));
263     assert(&Ref == &LHS);
264   }
265   {
266     path LHS(L);
267     StrView RHS(R);
268     path& Ref = LHS.append(RHS);
269     assert(PathEq(LHS, E));
270     assert(&Ref == &LHS);
271   }
272   // Char*
273   {
274     path LHS(L);
275     Str RHS(R);
276     path& Ref = (LHS /= RHS);
277     assert(PathEq(LHS, E));
278     assert(&Ref == &LHS);
279   }
280   {
281     path LHS(L);
282     Ptr RHS(R);
283     path& Ref = LHS.append(RHS);
284     assert(PathEq(LHS, E));
285     assert(&Ref == &LHS);
286   }
287   {
288     path LHS(L);
289     Ptr RHS(R);
290     path& Ref = LHS.append(RHS, StrEnd(RHS));
291     assert(PathEq(LHS, E));
292     assert(&Ref == &LHS);
293   }
294   // iterators
295   {
296     path LHS(L);
297     InputIter RHS(R);
298     path& Ref = (LHS /= RHS);
299     assert(PathEq(LHS, E));
300     assert(&Ref == &LHS);
301   }
302   {
303     path LHS(L); InputIter RHS(R);
304     path& Ref = LHS.append(RHS);
305     assert(PathEq(LHS, E));
306     assert(&Ref == &LHS);
307   }
308   {
309     path LHS(L);
310     InputIter RHS(R);
311     InputIter REnd(StrEnd(R));
312     path& Ref = LHS.append(RHS, REnd);
313     assert(PathEq(LHS, E));
314     assert(&Ref == &LHS);
315   }
316 }
317 
318 
319 
320 template <class It, class = decltype(fs::path{}.append(std::declval<It>()))>
321 constexpr bool has_append(int) { return true; }
322 template <class It>
323 constexpr bool has_append(long) { return false; }
324 
325 template <class It, class = decltype(fs::path{}.operator/=(std::declval<It>()))>
326 constexpr bool has_append_op(int) { return true; }
327 template <class It>
328 constexpr bool has_append_op(long) { return false; }
329 
330 template <class It>
331 constexpr bool has_append() {
332   static_assert(has_append<It>(0) == has_append_op<It>(0), "must be same");
333   return has_append<It>(0) && has_append_op<It>(0);
334 }
335 
336 void test_sfinae()
337 {
338   using namespace fs;
339   {
340     using It = const char* const;
341     static_assert(has_append<It>(), "");
342   }
343   {
344     using It = cpp17_input_iterator<const char*>;
345     static_assert(has_append<It>(), "");
346   }
347   {
348     struct Traits {
349       using iterator_category = std::input_iterator_tag;
350       using value_type = const char;
351       using pointer = const char*;
352       using reference = const char&;
353       using difference_type = std::ptrdiff_t;
354     };
355     using It = cpp17_input_iterator<const char*, Traits>;
356     static_assert(has_append<It>(), "");
357   }
358   {
359     using It = output_iterator<const char*>;
360     static_assert(!has_append<It>(), "");
361 
362   }
363   {
364     static_assert(!has_append<int*>(), "");
365   }
366   {
367     static_assert(!has_append<char>(), "");
368     static_assert(!has_append<const char>(), "");
369   }
370 }
371 
372 int main(int, char**)
373 {
374   using namespace fs;
375   for (auto const & TC : Cases) {
376     {
377       const char* LHS_In = TC.lhs;
378       const char* RHS_In = TC.rhs;
379       path LHS(LHS_In);
380       path RHS(RHS_In);
381       path& Res = (LHS /= RHS);
382       assert(PathEq(Res, (const char*)TC.expected_result()));
383       assert(&Res == &LHS);
384     }
385     doAppendSourceTest<char>    (TC);
386     doAppendSourceTest<wchar_t> (TC);
387     doAppendSourceTest<char16_t>(TC);
388     doAppendSourceTest<char32_t>(TC);
389   }
390   for (auto const & TC : LongLHSCases) {
391     (void)TC;
392     LIBCPP_ONLY(doAppendSourceAllocTest<char>(TC));
393     LIBCPP_ONLY(doAppendSourceAllocTest<wchar_t>(TC));
394   }
395   test_sfinae();
396 
397   return 0;
398 }
399