1//===- FileList.cpp ---------------------------------------------*- C++ -*-===//
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#include "clang/InstallAPI/FileList.h"
10#include "llvm/ADT/StringSwitch.h"
11#include "llvm/Support/Error.h"
12#include "llvm/Support/JSON.h"
13#include "llvm/TextAPI/TextAPIError.h"
14#include <optional>
15
16// clang-format off
17/*
18InstallAPI JSON Input Format specification.
19
20{
21 "headers" : [ # Required: Key must exist.
22 { # Optional: May contain 0 or more header inputs.
23 "path" : "/usr/include/mach-o/dlfn.h", # Required: Path should point to destination
24 # location where applicable.
25 "type" : "public", # Required: Maps to HeaderType for header.
26 "language": "c++" # Optional: Language mode for header.
27 }
28 ],
29 "version" : "3" # Required: Version 3 supports language mode
30 & project header input.
31}
32*/
33// clang-format on
34
35using namespace llvm;
36using namespace llvm::json;
37using namespace llvm::MachO;
38using namespace clang::installapi;
39
40namespace {
41class Implementation {
42private:
43 Expected<StringRef> parseString(const Object *Obj, StringRef Key,
44 StringRef Error);
45 Expected<StringRef> parsePath(const Object *Obj);
46 Expected<HeaderType> parseType(const Object *Obj);
47 std::optional<clang::Language> parseLanguage(const Object *Obj);
48 Error parseHeaders(Array &Headers);
49
50public:
51 std::unique_ptr<MemoryBuffer> InputBuffer;
52 clang::FileManager *FM;
53 unsigned Version;
54 HeaderSeq HeaderList;
55
56 Error parse(StringRef Input);
57};
58
59Expected<StringRef>
60Implementation::parseString(const Object *Obj, StringRef Key, StringRef Error) {
61 auto Str = Obj->getString(K: Key);
62 if (!Str)
63 return make_error<StringError>(Args&: Error, Args: inconvertibleErrorCode());
64 return *Str;
65}
66
67Expected<HeaderType> Implementation::parseType(const Object *Obj) {
68 auto TypeStr =
69 parseString(Obj, Key: "type", Error: "required field 'type' not specified");
70 if (!TypeStr)
71 return TypeStr.takeError();
72
73 if (*TypeStr == "public")
74 return HeaderType::Public;
75 else if (*TypeStr == "private")
76 return HeaderType::Private;
77 else if (*TypeStr == "project" && Version >= 2)
78 return HeaderType::Project;
79
80 return make_error<TextAPIError>(Args: TextAPIErrorCode::InvalidInputFormat,
81 Args: "unsupported header type");
82}
83
84Expected<StringRef> Implementation::parsePath(const Object *Obj) {
85 auto Path = parseString(Obj, Key: "path", Error: "required field 'path' not specified");
86 if (!Path)
87 return Path.takeError();
88
89 return *Path;
90}
91
92std::optional<clang::Language>
93Implementation::parseLanguage(const Object *Obj) {
94 auto Language = Obj->getString(K: "language");
95 if (!Language)
96 return std::nullopt;
97
98 return StringSwitch<clang::Language>(*Language)
99 .Case(S: "c", Value: clang::Language::C)
100 .Case(S: "c++", Value: clang::Language::CXX)
101 .Case(S: "objective-c", Value: clang::Language::ObjC)
102 .Case(S: "objective-c++", Value: clang::Language::ObjCXX)
103 .Default(Value: clang::Language::Unknown);
104}
105
106Error Implementation::parseHeaders(Array &Headers) {
107 for (const auto &H : Headers) {
108 auto *Obj = H.getAsObject();
109 if (!Obj)
110 return make_error<StringError>(Args: "expect a JSON object",
111 Args: inconvertibleErrorCode());
112 auto Type = parseType(Obj);
113 if (!Type)
114 return Type.takeError();
115 auto Path = parsePath(Obj);
116 if (!Path)
117 return Path.takeError();
118 auto Language = parseLanguage(Obj);
119
120 StringRef PathStr = *Path;
121 if (*Type == HeaderType::Project) {
122 HeaderList.emplace_back(
123 args: HeaderFile{PathStr, *Type, /*IncludeName=*/"", Language});
124 continue;
125 }
126
127 if (FM)
128 if (!FM->getOptionalFileRef(Filename: PathStr))
129 return createFileError(
130 F: PathStr, EC: make_error_code(e: std::errc::no_such_file_or_directory));
131
132 auto IncludeName = createIncludeHeaderName(FullPath: PathStr);
133 HeaderList.emplace_back(args&: PathStr, args&: *Type,
134 args: IncludeName.has_value() ? IncludeName.value() : "",
135 args&: Language);
136 }
137
138 return Error::success();
139}
140
141Error Implementation::parse(StringRef Input) {
142 auto Val = json::parse(JSON: Input);
143 if (!Val)
144 return Val.takeError();
145
146 auto *Root = Val->getAsObject();
147 if (!Root)
148 return make_error<StringError>(Args: "not a JSON object",
149 Args: inconvertibleErrorCode());
150
151 auto VersionStr = Root->getString(K: "version");
152 if (!VersionStr)
153 return make_error<TextAPIError>(Args: TextAPIErrorCode::InvalidInputFormat,
154 Args: "required field 'version' not specified");
155 if (VersionStr->getAsInteger(Radix: 10, Result&: Version))
156 return make_error<TextAPIError>(Args: TextAPIErrorCode::InvalidInputFormat,
157 Args: "invalid version number");
158
159 if (Version < 1 || Version > 3)
160 return make_error<TextAPIError>(Args: TextAPIErrorCode::InvalidInputFormat,
161 Args: "unsupported version");
162
163 // Not specifying any header files should be atypical, but valid.
164 auto Headers = Root->getArray(K: "headers");
165 if (!Headers)
166 return Error::success();
167
168 Error Err = parseHeaders(Headers&: *Headers);
169 if (Err)
170 return Err;
171
172 return Error::success();
173}
174} // namespace
175
176llvm::Error
177FileListReader::loadHeaders(std::unique_ptr<MemoryBuffer> InputBuffer,
178 HeaderSeq &Destination, clang::FileManager *FM) {
179 Implementation Impl;
180 Impl.InputBuffer = std::move(InputBuffer);
181 Impl.FM = FM;
182
183 if (llvm::Error Err = Impl.parse(Input: Impl.InputBuffer->getBuffer()))
184 return Err;
185
186 Destination.reserve(n: Destination.size() + Impl.HeaderList.size());
187 llvm::move(Range&: Impl.HeaderList, Out: std::back_inserter(x&: Destination));
188
189 return Error::success();
190}
191