FileReader: Normalize base path and strip it from normalized source paths

This commit is contained in:
Kamil Śliwak 2021-06-15 14:52:53 +02:00
parent 2d3ec69a05
commit 13f46ebb1e
8 changed files with 797 additions and 62 deletions

View File

@ -4,6 +4,7 @@ Language Features:
Compiler Features:
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path.
* Immutable variables can be read at construction time once they are initialized.
* SMTChecker: Support low level ``call`` as external calls to unknown code.
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.

View File

@ -71,8 +71,10 @@ The initial content of the VFS depends on how you invoke the compiler:
solc contract.sol /usr/local/dapp-bin/token.sol
The source unit name of a file loaded this way is simply the specified path after shell expansion
and with platform-specific separators converted to forward slashes.
The source unit name of a file loaded this way is constructed by converting its path to a
canonical form and making it relative to the base path if it is located inside.
See :ref:`Base Path Normalization and Stripping <base-path-normalization-and-stripping>` for
a detailed description of this process.
.. index:: standard JSON
@ -309,9 +311,67 @@ When the source unit name is a relative path, this results in the file being loo
directory the compiler has been invoked from.
It is also the only value that results in absolute paths in source unit names being actually
interpreted as absolute paths on disk.
If the base path itself is relative, it is interpreted as relative to the current working directory
of the compiler.
If the base path itself is relative, it is also interpreted as relative to the current working
directory of the compiler.
.. _base-path-normalization-and-stripping:
Base Path Normalization and Stripping
-------------------------------------
On the command line the compiler behaves just as you would expect from any other program:
it accepts paths in a format native to the platform and relative paths are relative to the current
working directory.
The source unit names assigned to files whose paths are specified on the command line, however,
should not change just because the project is being compiled on a different platform or because the
compiler happens to have been invoked from a different directory.
To achieve this, paths to source files coming from the command line must be converted to a canonical
form, and, if possible, made relative to the base path.
The normalization rules are as follows:
- If a path is relative, it is made absolute by prepending the current working directory to it.
- Internal ``.`` and ``..`` segments are collapsed.
- Platform-specific path separators are replaced with forward slashes.
- Sequences of multiple consecutive path separators are squashed into a single separator (unless
they are the leading slashes of an `UNC path <https://en.wikipedia.org/wiki/Path_(computing)#UNC>`_).
- If the path includes a root name (e.g. a drive letter on Windows) and the root is the same as the
root of the current working directory, the root is replaced with ``/``.
- Symbolic links in the path are **not** resolved.
- The only exception is the path to the current working directory prepended to relative paths in
the process of making them absolute.
On some platforms the working directory is reported always with symbolic links resolved so for
consistency the compiler resolves them everywhere.
- The original case of the path is preserved even if the filesystem is case-insensitive but
`case-preserving <https://en.wikipedia.org/wiki/Case_preservation>`_ and the actual case on
disk is different.
.. note::
There are situations where paths cannot be made platform-independent.
For example on Windows the compiler can avoid using drive letters by referring to the root
directory of the current drive as ``/`` but drive letters are still necessary for paths leading
to other drives.
You can avoid such situations by ensuring that all the files are available within a single
directory tree on the same drive.
Once canonicalized, the base path is stripped from all source file paths that start with it.
If the base path is empty or not specified, it is treated as if it was equal to the path to the
current working directory (with all symbolic links resolved).
The result is accepted only if the normalized directory path is the exact prefix of the normalized
file path.
Otherwise the file path remains absolute.
This makes the conversion unambiguous and ensures that the relative path does not start with ``../``.
The resulting file path becomes the source unit name.
.. note::
Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied
was the conversion of path separators.
When working with older versions of the compiler it is recommended to invoke the compiler from
the base path and to only use relative paths on the command line.
.. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
.. _import-remapping:
@ -414,7 +474,7 @@ Here are the detailed rules governing the behaviour of remappings:
.. code-block:: bash
solc /project/=/contracts/ /project/contract.sol --base-path /project # source unit name: /project/contract.sol
solc /project/=/contracts/ /project/contract.sol --base-path /project # source unit name: contract.sol
.. code-block:: solidity
:caption: /project/contract.sol

View File

