Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Side by Side Diff: lib/src/context.dart

Issue 1468343002: Improve the performance of isWithin(). (Closed) Base URL: git@github.com:dart-lang/path@master
Patch Set: remove -dev Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « benchmark/benchmark.dart ('k') | pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library path.context; 5 library path.context;
6 6
7 import 'characters.dart' as chars; 7 import 'characters.dart' as chars;
8 import 'internal_style.dart'; 8 import 'internal_style.dart';
9 import 'style.dart'; 9 import 'style.dart';
10 import 'parsed_path.dart'; 10 import 'parsed_path.dart';
(...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after
404 /// a context with a relative path for [current]. 404 /// a context with a relative path for [current].
405 /// 405 ///
406 /// var context = new Context(r'some/relative/path'); 406 /// var context = new Context(r'some/relative/path');
407 /// context.relative(r'/absolute/path'); // -> '/absolute/path' 407 /// context.relative(r'/absolute/path'); // -> '/absolute/path'
408 /// 408 ///
409 /// If [root] is relative, it may be impossible to determine a path from 409 /// If [root] is relative, it may be impossible to determine a path from
410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is 410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is
411 /// "/", no path can be determined. In this case, a [PathException] will be 411 /// "/", no path can be determined. In this case, a [PathException] will be
412 /// thrown. 412 /// thrown.
413 String relative(String path, {String from}) { 413 String relative(String path, {String from}) {
414 // Avoid calling [current] since it is slow and calling join() when 414 from = from == null ? current : absolute(from);
415 // [from] is absolute does nothing.
416 if (from == null) {
417 from = current;
418 } else if (this.isRelative(from) || this.isRootRelative(from)) {
419 from = this.join(current, from);
420 }
421 415
422 // We can't determine the path from a relative path to an absolute path. 416 // We can't determine the path from a relative path to an absolute path.
423 if (this.isRelative(from) && this.isAbsolute(path)) { 417 if (this.isRelative(from) && this.isAbsolute(path)) {
424 return this.normalize(path); 418 return this.normalize(path);
425 } 419 }
426 420
427 // If the given path is relative, resolve it relative to the context's 421 // If the given path is relative, resolve it relative to the context's
428 // current directory. 422 // current directory.
429 if (this.isRelative(path) || this.isRootRelative(path)) { 423 if (this.isRelative(path) || this.isRootRelative(path)) {
430 path = this.absolute(path); 424 path = this.absolute(path);
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
496 return pathParsed.toString(); 490 return pathParsed.toString();
497 } 491 }
498 492
499 /// Returns `true` if [child] is a path beneath `parent`, and `false` 493 /// Returns `true` if [child] is a path beneath `parent`, and `false`
500 /// otherwise. 494 /// otherwise.
501 /// 495 ///
502 /// path.isWithin('/root/path', '/root/path/a'); // -> true 496 /// path.isWithin('/root/path', '/root/path/a'); // -> true
503 /// path.isWithin('/root/path', '/root/other'); // -> false 497 /// path.isWithin('/root/path', '/root/other'); // -> false
504 /// path.isWithin('/root/path', '/root/path'); // -> false 498 /// path.isWithin('/root/path', '/root/path'); // -> false
505 bool isWithin(String parent, String child) { 499 bool isWithin(String parent, String child) {
500 // Make both paths the same level of relative. We're only able to do the
501 // quick comparison if both paths are in the same format, and making a path
502 // absolute is faster than making it relative.
503 var parentIsAbsolute = isAbsolute(parent);
504 var childIsAbsolute = isAbsolute(child);
505 if (parentIsAbsolute && !childIsAbsolute) {
506 child = absolute(child);
507 if (style.isRootRelative(parent)) parent = absolute(parent);
508 } else if (childIsAbsolute && !parentIsAbsolute) {
509 parent = absolute(parent);
510 if (style.isRootRelative(child)) child = absolute(child);
511 } else if (childIsAbsolute && parentIsAbsolute) {
512 var childIsRootRelative = style.isRootRelative(child);
513 var parentIsRootRelative = style.isRootRelative(parent);
514
515 if (childIsRootRelative && !parentIsRootRelative) {
516 child = absolute(child);
517 } else if (parentIsRootRelative && !childIsRootRelative) {
518 parent = absolute(parent);
519 }
520 }
521
522 var fastResult = _isWithinFast(parent, child);
523 if (fastResult != null) return fastResult;
524
506 var relative; 525 var relative;
507 try { 526 try {
508 relative = this.relative(child, from: parent); 527 relative = this.relative(child, from: parent);
509 } on PathException catch (_) { 528 } on PathException catch (_) {
510 // If no relative path from [parent] to [child] is found, [child] 529 // If no relative path from [parent] to [child] is found, [child]
511 // definitely isn't a child of [parent]. 530 // definitely isn't a child of [parent].
512 return false; 531 return false;
513 } 532 }
514 533
515 var parts = this.split(relative); 534 var parts = this.split(relative);
516 return this.isRelative(relative) && 535 return this.isRelative(relative) &&
517 parts.first != '..' && 536 parts.first != '..' &&
518 parts.first != '.'; 537 parts.first != '.';
519 } 538 }
520 539
540 /// An optimized implementation of [isWithin] that doesn't handle a few
541 /// complex cases.
542 bool _isWithinFast(String parent, String child) {
Bob Nystrom 2015/11/24 17:22:13 Possibly dumb question. How much mileage would we
nweiz 2015/12/01 00:15:15 That gives a false positive for isWithin("/foo/bar
543 // Normally we just bail when we see "." path components, but we can handle
544 // a single dot easily enough.
545 if (parent == '.') parent = '';
546
547 var parentRootLength = style.rootLength(parent);
548 var childRootLength = style.rootLength(child);
549
550 // If the roots aren't the same length, we know both paths are absolute or
551 // both are root-relative, and thus that the roots are meaningfully
552 // different.
553 //
554 // isWithin("C:/bar", "//foo/bar/baz") //=> false
555 // isWithin("http://example.com/", "http://google.com/bar") //=> false
556 if (parentRootLength != childRootLength) return false;
557
558 var parentCodeUnits = parent.codeUnits;
559 var childCodeUnits = child.codeUnits;
560
561 // Make sure that the roots are textually the same as well.
562 //
563 // isWithin("C:/bar", "D:/bar/baz") //=> false
564 // isWithin("http://example.com/", "http://example.org/bar") //=> false
565 for (var i = 0; i < parentRootLength; i++) {
566 var parentCodeUnit = parentCodeUnits[i];
567 var childCodeUnit = childCodeUnits[i];
568 if (parentCodeUnit == childCodeUnit) continue;
569
570 // If both code units are separators, that's fine too.
571 //
572 // isWithin("C:/", r"C:\foo") //=> true
573 if (!style.isSeparator(parentCodeUnit) ||
574 !style.isSeparator(childCodeUnit)) {
575 return false;
576 }
577 }
578
579 // Start by considering the last code unit as a separator, since
580 // semantically we're starting at a new path component even if we're
581 // comparing relative paths.
582 var lastCodeUnit = chars.SLASH;
583
584 // Iterate through both paths as long as they're semantically identical.
585 var parentIndex = parentRootLength;
586 var childIndex = childRootLength;
587 while (parentIndex < parent.length && childIndex < child.length) {
588 var parentCodeUnit = parentCodeUnits[parentIndex];
589 var childCodeUnit = childCodeUnits[childIndex];
590 if (parentCodeUnit == childCodeUnit) {
591 lastCodeUnit = parentCodeUnit;
592 parentIndex++;
593 childIndex++;
594 continue;
595 }
596
597 // Different separators are considered identical.
598 var parentIsSeparator = style.isSeparator(parentCodeUnit);
599 var childIsSeparator = style.isSeparator(childCodeUnit);
600 if (parentIsSeparator && childIsSeparator) {
601 lastCodeUnit = parentCodeUnit;
602 parentIndex++;
603 childIndex++;
604 continue;
605 }
606
607 // Ignore multiple separators in a row.
608 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) {
609 parentIndex++;
610 continue;
611 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) {
612 childIndex++;
613 continue;
614 }
615
616 // If a dot comes after a separator or another dot, it may be a
617 // directory traversal operator. Otherwise, it's just a normal
618 // non-matching character.
619 //
620 // isWithin("foo/./bar", "foo/bar/baz") //=> true
621 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
622 //
623 // We could stay on the fast path for "/./", but that adds a lot of
624 // complexity and isn't likely to come up much in practice.
625 if ((parentCodeUnit == chars.PERIOD || childCodeUnit == chars.PERIOD) &&
626 (style.isSeparator(lastCodeUnit) || lastCodeUnit == chars.PERIOD)) {
627 return null;
628 }
629
630 // If we're here, we've hit two non-matching, non-significant characters.
631 // As long as the remainders of the two paths don't have any unresolved
632 // ".." components, we can be confident that [child] is not within
633 // [parent].
634 if (_checkRemainder(childCodeUnits, childIndex) < 1) return null;
635 if (_checkRemainder(parentCodeUnits, parentIndex) < 1) return null;
636 return false;
637 }
638
639 // If the child is shorter than the parent, it's probably not within the
640 // parent. The only exception is if the parent has some weird ".." stuff
641 // going on, in which case we do the slow check.
642 //
643 // isWithin("foo/bar/baz", "foo/bar") //=> false
644 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true
645 if (childIndex == child.length) {
646 var result = _checkRemainder(parentCodeUnits, parentIndex);
647 return result < 0 ? null : false;
648 }
649
650 // We've reached the end of the parent path, which means it's time to make a
651 // decision. Before we do, though, we'll check the rest of the child to see
652 // what that tells us.
653 var result = _checkRemainder(childCodeUnits, childIndex);
654
655 // If there are no more components in the child, then it's the same as
656 // the parent, not within it.
657 //
658 // isWithin("foo/bar", "foo/bar") //=> false
659 // isWithin("foo/bar", "foo/bar//") //=> false
660 if (result == 0) return false;
661
662 // If there are unresolved ".." components in the child, no decision we make
663 // will be valid. We'll abort and do the slow check instead.
664 //
665 // isWithin("foo/bar", "foo/bar/..") //=> false
666 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
667 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
668 if (result < 0) return null;
669
670 // The child is within the parent if and only if we're on a separator
671 // boundary.
672 //
673 // isWithin("foo/bar", "foo/bar/baz") //=> true
674 // isWithin("foo/bar/", "foo/bar/baz") //=> true
675 // isWithin("foo/bar", "foo/barbaz") //=> false
676 return style.isSeparator(childCodeUnits[childIndex]) ||
677 style.isSeparator(lastCodeUnit);
678 }
679
680 // Returns the information about the path represented by [codeUnits] after
681 // [index].
682 //
683 // Specifically, this returns:
684 //
685 // * A negative number if the path contains ".." components that at any point
686 // cause the directory to go above the root.
687 //
688 // * Zero if the path has no components, or if it contains ".." that at any
689 // point cause the directory to go back to the root (but not above it).
690 //
691 // * A positive number otherwise.
692 //
693 // This ignores leading separators.
694 //
695 // checkRemainder("foo") //=> +
696 // checkRemainder("foo/bar/../baz") //=> +
697 // checkRemainder("//foo/bar/baz") //=> +
698 // checkRemainder("/") //=> 0
699 // checkRemainder("foo/../baz") //=> 0
700 // checkRemainder("foo/../..") //=> -
701 // checkRemainder("foo/../../foo/bar/baz") //=> -
702 int _checkRemainder(List<int> codeUnits, int index) {
Bob Nystrom 2015/11/24 17:22:13 How about _pathDirection()? Something other than "
nweiz 2015/12/01 00:15:15 Done.
703 var depth = 0;
704
705 // We initially consider ourselves to be after an invisible root separator.
706 var afterSeparator = true;
707 for (var i = index; i < codeUnits.length; i++) {
708 var codeUnit = codeUnits[i];
709
710 if (style.isSeparator(codeUnit)) {
711 // Ignore doubled separators (and initial separators).
712 if (!afterSeparator) depth++;
713 afterSeparator = true;
714 continue;
715 }
716
717 // Ignore non-meaningful characters.
718 if (codeUnit != chars.PERIOD || !afterSeparator) {
719 afterSeparator = false;
720 continue;
721 }
722
723 // Move forward so we're positioned after "/.".
724 i++;
725
726 if (i == codeUnits.length) return depth;
727 codeUnit = codeUnits[i];
728
729 // Ignore "/./", and don't increment the depth for the trailing slash.
730 if (style.isSeparator(codeUnit)) continue;
731
732 // "/.foo" isn't anything meaningful.
733 if (codeUnit != chars.PERIOD) {
734 afterSeparator = false;
735 continue;
736 }
737
738 // Move forward again so we're positioned after "/..".
739 i++;
740
741 if (i == codeUnits.length) return depth - 1;
742 codeUnit = codeUnits[i];
743
744 // "/../" decreases depth.
745 if (style.isSeparator(codeUnit)) {
746 depth--;
747 if (depth == 0) return 0;
748 if (depth < 0) return depth;
749 continue;
750 }
751
752 // "/..foo" isn't anything meaningful.
753 afterSeparator = false;
754 }
Bob Nystrom 2015/11/24 17:22:13 Instead of a for loop with a sort of state machine
nweiz 2015/12/01 00:15:15 Done. I also realized that there were some bugs in
755
756 // If the path didn't have a trailing separator, add another unit of depth
757 // to account for the current component. We have to do this because depth is counted
Bob Nystrom 2015/11/24 17:22:13 Long line.
nweiz 2015/12/01 00:15:15 Acknowledged.
758 // on each trailing separator.
759 return afterSeparator ? depth : depth + 1;
760 }
761
521 /// Removes a trailing extension from the last part of [path]. 762 /// Removes a trailing extension from the last part of [path].
522 /// 763 ///
523 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' 764 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
524 String withoutExtension(String path) { 765 String withoutExtension(String path) {
525 var parsed = _parse(path); 766 var parsed = _parse(path);
526 767
527 for (var i = parsed.parts.length - 1; i >= 0; i--) { 768 for (var i = parsed.parts.length - 1; i >= 0; i--) {
528 if (!parsed.parts[i].isEmpty) { 769 if (!parsed.parts[i].isEmpty) {
529 parsed.parts[i] = parsed.basenameWithoutExtension; 770 parsed.parts[i] = parsed.basenameWithoutExtension;
530 break; 771 break;
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
643 var message = new StringBuffer(); 884 var message = new StringBuffer();
644 message.write("$method("); 885 message.write("$method(");
645 message.write(args 886 message.write(args
646 .take(numArgs) 887 .take(numArgs)
647 .map((arg) => arg == null ? "null" : '"$arg"') 888 .map((arg) => arg == null ? "null" : '"$arg"')
648 .join(", ")); 889 .join(", "));
649 message.write("): part ${i - 1} was null, but part $i was not."); 890 message.write("): part ${i - 1} was null, but part $i was not.");
650 throw new ArgumentError(message.toString()); 891 throw new ArgumentError(message.toString());
651 } 892 }
652 } 893 }
OLDNEW
« no previous file with comments | « benchmark/benchmark.dart ('k') | pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698