1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android-base/logging.h>
18 #include <android-base/parseint.h>
19 #include <android-base/strings.h>
20 #include <hidl-util/FQName.h>
21 #include <hidl-util/Formatter.h>
22 #include <hidl-util/StringHelper.h>
23 #include <cstddef>
24 #include <vector>
25 
26 #include "AidlHelper.h"
27 #include "Coordinator.h"
28 #include "DocComment.h"
29 #include "FormattingConstants.h"
30 #include "Interface.h"
31 #include "Location.h"
32 #include "Method.h"
33 #include "NamedType.h"
34 #include "Reference.h"
35 #include "Type.h"
36 
37 namespace android {
38 
39 static void emitAidlMethodParams(WrappedOutput* wrappedOutput,
40                                  const std::vector<NamedReference<Type>*> args,
41                                  const std::string& prefix, const std::string& attachToLast,
42                                  const Interface& iface) {
43     if (args.size() == 0) {
44         *wrappedOutput << attachToLast;
45         return;
46     }
47 
48     for (size_t i = 0; i < args.size(); i++) {
49         const NamedReference<Type>* arg = args[i];
50         std::string out =
51                 prefix + AidlHelper::getAidlType(*arg->get(), iface.fqName()) + " " + arg->name();
52         wrappedOutput->group([&] {
53             if (i != 0) wrappedOutput->printUnlessWrapped(" ");
54             *wrappedOutput << out;
55             if (i == args.size() - 1) {
56                 if (!attachToLast.empty()) *wrappedOutput << attachToLast;
57             } else {
58                 *wrappedOutput << ",";
59             }
60         });
61     }
62 }
63 
64 std::vector<const Method*> AidlHelper::getUserDefinedMethods(const Interface& interface) {
65     std::vector<const Method*> methods;
66     for (const Interface* iface : interface.typeChain()) {
67         const std::vector<Method*> userDefined = iface->userDefinedMethods();
68         methods.insert(methods.end(), userDefined.begin(), userDefined.end());
69     }
70 
71     return methods;
72 }
73 
74 struct MethodWithVersion {
75     size_t major;
76     size_t minor;
77     const Method* method;
78     std::string name;
79 };
80 
81 static void pushVersionedMethodOntoMap(MethodWithVersion versionedMethod,
82                                        std::map<std::string, MethodWithVersion>* map,
83                                        std::vector<const MethodWithVersion*>* ignored) {
84     const Method* method = versionedMethod.method;
85     std::string name = method->name();
86     size_t underscore = name.find('_');
87     if (underscore != std::string::npos) {
88         std::string version = name.substr(underscore + 1);  // don't include _
89         std::string nameWithoutVersion = name.substr(0, underscore);
90         underscore = version.find('_');
91 
92         size_t major, minor;
93         if (underscore != std::string::npos &&
94             base::ParseUint(version.substr(0, underscore), &major) &&
95             base::ParseUint(version.substr(underscore + 1), &minor)) {
96             // contains major and minor version. consider it's nameWithoutVersion now.
97             name = nameWithoutVersion;
98             versionedMethod.name = nameWithoutVersion;
99         }
100     }
101 
102     // push name onto map
103     auto [it, inserted] = map->emplace(std::move(name), versionedMethod);
104     if (!inserted) {
105         auto* current = &it->second;
106 
107         // Method in the map is more recent
108         if ((current->major > versionedMethod.major) ||
109             (current->major == versionedMethod.major && current->minor > versionedMethod.minor)) {
110             // ignoring versionedMethod
111             ignored->push_back(&versionedMethod);
112             return;
113         }
114 
115         // Either current.major < versioned.major OR versioned.minor >= current.minor
116         ignored->push_back(current);
117         *current = std::move(versionedMethod);
118     }
119 }
120 
121 struct ResultTransformation {
122     enum class TransformType {
123         MOVED,    // Moved to the front of the method name
124         REMOVED,  // Removed the result
125     };
126 
127     std::string resultName;
128     TransformType type;
129 };
130 
131 static bool shouldWarnStatusType(const std::string& typeName) {
132     static const std::vector<std::string> kUppercaseIgnoreStatusTypes = {"ERROR", "STATUS"};
133 
134     const std::string uppercase = StringHelper::Uppercase(typeName);
135     for (const std::string& ignore : kUppercaseIgnoreStatusTypes) {
136         if (uppercase.find(ignore) != std::string::npos) return true;
137     }
138     return false;
139 }
140 
141 void AidlHelper::emitAidl(const Interface& interface, const Coordinator& coordinator) {
142     for (const NamedType* type : interface.getSubTypes()) {
143         emitAidl(*type, coordinator);
144     }
145 
146     Formatter out = getFileWithHeader(interface, coordinator);
147 
148     interface.emitDocComment(out);
149     if (interface.superType() && interface.superType()->fqName() != gIBaseFqName) {
150         out << "// Interface inherits from " << interface.superType()->fqName().string()
151             << " but AIDL does not support interface inheritance.\n";
152     }
153 
154     out << "interface " << getAidlName(interface.fqName()) << " ";
155     out.block([&] {
156         for (const NamedType* type : interface.getSubTypes()) {
157             emitAidl(*type, coordinator);
158         }
159 
160         std::map<std::string, MethodWithVersion> methodMap;
161         std::vector<const MethodWithVersion*> ignoredMethods;
162         std::vector<std::string> methodNames;
163         std::vector<const Interface*> typeChain = interface.typeChain();
164         for (auto iface = typeChain.rbegin(); iface != typeChain.rend(); ++iface) {
165             for (const Method* method : (*iface)->userDefinedMethods()) {
166                 pushVersionedMethodOntoMap(
167                         {(*iface)->fqName().getPackageMajorVersion(),
168                          (*iface)->fqName().getPackageMinorVersion(), method, method->name()},
169                         &methodMap, &ignoredMethods);
170                 methodNames.push_back(method->name());
171             }
172         }
173 
174         std::set<std::string> ignoredMethodNames;
175         out.join(ignoredMethods.begin(), ignoredMethods.end(), "\n",
176                  [&](const MethodWithVersion* versionedMethod) {
177                      out << "// Ignoring method " << versionedMethod->method->name() << " from "
178                          << versionedMethod->major << "." << versionedMethod->minor
179                          << "::" << getAidlName(interface.fqName())
180                          << " since a newer alternative is available.";
181                      ignoredMethodNames.insert(versionedMethod->method->name());
182                  });
183         if (!ignoredMethods.empty()) out << "\n\n";
184 
185         out.join(methodNames.begin(), methodNames.end(), "\n", [&](const std::string& name) {
186             const Method* method = methodMap[name].method;
187             if (method == nullptr) {
188                 CHECK(ignoredMethodNames.count(name) == 1);
189                 return;
190             }
191 
192             std::vector<NamedReference<Type>*> results;
193             std::vector<ResultTransformation> transformations;
194             for (NamedReference<Type>* res : method->results()) {
195                 const std::string aidlType = getAidlType(*res->get(), interface.fqName());
196 
197                 if (shouldWarnStatusType(aidlType)) {
198                     out << "// FIXME: AIDL has built-in status types. Do we need the status type "
199                            "here?\n";
200                 }
201                 results.push_back(res);
202             }
203 
204             if (method->name() != name) {
205                 out << "// Changing method name from " << method->name() << " to " << name << "\n";
206             }
207 
208             std::string returnType = "void";
209             if (results.size() == 1) {
210                 returnType = getAidlType(*results[0]->get(), interface.fqName());
211 
212                 out << "// Adding return type to method instead of out param " << returnType << " "
213                     << results[0]->name() << " since there is only one return value.\n";
214                 transformations.emplace_back(ResultTransformation{
215                         results[0]->name(), ResultTransformation::TransformType::MOVED});
216                 results.clear();
217             }
218 
219             if (method->getDocComment() != nullptr) {
220                 std::vector<std::string> modifiedDocComment;
221                 for (const std::string& line : method->getDocComment()->lines()) {
222                     std::vector<std::string> tokens = base::Split(line, " ");
223                     if (tokens.size() <= 1 || tokens[0] != "@return") {
224                         // unimportant line
225                         modifiedDocComment.emplace_back(line);
226                         continue;
227                     }
228 
229                     const std::string& res = tokens[1];
230                     bool transformed = false;
231                     for (const ResultTransformation& transform : transformations) {
232                         if (transform.resultName != res) continue;
233 
234                         // Some transform was done to it
235                         if (transform.type == ResultTransformation::TransformType::MOVED) {
236                             // remove the name
237                             tokens.erase(++tokens.begin());
238                             transformed = true;
239                         } else {
240                             CHECK(transform.type == ResultTransformation::TransformType::REMOVED);
241                             tokens.insert(tokens.begin(),
242                                           "FIXME: The following return was removed\n");
243                             transformed = true;
244                         }
245                     }
246 
247                     if (!transformed) {
248                         tokens.erase(tokens.begin());
249                         tokens.insert(tokens.begin(), "@param out");
250                     }
251 
252                     modifiedDocComment.emplace_back(base::Join(tokens, " "));
253                 }
254 
255                 DocComment(modifiedDocComment, HIDL_LOCATION_HERE).emit(out);
256             }
257 
258             WrappedOutput wrappedOutput(MAX_LINE_LENGTH);
259 
260             if (method->isOneway()) wrappedOutput << "oneway ";
261             wrappedOutput << returnType << " " << name << "(";
262 
263             if (results.empty()) {
264                 emitAidlMethodParams(&wrappedOutput, method->args(), /* prefix */ "in ",
265                                      /* attachToLast */ ");\n", interface);
266             } else {
267                 if (!method->args().empty()) {
268                     emitAidlMethodParams(&wrappedOutput, method->args(), /* prefix */ "in ",
269                                          /* attachToLast */ ",", interface);
270                     wrappedOutput.printUnlessWrapped(" ");
271                 }
272 
273                 // TODO: Emit warning if a primitive is given as a out param.
274                 emitAidlMethodParams(&wrappedOutput, results, /* prefix */ "out ",
275                                      /* attachToLast */ ");\n", interface);
276             }
277 
278             out << wrappedOutput;
279         });
280     });
281 }
282 
283 }  // namespace android
284