@ -22,6 +22,8 @@
#include <libsolutil/CommonIO.h>
#include <libsolutil/Exceptions.h>
#include <boost/algorithm/string/predicate.hpp>
using solidity::frontend::ReadCallback;
using solidity::langutil::InternalCompilerError;
using solidity::util::errinfo_comment;
@ -31,9 +33,22 @@ using std::string;
namespace solidity::frontend
{
void FileReader::setBasePath(boost::filesystem::path const& _path)
{
m_basePath = (_path.empty() ? "" : normalizeCLIPathForVFS(_path));
}
void FileReader::setSource(boost::filesystem::path const& _path, SourceCode _source)
{
m_sourceCodes[_path.generic_string()] = std::move(_source);
boost::filesystem::path normalizedPath = normalizeCLIPathForVFS(_path);
boost::filesystem::path prefix = (m_basePath.empty() ? normalizeCLIPathForVFS(".") : m_basePath);
m_sourceCodes[stripPrefixIfPresent(prefix, normalizedPath).generic_string()] = std::move(_source);
}
void FileReader::setStdin(SourceCode _source)
{
m_sourceCodes["<stdin>"] = std::move(_source);
}
void FileReader::setSources(StringMap _sources)
@ -92,5 +107,138 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
}
}
boost::filesystem::path FileReader::normalizeCLIPathForVFS(boost::filesystem::path const& _path)
{
// Detailed normalization rules:
// - Makes the path either be absolute or have slash as root (note that on Windows paths with
// slash as root are not considered absolute by Boost). If it is empty, it becomes
// the current working directory.
// - Collapses redundant . and .. segments.
// - Removes leading .. segments from an absolute path (i.e. /../../ becomes just /).
// - Squashes sequences of multiple path separators into one.
// - Ensures that forward slashes are used as path separators on all platforms.
// - Removes the root name (e.g. drive letter on Windows) when it matches the root name in the
// path to the current working directory.
//
// Also note that this function:
// - Does NOT resolve symlinks (except for symlinks in the path to the current working directory).
// - Does NOT check if the path refers to a file or a directory. If the path ends with a slash,
// the slash is preserved even if it's a file.
// - The only exception are paths where the file name is a dot (e.g. '.' or 'a/b/.'). These
// always have a trailing slash after normalization.
// - Preserves case. Even if the filesystem is case-insensitive but case-preserving and the
// case differs, the actual case from disk is NOT detected.
boost::filesystem::path canonicalWorkDir = boost::filesystem::weakly_canonical(boost::filesystem::current_path());
// NOTE: On UNIX systems the path returned from current_path() has symlinks resolved while on
// Windows it does not. To get consistent results we resolve them on all platforms.
boost::filesystem::path absolutePath = boost::filesystem::absolute(_path, canonicalWorkDir);
// NOTE: boost path preserves certain differences that are ignored by its operator ==.
// E.g. "a//b" vs "a/b" or "a/b/" vs "a/b/.". lexically_normal() does remove these differences.
boost::filesystem::path normalizedPath = absolutePath.lexically_normal();
solAssert(normalizedPath.is_absolute() || normalizedPath.root_path() == "/", "");
// If the path is on the same drive as the working dir, for portability we prefer not to
// include the root name. Do this only for non-UNC paths - my experiments show that on Windows
// when the working dir is an UNC path, / does not not actually refer to the root of the UNC path.
boost::filesystem::path normalizedRootPath = normalizedPath.root_path();
if (!isUNCPath(normalizedPath))
{
boost::filesystem::path workingDirRootPath = canonicalWorkDir.root_path();
if (normalizedRootPath == workingDirRootPath)
normalizedRootPath = "/";
}
// lexically_normal() will not squash paths like "/../../" into "/". We have to do it manually.
boost::filesystem::path dotDotPrefix = absoluteDotDotPrefix(normalizedPath);
boost::filesystem::path normalizedPathNoDotDot = normalizedPath;
if (dotDotPrefix.empty())
normalizedPathNoDotDot = normalizedRootPath / normalizedPath.relative_path();
else
normalizedPathNoDotDot = normalizedRootPath / normalizedPath.lexically_relative(normalizedPath.root_path() / dotDotPrefix);
solAssert(!hasDotDotSegments(normalizedPathNoDotDot), "");
// NOTE: On Windows lexically_normal() converts all separators to forward slashes. Convert them back.
// Separators do not affect path comparison but remain in internal representation returned by native().
// This will also normalize the root name to start with // in UNC paths.
normalizedPathNoDotDot = normalizedPathNoDotDot.generic_string();
// For some reason boost considers "/." different than "/" even though for other directories
// the trailing dot is ignored.
if (normalizedPathNoDotDot == "/.")
return "/";
return normalizedPathNoDotDot;
}
bool FileReader::isPathPrefix(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path)
{
solAssert(!_prefix.empty() && !_path.empty(), "");
// NOTE: On Windows paths starting with a slash (rather than a drive letter) are considered relative by boost.
solAssert(_prefix.is_absolute() || isUNCPath(_prefix) || _prefix.root_path() == "/", "");
solAssert(_path.is_absolute() || isUNCPath(_path) || _path.root_path() == "/", "");
solAssert(_prefix == _prefix.lexically_normal() && _path == _path.lexically_normal(), "");
solAssert(!hasDotDotSegments(_prefix) && !hasDotDotSegments(_path), "");
boost::filesystem::path strippedPath = _path.lexically_relative(
// Before 1.72.0 lexically_relative() was not handling paths with empty, dot and dot dot segments
// correctly (see https://github.com/boostorg/filesystem/issues/76). The only case where this
// is possible after our normalization is a directory name ending in a slash (filename is a dot).
_prefix.filename_is_dot() ? _prefix.parent_path() : _prefix
);
return !strippedPath.empty() && *strippedPath.begin() != "..";
}
boost::filesystem::path FileReader::stripPrefixIfPresent(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path)
{
if (!isPathPrefix(_prefix, _path))
return _path;
boost::filesystem::path strippedPath = _path.lexically_relative(
_prefix.filename_is_dot() ? _prefix.parent_path() : _prefix
);
solAssert(strippedPath.empty() || *strippedPath.begin() != "..", "");
return strippedPath;
}
boost::filesystem::path FileReader::absoluteDotDotPrefix(boost::filesystem::path const& _path)
{
solAssert(_path.is_absolute() || _path.root_path() == "/", "");
boost::filesystem::path _pathWithoutRoot = _path.relative_path();
boost::filesystem::path prefix;
for (boost::filesystem::path const& segment: _pathWithoutRoot)
if (segment.filename_is_dot_dot())
prefix /= segment;
return prefix;
}
bool FileReader::hasDotDotSegments(boost::filesystem::path const& _path)
{
for (boost::filesystem::path const& segment: _path)
if (segment.filename_is_dot_dot())
return true;
return false;
}
bool FileReader::isUNCPath(boost::filesystem::path const& _path)
{
string rootName = _path.root_name().string();
return (
rootName.size() == 2 ||
(rootName.size() > 2 && rootName[2] != rootName[1])
) && (
(rootName[0] == '/' && rootName[1] == '/')
#if defined(_WIN32)
|| (rootName[0] == '\\' && rootName[1] == '\\')
#endif
);
}
}

