Skip to content

Commit

Permalink
interpreter: do not use pathlib in validate_within_subproject
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
bonzini committed Jan 10, 2025
1 parent 2234b75 commit 0b2ef7f
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 13 deletions.
28 changes: 15 additions & 13 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .. import mesonlib
from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugException, MesonException, HoldableObject,
FileMode, MachineChoice, listify,
extract_as_list, has_path_sep, path_is_in_root, PerMachine)
path_starts_with, extract_as_list, has_path_sep, path_is_in_root, PerMachine)
from ..options import OptionKey
from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency
Expand Down Expand Up @@ -3135,29 +3135,31 @@ def do_validate_within_subproject(norm):
inputtype = 'directory'
else:
inputtype = 'file'
if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES in self.relaxations and builddir in norm.parents:
if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES in self.relaxations and path_starts_with(norm, self.environment.build_dir):
return
if srcdir not in norm.parents:

if not path_starts_with(norm, self.environment.source_dir):
# Grabbing files outside the source tree is ok.
# This is for vendor stuff like:
#
# /opt/vendorsdk/src/file_with_license_restrictions.c
return
project_root = Path(srcdir, self.root_subdir)
subproject_dir = project_root / self.subproject_dir
if norm == project_root:
return
if project_root not in norm.parents:
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} outside current (sub)project.')
if subproject_dir == norm or subproject_dir in norm.parents:
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.')

project_root = os.path.join(self.environment.source_dir, self.root_subdir, '')
if not path_starts_with(norm, project_root):
name = os.path.basename(norm)
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {name} outside current (sub)project.')

subproject_dir = os.path.join(project_root, self.subproject_dir)
if path_starts_with(norm, subproject_dir):
name = os.path.basename(norm)
raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {name} from a nested subproject.')

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

# Use os.path.abspath() to eliminate .. segments, but do not resolve symlinks
norm = Path(os.path.abspath(Path(srcdir, fname)))
norm = os.path.abspath(os.path.join(srcdir, fname))
do_validate_within_subproject(norm)
self.validated_cache.add(fname)

Expand Down
22 changes: 22 additions & 0 deletions mesonbuild/utils/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class _VerPickleLoadable(Protocol):
'listify_array_value',
'partition',
'path_is_in_root',
'path_starts_with',
'pickle_load',
'Popen_safe',
'Popen_safe_logged',
Expand Down Expand Up @@ -1126,6 +1127,27 @@ def determine_worker_count(varnames: T.Optional[T.List[str]] = None) -> int:
num_workers = 1
return num_workers

def path_starts_with(name: str, path: str) -> bool:
'''Checks if @name is a file under the directory @path. Both @name and @path should be
adequately normalized, and either both or none should be absolute.'''
assert os.path.isabs(name) == os.path.isabs(path)
if is_windows():
name = name.replace('\\', '/')
path = path.replace('\\', '/')

split_name = name.split('/')
split_path = path.split('/')
while not split_path[-1]:
del split_path[-1]

if len(split_name) < len(split_path):
return False
for i, component in enumerate(split_path):
if component != split_name[i]:
return False
return True


def has_path_sep(name: str, sep: str = '/\\') -> bool:
'Checks if any of the specified @sep path separators are in @name'
for each in sep:
Expand Down

0 comments on commit 0b2ef7f

Please sign in to comment.