1 //===- Utils.cpp ----------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Implements utility functions for TextAPI Darwin operations.
10 //
11 //===----------------------------------------------------------------------===//
12
13 #include "llvm/TextAPI/Utils.h"
14 #include "llvm/ADT/StringExtras.h"
15 #include "llvm/TextAPI/TextAPIError.h"
16
17 using namespace llvm;
18 using namespace llvm::MachO;
19
replace_extension(SmallVectorImpl<char> & Path,const Twine & Extension)20 void llvm::MachO::replace_extension(SmallVectorImpl<char> &Path,
21 const Twine &Extension) {
22 StringRef P(Path.begin(), Path.size());
23 auto ParentPath = sys::path::parent_path(P);
24 auto Filename = sys::path::filename(P);
25
26 if (!ParentPath.ends_with(Filename.str() + ".framework")) {
27 sys::path::replace_extension(Path, Extension);
28 return;
29 }
30 // Framework dylibs do not have a file extension, in those cases the new
31 // extension is appended. e.g. given Path: "Foo.framework/Foo" and Extension:
32 // "tbd", the result is "Foo.framework/Foo.tbd".
33 SmallString<8> Storage;
34 StringRef Ext = Extension.toStringRef(Storage);
35
36 // Append '.' if needed.
37 if (!Ext.empty() && Ext[0] != '.')
38 Path.push_back('.');
39
40 // Append extension.
41 Path.append(Ext.begin(), Ext.end());
42 }
43
shouldSkipSymLink(const Twine & Path,bool & Result)44 std::error_code llvm::MachO::shouldSkipSymLink(const Twine &Path,
45 bool &Result) {
46 Result = false;
47 SmallString<PATH_MAX> Storage;
48 auto P = Path.toNullTerminatedStringRef(Storage);
49 sys::fs::file_status Stat1;
50 auto EC = sys::fs::status(P.data(), Stat1);
51 if (EC == std::errc::too_many_symbolic_link_levels) {
52 Result = true;
53 return {};
54 }
55
56 if (EC)
57 return EC;
58
59 StringRef Parent = sys::path::parent_path(P);
60 while (!Parent.empty()) {
61 sys::fs::file_status Stat2;
62 if (auto ec = sys::fs::status(Parent, Stat2))
63 return ec;
64
65 if (sys::fs::equivalent(Stat1, Stat2)) {
66 Result = true;
67 return {};
68 }
69
70 Parent = sys::path::parent_path(Parent);
71 }
72 return {};
73 }
74
75 std::error_code
make_relative(StringRef From,StringRef To,SmallVectorImpl<char> & RelativePath)76 llvm::MachO::make_relative(StringRef From, StringRef To,
77 SmallVectorImpl<char> &RelativePath) {
78 SmallString<PATH_MAX> Src = From;
79 SmallString<PATH_MAX> Dst = To;
80 if (auto EC = sys::fs::make_absolute(Src))
81 return EC;
82
83 if (auto EC = sys::fs::make_absolute(Dst))
84 return EC;
85
86 SmallString<PATH_MAX> Result;
87 Src = sys::path::parent_path(From);
88 auto IT1 = sys::path::begin(Src), IT2 = sys::path::begin(Dst),
89 IE1 = sys::path::end(Src), IE2 = sys::path::end(Dst);
90 // Ignore the common part.
91 for (; IT1 != IE1 && IT2 != IE2; ++IT1, ++IT2) {
92 if (*IT1 != *IT2)
93 break;
94 }
95
96 for (; IT1 != IE1; ++IT1)
97 sys::path::append(Result, "../");
98
99 for (; IT2 != IE2; ++IT2)
100 sys::path::append(Result, *IT2);
101
102 if (Result.empty())
103 Result = ".";
104
105 RelativePath.swap(Result);
106
107 return {};
108 }
109
isPrivateLibrary(StringRef Path,bool IsSymLink)110 bool llvm::MachO::isPrivateLibrary(StringRef Path, bool IsSymLink) {
111 // Remove the iOSSupport and DriverKit prefix to identify public locations.
112 Path.consume_front(MACCATALYST_PREFIX_PATH);
113 Path.consume_front(DRIVERKIT_PREFIX_PATH);
114 // Also /Library/Apple prefix for ROSP.
115 Path.consume_front("/Library/Apple");
116
117 if (Path.starts_with("/usr/local/lib"))
118 return true;
119
120 if (Path.starts_with("/System/Library/PrivateFrameworks"))
121 return true;
122
123 if (Path.starts_with("/System/Library/SubFrameworks"))
124 return true;
125
126 // Everything in /usr/lib/swift (including sub-directories) are considered
127 // public.
128 if (Path.consume_front("/usr/lib/swift/"))
129 return false;
130
131 // Only libraries directly in /usr/lib are public. All other libraries in
132 // sub-directories are private.
133 if (Path.consume_front("/usr/lib/"))
134 return Path.contains('/');
135
136 // "/System/Library/Frameworks/" is a public location.
137 if (Path.starts_with("/System/Library/Frameworks/")) {
138 StringRef Name, Rest;
139 std::tie(Name, Rest) =
140 Path.drop_front(sizeof("/System/Library/Frameworks")).split('.');
141
142 // Allow symlinks to top-level frameworks.
143 if (IsSymLink && Rest == "framework")
144 return false;
145
146 // Only top level framework are public.
147 // /System/Library/Frameworks/Foo.framework/Foo ==> true
148 // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true
149 // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false
150 // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar
151 // ==> false
152 // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo
153 // ==> false
154 return !(Rest.starts_with("framework/") &&
155 (Rest.ends_with(Name) || Rest.ends_with((Name + ".tbd").str()) ||
156 (IsSymLink && Rest.ends_with("Current"))));
157 }
158 return false;
159 }
160
161 static StringLiteral RegexMetachars = "()^$|+.[]\\{}";
162
createRegexFromGlob(StringRef Glob)163 llvm::Expected<Regex> llvm::MachO::createRegexFromGlob(StringRef Glob) {
164 SmallString<128> RegexString("^");
165 unsigned NumWildcards = 0;
166 for (unsigned i = 0; i < Glob.size(); ++i) {
167 char C = Glob[i];
168 switch (C) {
169 case '?':
170 RegexString += '.';
171 break;
172 case '*': {
173 const char *PrevChar = i > 0 ? Glob.data() + i - 1 : nullptr;
174 NumWildcards = 1;
175 ++i;
176 while (i < Glob.size() && Glob[i] == '*') {
177 ++NumWildcards;
178 ++i;
179 }
180 const char *NextChar = i < Glob.size() ? Glob.data() + i : nullptr;
181
182 if ((NumWildcards > 1) && (PrevChar == nullptr || *PrevChar == '/') &&
183 (NextChar == nullptr || *NextChar == '/')) {
184 RegexString += "(([^/]*(/|$))*)";
185 } else
186 RegexString += "([^/]*)";
187 break;
188 }
189 default:
190 if (RegexMetachars.contains(C))
191 RegexString.push_back('\\');
192 RegexString.push_back(C);
193 }
194 }
195 RegexString.push_back('$');
196 if (NumWildcards == 0)
197 return make_error<StringError>("not a glob", inconvertibleErrorCode());
198
199 llvm::Regex Rule = Regex(RegexString);
200 std::string Error;
201 if (!Rule.isValid(Error))
202 return make_error<StringError>(Error, inconvertibleErrorCode());
203
204 return std::move(Rule);
205 }
206
207 Expected<AliasMap>
parseAliasList(std::unique_ptr<llvm::MemoryBuffer> & Buffer)208 llvm::MachO::parseAliasList(std::unique_ptr<llvm::MemoryBuffer> &Buffer) {
209 SmallVector<StringRef, 16> Lines;
210 AliasMap Aliases;
211 Buffer->getBuffer().split(Lines, "\n", /*MaxSplit=*/-1,
212 /*KeepEmpty=*/false);
213 for (const StringRef Line : Lines) {
214 StringRef L = Line.trim();
215 if (L.empty())
216 continue;
217 // Skip comments.
218 if (L.starts_with("#"))
219 continue;
220 StringRef Symbol, Remain, Alias;
221 // Base symbol is separated by whitespace.
222 std::tie(Symbol, Remain) = getToken(L);
223 // The Alias symbol ends before a comment or EOL.
224 std::tie(Alias, Remain) = getToken(Remain, "#");
225 Alias = Alias.trim();
226 if (Alias.empty())
227 return make_error<TextAPIError>(
228 TextAPIError(TextAPIErrorCode::InvalidInputFormat,
229 ("missing alias for: " + Symbol).str()));
230 SimpleSymbol AliasSym = parseSymbol(Alias);
231 SimpleSymbol BaseSym = parseSymbol(Symbol);
232 Aliases[{AliasSym.Name.str(), AliasSym.Kind}] = {BaseSym.Name.str(),
233 BaseSym.Kind};
234 }
235
236 return Aliases;
237 }
238
getPathsForPlatform(const PathToPlatformSeq & Paths,PlatformType Platform)239 PathSeq llvm::MachO::getPathsForPlatform(const PathToPlatformSeq &Paths,
240 PlatformType Platform) {
241 PathSeq Result;
242 for (const auto &[Path, CurrP] : Paths) {
243 if (!CurrP.has_value() || CurrP.value() == Platform)
244 Result.push_back(Path);
245 }
246 return Result;
247 }
248