| 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(path: P); |
| 24 | auto Filename = sys::path::filename(path: P); |
| 25 | |
| 26 | if (!ParentPath.ends_with(Suffix: Filename.str() + ".framework" )) { |
| 27 | sys::path::replace_extension(path&: Path, extension: 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(Out&: Storage); |
| 35 | |
| 36 | // Append '.' if needed. |
| 37 | if (!Ext.empty() && Ext[0] != '.') |
| 38 | Path.push_back(Elt: '.'); |
| 39 | |
| 40 | // Append extension. |
| 41 | Path.append(in_start: Ext.begin(), in_end: 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(Out&: Storage); |
| 49 | sys::fs::file_status Stat1; |
| 50 | auto EC = sys::fs::status(path: P.data(), result&: 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(path: P); |
| 60 | while (!Parent.empty()) { |
| 61 | sys::fs::file_status Stat2; |
| 62 | if (auto ec = sys::fs::status(path: Parent, result&: Stat2)) |
| 63 | return ec; |
| 64 | |
| 65 | if (sys::fs::equivalent(A: Stat1, B: Stat2)) { |
| 66 | Result = true; |
| 67 | return {}; |
| 68 | } |
| 69 | |
| 70 | Parent = sys::path::parent_path(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(path&: Src)) |
| 81 | return EC; |
| 82 | |
| 83 | if (auto EC = sys::fs::make_absolute(path&: Dst)) |
| 84 | return EC; |
| 85 | |
| 86 | SmallString<PATH_MAX> Result; |
| 87 | Src = sys::path::parent_path(path: From); |
| 88 | auto IT1 = sys::path::begin(path: Src), IT2 = sys::path::begin(path: Dst), |
| 89 | IE1 = sys::path::end(path: Src), IE2 = sys::path::end(path: 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(path&: Result, a: "../" ); |
| 98 | |
| 99 | for (; IT2 != IE2; ++IT2) |
| 100 | sys::path::append(path&: Result, a: *IT2); |
| 101 | |
| 102 | if (Result.empty()) |
| 103 | Result = "." ; |
| 104 | |
| 105 | RelativePath.swap(RHS&: 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(Prefix: "/Library/Apple" ); |
| 116 | |
| 117 | if (Path.starts_with(Prefix: "/usr/local/lib" )) |
| 118 | return true; |
| 119 | |
| 120 | if (Path.starts_with(Prefix: "/System/Library/PrivateFrameworks" )) |
| 121 | return true; |
| 122 | |
| 123 | if (Path.starts_with(Prefix: "/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(Prefix: "/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(Prefix: "/usr/lib/" )) |
| 134 | return Path.contains(C: '/'); |
| 135 | |
| 136 | // "/System/Library/Frameworks/" is a public location. |
| 137 | if (Path.starts_with(Prefix: "/System/Library/Frameworks/" )) { |
| 138 | StringRef Name, Rest; |
| 139 | std::tie(args&: Name, args&: Rest) = |
| 140 | Path.drop_front(N: sizeof("/System/Library/Frameworks" )).split(Separator: '.'); |
| 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(Prefix: "framework/" ) && |
| 155 | (Rest.ends_with(Suffix: Name) || Rest.ends_with(Suffix: (Name + ".tbd" ).str()) || |
| 156 | (IsSymLink && Rest.ends_with(Suffix: "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(Elt: '\\'); |
| 192 | RegexString.push_back(Elt: C); |
| 193 | } |
| 194 | } |
| 195 | RegexString.push_back(Elt: '$'); |
| 196 | if (NumWildcards == 0) |
| 197 | return make_error<StringError>(Args: "not a glob" , Args: inconvertibleErrorCode()); |
| 198 | |
| 199 | llvm::Regex Rule = Regex(RegexString); |
| 200 | std::string Error; |
| 201 | if (!Rule.isValid(Error)) |
| 202 | return make_error<StringError>(Args&: Error, Args: 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(A&: Lines, Separator: "\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(Prefix: "#" )) |
| 219 | continue; |
| 220 | StringRef Symbol, Remain, Alias; |
| 221 | // Base symbol is separated by whitespace. |
| 222 | std::tie(args&: Symbol, args&: Remain) = getToken(Source: L); |
| 223 | // The Alias symbol ends before a comment or EOL. |
| 224 | std::tie(args&: Alias, args&: Remain) = getToken(Source: Remain, Delimiters: "#" ); |
| 225 | Alias = Alias.trim(); |
| 226 | if (Alias.empty()) |
| 227 | return make_error<TextAPIError>( |
| 228 | Args: TextAPIError(TextAPIErrorCode::InvalidInputFormat, |
| 229 | ("missing alias for: " + Symbol).str())); |
| 230 | SimpleSymbol AliasSym = parseSymbol(SymName: Alias); |
| 231 | SimpleSymbol BaseSym = parseSymbol(SymName: 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(x: Path); |
| 245 | } |
| 246 | return Result; |
| 247 | } |
| 248 | |