Skip to content

Commit bf03ed2

Browse files
committed
interpreter: do not use pathlib in validate_within_subproject
pathlib's implementation of Path iteration is expensive; "foo.parents" has quadratic complexity when building it: return self._path._from_parsed_parts(self._drv, self._root, self._tail[:-idx - 1]) and comparisons performed by "path in file.parents" potentially have the same issue. Introduce a new function that checks whether a file is under a path in the same way, removing usage of Path from the biggest hotspot. Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 0a8ea78 commit bf03ed2

File tree

2 files changed

+39
-15
lines changed

2 files changed

+39
-15
lines changed

mesonbuild/interpreter/interpreter.py

+17-15
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .. import mesonlib
2121
from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugException, MesonException, HoldableObject,
2222
FileMode, MachineChoice, listify,
23-
extract_as_list, has_path_sep, path_is_in_root, PerMachine)
23+
path_starts_with, extract_as_list, has_path_sep, path_is_in_root, PerMachine)
2424
from ..options import OptionKey
2525
from ..programs import ExternalProgram, NonExistingExternalProgram
2626
from ..dependencies import Dependency
@@ -3108,8 +3108,8 @@ def check_clang_asan_lundef(self) -> None:
31083108
# subproject than it is defined in (due to e.g. a
31093109
# declare_dependency).
31103110
def validate_within_subproject(self, subdir, fname):
3111-
srcdir = Path(self.environment.source_dir)
3112-
builddir = Path(self.environment.build_dir)
3111+
srcdir = self.environment.source_dir
3112+
builddir = self.environment.build_dir
31133113
if isinstance(fname, P_OBJ.DependencyVariableString):
31143114
def validate_installable_file(fpath: Path) -> bool:
31153115
installablefiles: T.Set[Path] = set()
@@ -3135,29 +3135,31 @@ def do_validate_within_subproject(norm):
31353135
inputtype = 'directory'
31363136
else:
31373137
inputtype = 'file'
3138-
if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES in self.relaxations and builddir in norm.parents:
3138+
if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES in self.relaxations and path_starts_with(norm, self.environment.build_dir):
31393139
return
3140-
if srcdir not in norm.parents:
3140+
3141+
if not path_starts_with(norm, self.environment.source_dir):
31413142
# Grabbing files outside the source tree is ok.
31423143
# This is for vendor stuff like:
31433144
#
31443145
# /opt/vendorsdk/src/file_with_license_restrictions.c
31453146
return
3146-
project_root = Path(srcdir, self.root_subdir)
3147-
subproject_dir = project_root / self.subproject_dir
3148-
if norm == project_root:
3149-
return
3150-
if project_root not in norm.parents:
3151-
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} outside current (sub)project.')
3152-
if subproject_dir == norm or subproject_dir in norm.parents:
3153-
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.')
3147+
3148+
project_root = os.path.join(self.environment.source_dir, self.root_subdir, '')
3149+
if not path_starts_with(norm, project_root):
3150+
name = os.path.basename(norm)
3151+
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {name} outside current (sub)project.')
3152+
3153+
subproject_dir = os.path.join(project_root, self.subproject_dir)
3154+
if path_starts_with(norm, subproject_dir):
3155+
name = os.path.basename(norm)
3156+
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {name} from a nested subproject.')
31543157

31553158
fname = os.path.join(subdir, fname)
31563159
if fname in self.validated_cache:
31573160
return
31583161

3159-
# Use os.path.abspath() to eliminate .. segments, but do not resolve symlinks
3160-
norm = Path(os.path.abspath(Path(srcdir, fname)))
3162+
norm = os.path.abspath(os.path.join(srcdir, fname))
31613163
do_validate_within_subproject(norm)
31623164
self.validated_cache.add(fname)
31633165

mesonbuild/utils/universal.py

+22
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ class _VerPickleLoadable(Protocol):
135135
'listify_array_value',
136136
'partition',
137137
'path_is_in_root',
138+
'path_starts_with',
138139
'pickle_load',
139140
'Popen_safe',
140141
'Popen_safe_logged',
@@ -1126,6 +1127,27 @@ def determine_worker_count(varnames: T.Optional[T.List[str]] = None) -> int:
11261127
num_workers = 1
11271128
return num_workers
11281129

1130+
def path_starts_with(name: str, path: str) -> bool:
1131+
'''Checks if @name is a file under the directory @path. Both @name and @path should be
1132+
adequately normalized, and either both or none should be absolute.'''
1133+
assert os.path.isabs(name) == os.path.isabs(path)
1134+
if is_windows():
1135+
name = name.replace('\\', '/')
1136+
path = path.replace('\\', '/')
1137+
1138+
split_name = name.split('/')
1139+
split_path = path.split('/')
1140+
while not split_path[-1]:
1141+
del split_path[-1]
1142+
1143+
if len(split_name) < len(split_path):
1144+
return False
1145+
for i, component in enumerate(split_path):
1146+
if component != split_name[i]:
1147+
return False
1148+
return True
1149+
1150+
11291151
def has_path_sep(name: str, sep: str = '/\\') -> bool:
11301152
'Checks if any of the specified @sep path separators are in @name'
11311153
for each in sep:

0 commit comments

Comments
 (0)