View File

@ -45,12 +45,13 @@ public:
boost::filesystem::path _basePath = {},
FileSystemPathSet _allowedDirectories = {}
):
m_basePath(std::move(_basePath)),
m_allowedDirectories(std::move(_allowedDirectories)),
m_sourceCodes()
{}
{
setBasePath(_basePath);
}
void setBasePath(boost::filesystem::path _path) { m_basePath = std::move(_path); }
void setBasePath(boost::filesystem::path const& _path);
boost::filesystem::path const& basePath() const noexcept { return m_basePath; }
void allowDirectory(boost::filesystem::path _path) { m_allowedDirectories.insert(std::move(_path)); }
@ -58,17 +59,21 @@ public:
StringMap const& sourceCodes() const noexcept { return m_sourceCodes; }
/// Retrieves the source code for a given source unit ID.
/// Retrieves the source code for a given source unit name.
SourceCode const& sourceCode(SourceUnitName const& _sourceUnitName) const { return m_sourceCodes.at(_sourceUnitName); }
/// Resets all sources to the given map of source unit ID to source codes.
/// Resets all sources to the given map of source unit name to source codes.
/// Does not enforce @a allowedDirectories().
void setSources(StringMap _sources);
/// Adds the source code for a given source unit ID.
/// Adds the source code under a source unit name created by normalizing the file path.
/// Does not enforce @a allowedDirectories().
void setSource(boost::filesystem::path const& _path, SourceCode _source);
/// Adds the source code under the source unit name of @a <stdin>.
/// Does not enforce @a allowedDirectories().
void setStdin(SourceCode _source);
/// Receives a @p _sourceUnitName that refers to a source unit in compiler's virtual filesystem
/// and attempts to interpret it as a path and read the corresponding file from disk.
/// The read will only succeed if the canonical path of the file is within one of the @a allowedDirectories().
@ -83,7 +88,43 @@ public:
return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); };
}
/// Normalizes a filesystem path to make it include all components up to the filesystem root,
/// remove small, inconsequential differences that do not affect the meaning and make it look
/// the same on all platforms (if possible). Symlinks in the path are not resolved.
/// The resulting path uses forward slashes as path separators, has no redundant separators,
/// has no redundant . or .. segments and has no root name if removing it does not change the meaning.
/// The path does not have to actually exist.
static boost::filesystem::path normalizeCLIPathForVFS(boost::filesystem::path const& _path);
/// @returns true if all the path components of @a _prefix are present at the beginning of @a _path.
/// Both paths must be absolute (or have slash as root) and normalized (no . or .. segments, no
/// multiple consecutive slashes).
/// Paths are treated as case-sensitive. Does not require the path to actually exist in the
/// filesystem and does not follow symlinks. Only considers whole segments, e.g. /abc/d is not
/// considered a prefix of /abc/def. Both paths must be non-empty.
/// Ignores the trailing slash, i.e. /a/b/c.sol/ is treated as a valid prefix of /a/b/c.sol.
static bool isPathPrefix(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path);
/// If @a _prefix is actually a prefix of @p _path, removes it from @a _path to make it relative.
/// @returns The path without the prefix or unchanged path if there is not prefix.
/// If @a _path and @_prefix are identical, the result is '.'.
static boost::filesystem::path stripPrefixIfPresent(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path);
/// @returns true if the specified path is an UNC path.
/// UNC paths start with // followed by a name (on Windows they can also start with \\).
/// They are used for network shares on Windows. On UNIX systems they do not have the same
/// functionality but usually they are still recognized and treated in a special way.
static bool isUNCPath(boost::filesystem::path const& _path);
private:
/// If @a _path starts with a number of .. segments, returns a path consisting only of those
/// segments (root name is not included). Otherwise returns an empty path. @a _path must be
/// absolute (or have slash as root).
static boost::filesystem::path absoluteDotDotPrefix(boost::filesystem::path const& _path);
/// @returns true if the path contains any .. segments.
static bool hasDotDotSegments(boost::filesystem::path const& _path);
/// Base path, used for resolving relative paths in imports.
boost::filesystem::path m_basePath;

