You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
16 KiB

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <hidl-util/FQName.h>
#include <hidl-util/Formatter.h>
#include <hidl-util/StringHelper.h>
#include <cstddef>
#include <vector>
#include "AidlHelper.h"
#include "Coordinator.h"
#include "DocComment.h"
#include "FormattingConstants.h"
#include "Interface.h"
#include "Location.h"
#include "Method.h"
#include "NamedType.h"
#include "Reference.h"
#include "Type.h"
namespace android {
static void emitAidlMethodParams(WrappedOutput* wrappedOutput,
const std::vector<NamedReference<Type>*> args,
const std::string& prefix, const std::string& attachToLast,
const Interface& iface) {
if (args.size() == 0) {
*wrappedOutput << attachToLast;
return;
}
for (size_t i = 0; i < args.size(); i++) {
const NamedReference<Type>* arg = args[i];
std::string out =
prefix + AidlHelper::getAidlType(*arg->get(), iface.fqName()) + " " + arg->name();
wrappedOutput->group([&] {
if (i != 0) wrappedOutput->printUnlessWrapped(" ");
*wrappedOutput << out;
if (i == args.size() - 1) {
if (!attachToLast.empty()) *wrappedOutput << attachToLast;
} else {
*wrappedOutput << ",";
}
});
}
}
std::vector<const Method*> AidlHelper::getUserDefinedMethods(Formatter& out,
const Interface& interface) {
std::vector<const Method*> methods;
for (const Interface* iface : interface.typeChain()) {
if (!AidlHelper::shouldBeExpanded(interface.fqName(), iface->fqName()) &&
iface->fqName() != gIBaseFqName) {
out << "// Types from " << iface->fqName().string()
<< " are not included because it is in a separate package, and it is expected to "
"be a separate AIDL interface. To include these methods, use the '-e' argument. "
"\n";
break;
}
const std::vector<Method*> userDefined = iface->userDefinedMethods();
methods.insert(methods.end(), userDefined.begin(), userDefined.end());
}
return methods;
}
// Represents a node which is potentially overriding another node.
// e.g. if this is 'foo_1_4'
template <class NODE>
struct NodeWithVersion {
size_t major; // 1
size_t minor; // 4
const NODE* node; // HIDL object representing foo_1_4.
std::string baseName; // foo
};
std::string getBaseName(const std::string& rawName) {
size_t underscore = rawName.find('_');
if (underscore != std::string::npos) {
std::string version = rawName.substr(underscore + 1); // don't include _
std::string baseName = rawName.substr(0, underscore);
underscore = version.find('_');
size_t major, minor;
if (underscore != std::string::npos &&
base::ParseUint(version.substr(0, underscore), &major) &&
base::ParseUint(version.substr(underscore + 1), &minor)) {
// contains major and minor version. consider it's baseName now.
return baseName;
}
}
return rawName;
}
template <class NODE>
static void pushVersionedNodeOntoMap(const NODE& versionedNode,
std::map<std::string, NODE>* latestNodeForBaseName,
std::vector<const NODE>* supersededNode) {
// attempt to push name onto latestNodeForBaseName
auto [it, inserted] =
latestNodeForBaseName->emplace(std::move(versionedNode.baseName), versionedNode);
if (!inserted) {
auto* current = &it->second;
// Node in the latestNodeForBaseName is more recent
if ((current->major > versionedNode.major) ||
(current->major == versionedNode.major && current->minor > versionedNode.minor)) {
supersededNode->push_back(versionedNode);
return;
}
// Either current.major < versioned.major OR versioned.minor >= current.minor
supersededNode->push_back(*current);
*current = std::move(versionedNode);
}
}
struct ResultTransformation {
enum class TransformType {
MOVED, // Moved to the front of the method name
REMOVED, // Removed the result
};
std::string resultName;
TransformType type;
};
static bool shouldWarnStatusType(const std::string& typeName) {
static const std::vector<std::string> kUppercaseIgnoreStatusTypes = {"ERROR", "STATUS"};
const std::string uppercase = StringHelper::Uppercase(typeName);
for (const std::string& ignore : kUppercaseIgnoreStatusTypes) {
if (uppercase.find(ignore) != std::string::npos) return true;
}
return false;
}
static bool shouldWarnOutParam(const std::string& typeName) {
static const std::vector<std::string> kNoOutParamTypes = {"ParcelFileDescriptor",
"FileDescriptor",
"ParcelableHolder",
"IBinder",
"String",
"CharacterSequence",
"void",
"boolean",
"byte",
"char",
"int",
"long",
"float",
"double"};
return std::find(kNoOutParamTypes.begin(), kNoOutParamTypes.end(), typeName) !=
kNoOutParamTypes.end();
}
void AidlHelper::emitAidl(
const Interface& interface, const Coordinator& coordinator,
const std::map<const NamedType*, const ProcessedCompoundType>& processedTypes) {
Formatter out = getFileWithHeader(interface, coordinator, processedTypes);
interface.emitDocComment(out);
if (interface.superType() && interface.superType()->fqName() != gIBaseFqName) {
out << "// Interface inherits from " << interface.superType()->fqName().string()
<< " but AIDL does not support interface inheritance.\n";
}
out << "@VintfStability\n";
out << "interface " << getAidlName(interface.fqName()) << " ";
out.block([&] {
std::map<std::string, NodeWithVersion<NamedType>> latestTypeForBaseName;
std::vector<const NodeWithVersion<NamedType>> supersededNamedTypes;
std::map<std::string, NodeWithVersion<Method>> latestMethodForBaseName;
std::vector<const NodeWithVersion<Method>> supersededMethods;
for (const Interface* iface : interface.typeChain()) {
if (!AidlHelper::shouldBeExpanded(interface.fqName(), iface->fqName())) {
// Stop traversing extended interfaces once they leave this package
break;
}
for (const Method* method : iface->userDefinedMethods()) {
pushVersionedNodeOntoMap({iface->fqName().getPackageMajorVersion(),
iface->fqName().getPackageMinorVersion(), method,
getBaseName(method->name())},
&latestMethodForBaseName, &supersededMethods);
}
// Types from other interfaces will be handled while those interfaces
// are being emitted.
if (iface->getBaseName() != interface.getBaseName()) {
continue;
}
for (const NamedType* type : iface->getSubTypes()) {
// The baseName for types is not being stripped of the version
// numbers like that of the methods. If a type was named
// BigStruct_1_1 and the previous version was named BigStruct,
// they will be treated as two different types.
pushVersionedNodeOntoMap({iface->fqName().getPackageMajorVersion(),
iface->fqName().getPackageMinorVersion(), type,
getAidlName(type->fqName())},
&latestTypeForBaseName, &supersededNamedTypes);
}
}
// Add comment for superseded types
out.join(supersededNamedTypes.begin(), supersededNamedTypes.end(), "\n",
[&](const NodeWithVersion<NamedType>& versionedType) {
out << "// Ignoring type " << getAidlName(versionedType.node->fqName())
<< " from " << versionedType.major << "." << versionedType.minor
<< "::" << getAidlName(interface.fqName())
<< " since a newer alternative is available.";
});
if (!supersededNamedTypes.empty()) out << "\n\n";
// Add comment for superseded methods
out.join(supersededMethods.begin(), supersededMethods.end(), "\n",
[&](const NodeWithVersion<Method>& versionedMethod) {
out << "// Ignoring method " << versionedMethod.node->name() << " from "
<< versionedMethod.major << "." << versionedMethod.minor
<< "::" << getAidlName(interface.fqName())
<< " since a newer alternative is available.";
});
if (!supersededMethods.empty()) out << "\n\n";
// Emit latest methods defined for this interface
out.join(latestMethodForBaseName.begin(), latestMethodForBaseName.end(), "\n",
[&](const std::pair<std::string, NodeWithVersion<Method>>& methodPair) {
const Method* method = methodPair.second.node;
const std::string& baseName = methodPair.first;
std::vector<NamedReference<Type>*> results;
std::vector<ResultTransformation> transformations;
for (NamedReference<Type>* res : method->results()) {
const std::string aidlType = getAidlType(*res->get(), interface.fqName());
if (shouldWarnStatusType(aidlType)) {
out << "// FIXME: AIDL has built-in status types. Do we need the "
"status type here?\n";
}
if (method->results().size() > 1 && shouldWarnOutParam(aidlType)) {
out << "// FIXME: AIDL does not allow " << aidlType
<< " to be an out parameter.\n";
out << "// Move it to return, or add it to a Parcelable.\n";
}
results.push_back(res);
}
if (method->name() != baseName) {
out << "// Changing method name from " << method->name() << " to "
<< baseName << "\n";
}
std::string returnType = "void";
if (results.size() == 1) {
returnType = getAidlType(*results[0]->get(), interface.fqName());
out << "// Adding return type to method instead of out param "
<< returnType << " " << results[0]->name()
<< " since there is only one return value.\n";
transformations.emplace_back(ResultTransformation{
results[0]->name(), ResultTransformation::TransformType::MOVED});
results.clear();
}
if (method->getDocComment() != nullptr) {
std::vector<std::string> modifiedDocComment;
for (const std::string& line : method->getDocComment()->lines()) {
std::vector<std::string> tokens = base::Split(line, " ");
if (tokens.size() <= 1 || tokens[0] != "@return") {
// unimportant line
modifiedDocComment.emplace_back(line);
continue;
}
const std::string& res = tokens[1];
bool transformed = false;
for (const ResultTransformation& transform : transformations) {
if (transform.resultName != res) continue;
// Some transform was done to it
if (transform.type == ResultTransformation::TransformType::MOVED) {
// remove the name
tokens.erase(++tokens.begin());
transformed = true;
} else {
CHECK(transform.type ==
ResultTransformation::TransformType::REMOVED);
tokens.insert(tokens.begin(),
"FIXME: The following return was removed\n");
transformed = true;
}
}
if (!transformed) {
tokens.erase(tokens.begin());
tokens.insert(tokens.begin(), "@param out");
}
modifiedDocComment.emplace_back(base::Join(tokens, " "));
}
DocComment(modifiedDocComment, HIDL_LOCATION_HERE).emit(out);
}
WrappedOutput wrappedOutput(MAX_LINE_LENGTH);
if (method->isOneway()) wrappedOutput << "oneway ";
wrappedOutput << returnType << " " << baseName << "(";
if (results.empty()) {
emitAidlMethodParams(&wrappedOutput, method->args(), /* prefix */ "in ",
/* attachToLast */ ");\n", interface);
} else {
const bool emitArgs = !method->args().empty();
if (emitArgs) {
emitAidlMethodParams(&wrappedOutput, method->args(),
/* prefix */ "in ",
/* attachToLast */ ",", interface);
}
wrappedOutput.group([&] {
if (emitArgs) wrappedOutput.printUnlessWrapped(" ");
emitAidlMethodParams(&wrappedOutput, results, /* prefix */ "out ",
/* attachToLast */ ");\n", interface);
});
}
out << wrappedOutput;
});
});
}
} // namespace android