22
22
#include < libsolutil/CommonIO.h>
23
23
#include < libsolutil/Exceptions.h>
24
24
25
+ #include < boost/algorithm/string/predicate.hpp>
26
+
25
27
using solidity::frontend::ReadCallback;
26
28
using solidity::langutil::InternalCompilerError;
27
29
using solidity::util::errinfo_comment;
@@ -31,9 +33,22 @@ using std::string;
31
33
namespace solidity ::frontend
32
34
{
33
35
36
+ void FileReader::setBasePath (boost::filesystem::path const & _path)
37
+ {
38
+ m_basePath = (_path.empty () ? " " : normalizeCLIPathForVFS (_path));
39
+ }
40
+
34
41
void FileReader::setSource (boost::filesystem::path const & _path, SourceCode _source)
35
42
{
36
- m_sourceCodes[_path.generic_string ()] = std::move (_source);
43
+ boost::filesystem::path normalizedPath = normalizeCLIPathForVFS (_path);
44
+ boost::filesystem::path prefix = (m_basePath.empty () ? normalizeCLIPathForVFS (" ." ) : m_basePath);
45
+
46
+ m_sourceCodes[stripPrefixIfPresent (prefix, normalizedPath).generic_string ()] = std::move (_source);
47
+ }
48
+
49
+ void FileReader::setStdin (SourceCode _source)
50
+ {
51
+ m_sourceCodes[" <stdin>" ] = std::move (_source);
37
52
}
38
53
39
54
void FileReader::setSources (StringMap _sources)
@@ -92,5 +107,138 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
92
107
}
93
108
}
94
109
110
+ boost::filesystem::path FileReader::normalizeCLIPathForVFS (boost::filesystem::path const & _path)
111
+ {
112
+ // Detailed normalization rules:
113
+ // - Makes the path either be absolute or have slash as root (note that on Windows paths with
114
+ // slash as root are not considered absolute by Boost). If it is empty, it becomes
115
+ // the current working directory.
116
+ // - Collapses redundant . and .. segments.
117
+ // - Removes leading .. segments from an absolute path (i.e. /../../ becomes just /).
118
+ // - Squashes sequences of multiple path separators into one.
119
+ // - Ensures that forward slashes are used as path separators on all platforms.
120
+ // - Removes the root name (e.g. drive letter on Windows) when it matches the root name in the
121
+ // path to the current working directory.
122
+ //
123
+ // Also note that this function:
124
+ // - Does NOT resolve symlinks (except for symlinks in the path to the current working directory).
125
+ // - Does NOT check if the path refers to a file or a directory. If the path ends with a slash,
126
+ // the slash is preserved even if it's a file.
127
+ // - The only exception are paths where the file name is a dot (e.g. '.' or 'a/b/.'). These
128
+ // always have a trailing slash after normalization.
129
+ // - Preserves case. Even if the filesystem is case-insensitive but case-preserving and the
130
+ // case differs, the actual case from disk is NOT detected.
131
+
132
+ boost::filesystem::path canonicalWorkDir = boost::filesystem::weakly_canonical (boost::filesystem::current_path ());
133
+
134
+ // NOTE: On UNIX systems the path returned from current_path() has symlinks resolved while on
135
+ // Windows it does not. To get consistent results we resolve them on all platforms.
136
+ boost::filesystem::path absolutePath = boost::filesystem::absolute (_path, canonicalWorkDir);
137
+
138
+ // NOTE: boost path preserves certain differences that are ignored by its operator ==.
139
+ // E.g. "a//b" vs "a/b" or "a/b/" vs "a/b/.". lexically_normal() does remove these differences.
140
+ boost::filesystem::path normalizedPath = absolutePath.lexically_normal ();
141
+ solAssert (normalizedPath.is_absolute () || normalizedPath.root_path () == " /" , " " );
142
+
143
+ // If the path is on the same drive as the working dir, for portability we prefer not to
144
+ // include the root name. Do this only for non-UNC paths - my experiments show that on Windows
145
+ // when the working dir is an UNC path, / does not not actually refer to the root of the UNC path.
146
+ boost::filesystem::path normalizedRootPath = normalizedPath.root_path ();
147
+ if (!isUNCPath (normalizedPath))
148
+ {
149
+ boost::filesystem::path workingDirRootPath = canonicalWorkDir.root_path ();
150
+ if (normalizedRootPath == workingDirRootPath)
151
+ normalizedRootPath = " /" ;
152
+ }
153
+
154
+ // lexically_normal() will not squash paths like "/../../" into "/". We have to do it manually.
155
+ boost::filesystem::path dotDotPrefix = absoluteDotDotPrefix (normalizedPath);
156
+
157
+ boost::filesystem::path normalizedPathNoDotDot = normalizedPath;
158
+ if (dotDotPrefix.empty ())
159
+ normalizedPathNoDotDot = normalizedRootPath / normalizedPath.relative_path ();
160
+ else
161
+ normalizedPathNoDotDot = normalizedRootPath / normalizedPath.lexically_relative (normalizedPath.root_path () / dotDotPrefix);
162
+ solAssert (!hasDotDotSegments (normalizedPathNoDotDot), " " );
163
+
164
+ // NOTE: On Windows lexically_normal() converts all separators to forward slashes. Convert them back.
165
+ // Separators do not affect path comparison but remain in internal representation returned by native().
166
+ // This will also normalize the root name to start with // in UNC paths.
167
+ normalizedPathNoDotDot = normalizedPathNoDotDot.generic_string ();
168
+
169
+ // For some reason boost considers "/." different than "/" even though for other directories
170
+ // the trailing dot is ignored.
171
+ if (normalizedPathNoDotDot == " /." )
172
+ return " /" ;
173
+
174
+ return normalizedPathNoDotDot;
95
175
}
96
176
177
+ bool FileReader::isPathPrefix (boost::filesystem::path const & _prefix, boost::filesystem::path const & _path)
178
+ {
179
+ solAssert (!_prefix.empty () && !_path.empty (), " " );
180
+ // NOTE: On Windows paths starting with a slash (rather than a drive letter) are considered relative by boost.
181
+ solAssert (_prefix.is_absolute () || isUNCPath (_prefix) || _prefix.root_path () == " /" , " " );
182
+ solAssert (_path.is_absolute () || isUNCPath (_path) || _path.root_path () == " /" , " " );
183
+ solAssert (_prefix == _prefix.lexically_normal () && _path == _path.lexically_normal (), " " );
184
+ solAssert (!hasDotDotSegments (_prefix) && !hasDotDotSegments (_path), " " );
185
+
186
+ boost::filesystem::path strippedPath = _path.lexically_relative (
187
+ // Before 1.72.0 lexically_relative() was not handling paths with empty, dot and dot dot segments
188
+ // correctly (see https://github.com/boostorg/filesystem/issues/76). The only case where this
189
+ // is possible after our normalization is a directory name ending in a slash (filename is a dot).
190
+ _prefix.filename_is_dot () ? _prefix.parent_path () : _prefix
191
+ );
192
+ return !strippedPath.empty () && *strippedPath.begin () != " .." ;
193
+ }
194
+
195
+ boost::filesystem::path FileReader::stripPrefixIfPresent (boost::filesystem::path const & _prefix, boost::filesystem::path const & _path)
196
+ {
197
+ if (!isPathPrefix (_prefix, _path))
198
+ return _path;
199
+
200
+ boost::filesystem::path strippedPath = _path.lexically_relative (
201
+ _prefix.filename_is_dot () ? _prefix.parent_path () : _prefix
202
+ );
203
+ solAssert (strippedPath.empty () || *strippedPath.begin () != " .." , " " );
204
+ return strippedPath;
205
+ }
206
+
207
+ boost::filesystem::path FileReader::absoluteDotDotPrefix (boost::filesystem::path const & _path)
208
+ {
209
+ solAssert (_path.is_absolute () || _path.root_path () == " /" , " " );
210
+
211
+ boost::filesystem::path _pathWithoutRoot = _path.relative_path ();
212
+ boost::filesystem::path prefix;
213
+ for (boost::filesystem::path const & segment: _pathWithoutRoot)
214
+ if (segment.filename_is_dot_dot ())
215
+ prefix /= segment;
216
+
217
+ return prefix;
218
+ }
219
+
220
+ bool FileReader::hasDotDotSegments (boost::filesystem::path const & _path)
221
+ {
222
+ for (boost::filesystem::path const & segment: _path)
223
+ if (segment.filename_is_dot_dot ())
224
+ return true ;
225
+
226
+ return false ;
227
+ }
228
+
229
+ bool FileReader::isUNCPath (boost::filesystem::path const & _path)
230
+ {
231
+ string rootName = _path.root_name ().string ();
232
+
233
+ return (
234
+ rootName.size () == 2 ||
235
+ (rootName.size () > 2 && rootName[2 ] != rootName[1 ])
236
+ ) && (
237
+ (rootName[0 ] == ' /' && rootName[1 ] == ' /' )
238
+ #if defined(_WIN32)
239
+ || (rootName[0 ] == ' \\ ' && rootName[1 ] == ' \\ ' )
240
+ #endif
241
+ );
242
+ }
243
+
244
+ }
0 commit comments