View File

@ -451,7 +451,7 @@ bool CommandLineInterface::readInputFiles()
m_standardJsonInput = readUntilEnd(m_sin);
}
else
m_fileReader.setSource(g_stdinFileName, readUntilEnd(m_sin));
m_fileReader.setStdin(readUntilEnd(m_sin));
}
if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value())

View File

@ -103,6 +103,7 @@ set(libsolidity_sources
libsolidity/SyntaxTest.h
libsolidity/ViewPureChecker.cpp
libsolidity/analysis/FunctionCallGraph.cpp
libsolidity/interface/FileReader.cpp
)
detect_stray_source_files("${libsolidity_sources}" "libsolidity/")

View File

@ -0,0 +1,450 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/// Unit tests for libsolidity/interface/FileReader.h
#include <libsolidity/interface/FileReader.h>
#include <test/Common.h>
#include <test/FilesystemUtils.h>
#include <test/TemporaryDirectory.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
using namespace std;
using namespace solidity::test;
#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name)
namespace solidity::frontend::test
{
BOOST_AUTO_TEST_SUITE(FileReaderTest)
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_absolute_path)
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/"), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/."), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/"), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/."), "/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b"), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/./b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/.."), "/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../../"), "/");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_relative_path)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
boost::filesystem::create_directories(tempDir.path() / "x/y/z");
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "x/y/z");
// NOTE: If path to work dir contains symlinks (often the case on macOS), boost might resolve
// them, making the path different from tempDirPath.
boost::filesystem::path expectedPrefix = boost::filesystem::current_path().parent_path().parent_path().parent_path();
// On Windows tempDir.path() normally contains the drive letter while the normalized path should not.
expectedPrefix = "/" / expectedPrefix.relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("."), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./"), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".//"), expectedPrefix / "x/y/z/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), expectedPrefix / "x/y");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), expectedPrefix / "x/y/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..//"), expectedPrefix / "x/y/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a"), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a"), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/.//"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./."), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././/"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/b"), expectedPrefix / "x/y/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/b"), expectedPrefix / "x/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("././a/b"), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/./b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../a/b/"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/.."), expectedPrefix / "x/y/z/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/..//"), expectedPrefix / "x/y/z/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../.."), expectedPrefix / "x/y/z/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../../"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../..//"), expectedPrefix / "x/y/z/a/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.././../p/../q/../a/b"), expectedPrefix / "a/b");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_redundant_slashes)
{
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("///"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a//b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a////b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b//"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b////"), "/a/b/");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_unc_path)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
// On Windows tempDir.path() normally contains the drive letter while the normalized path should not.
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
// UNC paths start with // or \\ followed by a name. They are used for network shares on Windows.
// On UNIX systems they are not supported but still treated in a special way.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/"), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b"), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b/"), "//host/a/b/");
#if defined(_WIN32)
// On Windows an UNC path can also start with \\ instead of //
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), "//host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), "//host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), "//host/a/b/");
#else
// On UNIX systems it's just a fancy relative path instead
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), expectedWorkDir / "\\\\host/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), expectedWorkDir / "\\\\host/a/b");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), expectedWorkDir / "\\\\host/a/b/");
#endif
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_root_name_only)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
// A root **path** consists of a directory name (typically / or \) and the root name (drive
// letter (C:), UNC host name (//host), etc.). Either can be empty. Root path as a whole may be
// an absolute path but root name on its own is considered relative. For example on Windows
// C:\ represents the root directory of drive C: but C: on its own refers to the current working
// directory.
// UNC paths
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//"), "//" / expectedWorkDir);
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host"), "//host" / expectedWorkDir);
// On UNIX systems root name is empty.
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(""), expectedWorkDir);
#if defined(_WIN32)
boost::filesystem::path driveLetter = boost::filesystem::current_path().root_name();
solAssert(!driveLetter.empty(), "");
solAssert(driveLetter.is_relative(), "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(driveLetter), expectedWorkDir);
#endif
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_stripping_root_name)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
soltestAssert(boost::filesystem::current_path().is_absolute(), "");
#if defined(_WIN32)
soltestAssert(!boost::filesystem::current_path().root_name().empty(), "");
#endif
boost::filesystem::path normalizedPath = FileReader::normalizeCLIPathForVFS(boost::filesystem::current_path());
BOOST_CHECK_EQUAL(normalizedPath, "/" / boost::filesystem::current_path().relative_path());
BOOST_TEST(normalizedPath.root_name().empty());
BOOST_CHECK_EQUAL(normalizedPath.root_directory(), "/");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_beyond_root)
{
TemporaryWorkingDirectory tempWorkDir("/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../../b/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a"), "/a");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../.."), "/");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../../b/../.."), "/");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_case_sensitivity)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") == expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") != expectedPrefix / "ABC");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") != expectedPrefix / "abc");
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") == expectedPrefix / "ABC");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_separators)
{
// Even on Windows we want / as a separator.
BOOST_TEST((FileReader::normalizeCLIPathForVFS("/a/b/c").native() == boost::filesystem::path("/a/b/c").native()));
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_not_resolve_symlinks)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
soltestAssert(tempDir.path().is_absolute(), "");
boost::filesystem::create_directories(tempDir.path() / "abc");
if (!createSymlinkIfSupportedByFilesystem(tempDir.path() / "abc", tempDir.path() / "sym", true))
return;
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
}
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_resolve_symlinks_in_workdir_when_path_is_relative)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
soltestAssert(tempDir.path().is_absolute(), "");
boost::filesystem::create_directories(tempDir.path() / "abc");
if (!createSymlinkIfSupportedByFilesystem(tempDir.path() / "abc", tempDir.path() / "sym", true))
return;
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "sym");
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::weakly_canonical(boost::filesystem::current_path()).relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("contract.sol"), expectedWorkDir / "contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
}
BOOST_AUTO_TEST_CASE(isPathPrefix_file_prefix)
{
BOOST_TEST(FileReader::isPathPrefix("/", "/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/contract.sol", "/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/contract.sol/", "/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/contract.sol/.", "/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/def/contract.sol"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/token.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/contract", "/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/contract"));
BOOST_TEST(!FileReader::isPathPrefix("/contract.so", "/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/contract.so"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/a/b/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/contract.sol", "/a/b/c/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/a/b/c/d/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/d/contract.sol", "/a/b/c/contract.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/contract.sol"));
}
BOOST_AUTO_TEST_CASE(isPathPrefix_directory_prefix)
{
BOOST_TEST(FileReader::isPathPrefix("/", "/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c", "/"));
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/"));
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/"));
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/"));
BOOST_TEST(FileReader::isPathPrefix("/a/bc/", "/a/bc/"));
BOOST_TEST(!FileReader::isPathPrefix("/a", "/b/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/", "/b/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/contract.sol", "/a/b/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "/a/b/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c", "/a/b/"));
}
BOOST_AUTO_TEST_CASE(isPathPrefix_unc_path)
{
BOOST_TEST(FileReader::isPathPrefix("//host/a/b/", "//host/a/b/"));
BOOST_TEST(FileReader::isPathPrefix("//host/a/b", "//host/a/b/"));
BOOST_TEST(FileReader::isPathPrefix("//host/a/", "//host/a/b/"));
BOOST_TEST(FileReader::isPathPrefix("//host/a", "//host/a/b/"));
BOOST_TEST(FileReader::isPathPrefix("//host/", "//host/a/b/"));
// NOTE: //host and // cannot be passed to isPathPrefix() because they are considered relative.
BOOST_TEST(!FileReader::isPathPrefix("//host1/", "//host2/"));
BOOST_TEST(!FileReader::isPathPrefix("//host1/a/b/", "//host2/a/b/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "//a/b/c/"));
BOOST_TEST(!FileReader::isPathPrefix("//a/b/c/", "/a/b/c/"));
}
BOOST_AUTO_TEST_CASE(isPathPrefix_case_sensitivity)
{
BOOST_TEST(!FileReader::isPathPrefix("/a.sol", "/A.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/A.sol", "/a.sol"));
BOOST_TEST(!FileReader::isPathPrefix("/A/", "/a/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/", "/A/"));
BOOST_TEST(!FileReader::isPathPrefix("/a/BC/def/", "/a/bc/def/contract.sol"));
}
BOOST_AUTO_TEST_CASE(stripPrefixIfPresent_file_prefix)
{
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/contract.sol"), "contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/contract.sol"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol/", "/contract.sol"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol/.", "/contract.sol"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/a/bc/def/contract.sol"), "a/bc/def/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/a/bc/def/contract.sol"), "bc/def/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/a/bc/def/contract.sol"), "bc/def/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc", "/a/bc/def/contract.sol"), "def/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/", "/a/bc/def/contract.sol"), "contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/token.sol"), "/token.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract", "/contract.sol"), "/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/contract"), "/contract");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c/contract.sol", "/a/b/contract.sol"), "/a/b/contract.sol");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/contract.sol", "/a/b/c/contract.sol"), "/a/b/c/contract.sol");
}
BOOST_AUTO_TEST_CASE(stripPrefixIfPresent_directory_prefix)
{
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/a/bc/def/"), "a/bc/def/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/a/bc/def/"), "bc/def/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/a/bc/def/"), "bc/def/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc", "/a/bc/def/"), "def/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/", "/a/bc/def/"), ".");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/b/"), "/b/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/b/"), "/b/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c/", "/a/b/"), "/a/b/");
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c", "/a/b/"), "/a/b/");
}
BOOST_AUTO_TEST_CASE(isUNCPath)
{
BOOST_TEST(FileReader::isUNCPath("//"));
BOOST_TEST(FileReader::isUNCPath("//root"));
BOOST_TEST(FileReader::isUNCPath("//root/"));
#if defined(_WIN32)
// On Windows boost sees these as ///, which is equivalent to /
BOOST_TEST(!FileReader::isUNCPath("//\\"));
BOOST_TEST(!FileReader::isUNCPath("\\\\/"));
BOOST_TEST(!FileReader::isUNCPath("\\/\\"));
BOOST_TEST(FileReader::isUNCPath("\\\\"));
BOOST_TEST(FileReader::isUNCPath("\\\\root"));
BOOST_TEST(FileReader::isUNCPath("\\\\root/"));
#else
// On UNIX it's actually an UNC path
BOOST_TEST(FileReader::isUNCPath("//\\"));
// On UNIX these are just weird relative directory names consisting only of backslashes.
BOOST_TEST(!FileReader::isUNCPath("\\\\/"));
BOOST_TEST(!FileReader::isUNCPath("\\/\\"));
BOOST_TEST(!FileReader::isUNCPath("\\\\"));
BOOST_TEST(!FileReader::isUNCPath("\\\\root"));
BOOST_TEST(!FileReader::isUNCPath("\\\\root/"));
#endif
BOOST_TEST(!FileReader::isUNCPath("\\/"));
BOOST_TEST(!FileReader::isUNCPath("/\\"));
BOOST_TEST(!FileReader::isUNCPath(""));
BOOST_TEST(!FileReader::isUNCPath("."));
BOOST_TEST(!FileReader::isUNCPath(".."));
BOOST_TEST(!FileReader::isUNCPath("/"));
BOOST_TEST(!FileReader::isUNCPath("a"));
BOOST_TEST(!FileReader::isUNCPath("a/b/c"));
BOOST_TEST(!FileReader::isUNCPath("contract.sol"));
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace solidity::frontend::test

View File

@ -141,14 +141,19 @@ BOOST_AUTO_TEST_CASE(cli_input)
createFilesWithParentDirs({tempDir1.path() / "input1.sol"});
createFilesWithParentDirs({tempDir2.path() / "input2.sol"});
boost::filesystem::path expectedDir1 = "/" / tempDir1.path().relative_path();
boost::filesystem::path expectedDir2 = "/" / tempDir2.path().relative_path();
soltestAssert(expectedDir1.is_absolute() || expectedDir1.root_path() == "/", "");
soltestAssert(expectedDir2.is_absolute() || expectedDir2.root_path() == "/", "");
vector<ImportRemapper::Remapping> expectedRemappings = {
{"", "a", "b/c/d"},
{"a", "b", "c/d/e/"},
};
map<string, string> expectedSources = {
{"<stdin>", "\n"},
{(tempDir1.path() / "input1.sol").generic_string(), ""},
{(tempDir2.path() / "input2.sol").generic_string(), ""},
{(expectedDir1 / "input1.sol").generic_string(), ""},
{(expectedDir2 / "input2.sol").generic_string(), ""},
};
PathSet expectedAllowedPaths = {
boost::filesystem::canonical(tempDir1.path()),
@ -181,8 +186,11 @@ BOOST_AUTO_TEST_CASE(cli_ignore_missing_some_files_exist)
TemporaryDirectory tempDir2(TEST_CASE_NAME);
createFilesWithParentDirs({tempDir1.path() / "input1.sol"});
boost::filesystem::path expectedDir1 = "/" / tempDir1.path().relative_path();
soltestAssert(expectedDir1.is_absolute() || expectedDir1.root_path() == "/", "");
// NOTE: Allowed paths should not be added for skipped files.
map<string, string> expectedSources = {{(tempDir1.path() / "input1.sol").generic_string(), ""}};
map<string, string> expectedSources = {{(expectedDir1 / "input1.sol").generic_string(), ""}};
PathSet expectedAllowedPaths = {boost::filesystem::canonical(tempDir1.path())};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({
@ -232,6 +240,7 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file)
BOOST_AUTO_TEST_CASE(standard_json_base_path)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir.path().root_path());
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({
"solc",
@ -245,7 +254,7 @@ BOOST_AUTO_TEST_CASE(standard_json_base_path)
BOOST_TEST(result.options.input.paths.empty());
BOOST_TEST(result.reader.sourceCodes().empty());
BOOST_TEST(result.reader.allowedDirectories().empty());
BOOST_TEST(result.reader.basePath() == tempDir.path());
BOOST_TEST(result.reader.basePath() == "/" / tempDir.path().relative_path());
}
BOOST_AUTO_TEST_CASE(standard_json_no_input_file)
@ -354,6 +363,9 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path)
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
"contract1.sol", // Relative path
@ -374,8 +386,8 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path)
map<string, string> expectedSources = {
{"contract1.sol", ""},
{"c/d/contract2.sol", ""},
{currentDirNoSymlinks.generic_string() + "/contract3.sol", ""},
{otherDirNoSymlinks.generic_string() + "/contract4.sol", ""},
{"contract3.sol", ""},
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
@ -409,6 +421,11 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_same_as_work_dir)
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
"--base-path=" + currentDirNoSymlinks.string(),
@ -431,8 +448,8 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_same_as_work_dir)
map<string, string> expectedSources = {
{"contract1.sol", ""},
{"c/d/contract2.sol", ""},
{currentDirNoSymlinks.generic_string() + "/contract3.sol", ""},
{otherDirNoSymlinks.generic_string() + "/contract4.sol", ""},
{"contract3.sol", ""},
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
@ -450,7 +467,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_same_as_work_dir)
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedOptions.input.basePath);
BOOST_TEST(result.reader.basePath() == expectedWorkDir);
}
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_different_from_work_dir)
@ -469,6 +486,15 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_different_from_wor
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
boost::filesystem::path baseDirNoSymlinks = boost::filesystem::canonical(tempDirBase.path());
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
boost::filesystem::path expectedCurrentDir = "/" / currentDirNoSymlinks.relative_path();
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
boost::filesystem::path expectedBaseDir = "/" / baseDirNoSymlinks.relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
soltestAssert(expectedCurrentDir.is_absolute() || expectedCurrentDir.root_path() == "/", "");
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
soltestAssert(expectedBaseDir.is_absolute() || expectedBaseDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
"--base-path=" + baseDirNoSymlinks.string(),
@ -491,11 +517,11 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_different_from_wor
expectedOptions.modelChecker.initialize = true;
map<string, string> expectedSources = {
{"contract1.sol", ""},
{"c/d/contract2.sol", ""},
{currentDirNoSymlinks.generic_string() + "/contract3.sol", ""},
{otherDirNoSymlinks.generic_string() + "/contract4.sol", ""},
{baseDirNoSymlinks.generic_string() + "/contract5.sol", ""},
{expectedWorkDir.generic_string() + "/contract1.sol", ""},
{expectedWorkDir.generic_string() + "/c/d/contract2.sol", ""},
{expectedCurrentDir.generic_string() + "/contract3.sol", ""},
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
{"contract5.sol", ""},
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
@ -514,7 +540,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_different_from_wor
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedOptions.input.basePath);
BOOST_TEST(result.reader.basePath() == expectedBaseDir);
}
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_relative_base_path)
@ -530,6 +556,11 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_relative_base_path)
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
"--base-path=base",
@ -554,12 +585,12 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_relative_base_path)
expectedOptions.modelChecker.initialize = true;
map<string, string> expectedSources = {
{"contract1.sol", ""},
{"base/contract2.sol", ""},
{currentDirNoSymlinks.generic_string() + "/contract3.sol", ""},
{currentDirNoSymlinks.generic_string() + "/base/contract4.sol", ""},
{otherDirNoSymlinks.generic_string() + "/contract5.sol", ""},
{otherDirNoSymlinks.generic_string() + "/base/contract6.sol", ""},
{expectedWorkDir.generic_string() + "/contract1.sol", ""},
{"contract2.sol", ""},
{expectedWorkDir.generic_string() + "/contract3.sol", ""},
{"contract4.sol", ""},
{expectedOtherDir.generic_string() + "/contract5.sol", ""},
{expectedOtherDir.generic_string() + "/base/contract6.sol", ""},
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
@ -578,7 +609,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_relative_base_path)
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedOptions.input.basePath);
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base");
}
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_normalization_and_weird_names)
@ -589,11 +620,13 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_normalization_and_weird_name
soltestAssert(tempDir.path().is_absolute(), "");
string uncPath = "//" + tempDir.path().relative_path().generic_string();
soltestAssert(uncPath[0] == '/' && uncPath[1] == '/', "");
soltestAssert(uncPath[2] != '/', "");
soltestAssert(FileReader::isUNCPath(uncPath), "");
boost::filesystem::path tempDirNoSymlinks = boost::filesystem::canonical(tempDir.path());
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
@ -684,30 +717,29 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_normalization_and_weird_name
map<string, string> expectedSources = {
#if !defined(_WIN32)
{"file://c/d/contract1.sol", ""},
{"file:///c/d/contract2.sol", ""},
{"https://example.com/contract3.sol", ""},
{"file:/c/d/contract1.sol", ""},
{"file:/c/d/contract2.sol", ""},
{"https:/example.com/contract3.sol", ""},
#endif
{"a/b//contract4.sol", ""},
{"a/b///contract5.sol", ""},
{"a/b////contract6.sol", ""},
{"a/b/contract4.sol", ""},
{"a/b/contract5.sol", ""},
{"a/b/contract6.sol", ""},
{"./a/b/contract7.sol", ""},
{"././a/b/contract8.sol", ""},
{"a/./b/contract9.sol", ""},
{"a/././b/contract10.sol", ""},
{"a/b/contract7.sol", ""},
{"a/b/contract8.sol", ""},
{"a/b/contract9.sol", ""},
{"a/b/contract10.sol", ""},
{"../a/b/contract11.sol", ""},
{"../../a/b/contract12.sol", ""},
{"a/../b/contract13.sol", ""},
{"a/b/../../contract14.sol", ""},
{tempDirNoSymlinks.string() + "/x/y/z/a/../b/contract15.sol", ""},
{tempDirNoSymlinks.string() + "/x/y/z/a/b/../../contract16.sol", ""},
{"/../" + tempDir.path().relative_path().generic_string() + "/contract17.sol", ""},
{"/../../" + tempDir.path().relative_path().generic_string() + "/contract18.sol", ""},
{expectedWorkDir.parent_path().generic_string() + "/a/b/contract11.sol", ""},
{expectedWorkDir.parent_path().parent_path().generic_string() + "/a/b/contract12.sol", ""},
{"b/contract13.sol", ""},
{"contract14.sol", ""},
{"b/contract15.sol", ""},
{"contract16.sol", ""},
{"/" + tempDir.path().relative_path().generic_string() + "/contract17.sol", ""},
{"/" + tempDir.path().relative_path().generic_string() + "/contract18.sol", ""},
#if !defined(_WIN32)
{"<stdin>", ""},
@ -766,6 +798,8 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_symlinks)
)
return;
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
vector<string> commandLine = {
"solc",
@ -788,10 +822,10 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_symlinks)
expectedOptions.modelChecker.initialize = true;
map<string, string> expectedSources = {
{"sym/z/contract.sol", ""},
{"../x/y/z/contract.sol", ""},
{"sym/z/contract_symlink.sol", ""},
{"../x/y/z/contract_symlink.sol", ""},
{"contract.sol", ""},
{(expectedWorkDir.parent_path() / "x/y/z/contract.sol").generic_string(), ""},
{"contract_symlink.sol", ""},
{(expectedWorkDir.parent_path() / "x/y/z/contract_symlink.sol").generic_string(), ""},
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
@ -806,7 +840,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_symlinks)
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedOptions.input.basePath);
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "sym/z/");
}
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_and_stdin)
@ -838,7 +872,7 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_and_stdin)
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == "base");
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base");
}
BOOST_AUTO_TEST_SUITE_END()