@@ -171,10 +171,9 @@ bool canonical_path::parent() {
171171
172172canonical_path_result::canonical_path_result () {}
173173
174- canonical_path_result::canonical_path_result (std::string &&path)
175- : path_(std::move(path)) {}
176-
177- canonical_path_result::canonical_path_result (const char *path) : path_(path) {}
174+ canonical_path_result::canonical_path_result (std::string &&path,
175+ std::size_t existing_path_length)
176+ : path_(std::move(path)), existing_path_length_(existing_path_length) {}
178177
179178std::string_view canonical_path_result::path () const &noexcept {
180179 QLJS_ASSERT (this ->ok ());
@@ -206,6 +205,16 @@ std::string &&canonical_path_result::error() && noexcept {
206205 return std::move (this ->error_ );
207206}
208207
208+ bool canonical_path_result::have_missing_components () const noexcept {
209+ QLJS_ASSERT (this ->ok ());
210+ return this ->existing_path_length_ != this ->path_ ->path_ .size ();
211+ }
212+
213+ void canonical_path_result::drop_missing_components () {
214+ QLJS_ASSERT (this ->ok ());
215+ this ->path_ ->path_ .resize (this ->existing_path_length_ );
216+ }
217+
209218canonical_path_result canonical_path_result::failure (std::string &&error) {
210219 canonical_path_result result;
211220 result.error_ = std::move (error);
@@ -224,9 +233,13 @@ class path_canonicalizer {
224233 }
225234#if QLJS_PATHS_WIN32
226235 // HACK(strager): Convert UTF-16 to UTF-8.
227- return canonical_path_result (std::filesystem::path (canonical_).string ());
236+ // TODO(strager): existing_path_length_ is in UTF-16 code units, but it's
237+ // interpreted as UTF-8 code units! Fix by storing a std::wstring in
238+ // canonical_path.
239+ return canonical_path_result (std::filesystem::path (canonical_).string (),
240+ existing_path_length_);
228241#else
229- return canonical_path_result (std::move (canonical_));
242+ return canonical_path_result (std::move (canonical_), existing_path_length_ );
230243#endif
231244 }
232245
@@ -251,13 +264,18 @@ class path_canonicalizer {
251264#endif
252265 canonical_ += preferred_component_separator;
253266 }
267+
268+ if (existing_path_length_ == 0 ) {
269+ existing_path_length_ = canonical_.size ();
270+ }
254271 }
255272
256273 private:
257274 enum class file_type {
258275 error,
259276
260277 directory,
278+ does_not_exist,
261279 other,
262280 symlink,
263281 };
@@ -358,19 +376,41 @@ class path_canonicalizer {
358376 if (component == dot) {
359377 skip_to_next_component ();
360378 } else if (component == dot_dot) {
361- parent ();
379+ if (existing_path_length_ == 0 ) {
380+ parent ();
381+ } else {
382+ QLJS_ASSERT (!canonical_.empty ());
383+ canonical_ += preferred_component_separator;
384+ canonical_ += component;
385+ }
362386 skip_to_next_component ();
363387 } else {
388+ std::size_t canonical_length_without_component = canonical_.size ();
389+
364390 canonical_ += preferred_component_separator;
365391 canonical_ += component;
366392 need_root_slash_ = false ;
367393
394+ if (existing_path_length_ != 0 ) {
395+ // A parent path did not exist, so this path certainly does not exist.
396+ // Don't bother checking.
397+ skip_to_next_component ();
398+ return ;
399+ }
400+
368401 file_type type = get_file_type (canonical_);
369402 switch (type) {
370403 case file_type::error:
371404 QLJS_ASSERT (!error_.empty ());
372405 return ;
373406
407+ case file_type::does_not_exist:
408+ if (existing_path_length_ == 0 ) {
409+ existing_path_length_ = canonical_length_without_component;
410+ }
411+ skip_to_next_component ();
412+ break ;
413+
374414 case file_type::directory:
375415 skip_to_next_component ();
376416 break ;
@@ -448,10 +488,14 @@ class path_canonicalizer {
448488#if defined(_WIN32)
449489 DWORD attributes = ::GetFileAttributesW (file_path.c_str ());
450490 if (attributes == INVALID_FILE_ATTRIBUTES) {
491+ DWORD error = ::GetLastError ();
492+ if (error == ERROR_FILE_NOT_FOUND) {
493+ return file_type::does_not_exist;
494+ }
451495 error_ = std::string (" failed to canonicalize path " ) +
452496 string_for_error_message (original_path_) + " : " +
453497 string_for_error_message (canonical_) + " : " +
454- windows_error_message (:: GetLastError () );
498+ windows_error_message (error );
455499 return file_type::error;
456500 }
457501 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
@@ -465,6 +509,9 @@ class path_canonicalizer {
465509 struct stat s;
466510 int lstat_rc = ::lstat (file_path.c_str (), &s);
467511 if (lstat_rc == -1 ) {
512+ if (errno == ENOENT) {
513+ return file_type::does_not_exist;
514+ }
468515 error_ = std::string (" failed to canonicalize path " ) +
469516 string_for_error_message (original_path_) + " : " +
470517 string_for_error_message (canonical_) + " : " +
@@ -540,6 +587,13 @@ class path_canonicalizer {
540587 path_string canonical_;
541588 bool need_root_slash_;
542589
590+ // During canonicalization, if existing_path_length_ is 0, then we have not
591+ // found a non-existing path.
592+ //
593+ // During canonicalization, if existing_path_length_ is not 0, then we have
594+ // found a non-existing path. '..' should be preserved.
595+ std::size_t existing_path_length_ = 0 ;
596+
543597 path_string readlink_buffers_[2 ];
544598 int used_readlink_buffer_ = 0 ; // Index into readlink_buffers_.
545599
0 commit comments