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 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 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 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 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 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> 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 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