// Copyright 2014 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos-dbus-bindings/proxy_generator.h" #include <utility> #include <base/files/file_path.h> #include <base/format_macros.h> #include <base/logging.h> #include <base/strings/stringprintf.h> #include <brillo/strings/string_utils.h> #include "chromeos-dbus-bindings/dbus_signature.h" #include "chromeos-dbus-bindings/indented_text.h" #include "chromeos-dbus-bindings/name_parser.h" using base::StringPrintf; using std::pair; using std::string; using std::vector; namespace chromeos_dbus_bindings { namespace { // Helper struct to encapsulate information about method call parameter during // code generation. struct ParamDef { ParamDef(const string& param_type, const string& param_name, bool param_ref) : type(param_type), name(param_name), is_const_ref(param_ref) {} string type; string name; bool is_const_ref; }; string GetParamString(const ParamDef& param_def) { return StringPrintf(param_def.is_const_ref ? "const %s& %s" : "%s* %s", param_def.type.c_str(), param_def.name.c_str()); } } // anonymous namespace // static bool ProxyGenerator::GenerateProxies( const ServiceConfig& config, const std::vector<Interface>& interfaces, const base::FilePath& output_file) { IndentedText text; text.AddLine("// Automatic generation of D-Bus interfaces:"); for (const auto& interface : interfaces) { text.AddLine(StringPrintf("// - %s", interface.name.c_str())); } string header_guard = GenerateHeaderGuard(output_file); text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str())); text.AddLine(StringPrintf("#define %s", header_guard.c_str())); text.AddLine("#include <memory>"); text.AddLine("#include <string>"); text.AddLine("#include <vector>"); text.AddBlankLine(); text.AddLine("#include <base/bind.h>"); text.AddLine("#include <base/callback.h>"); text.AddLine("#include <base/logging.h>"); text.AddLine("#include <base/macros.h>"); text.AddLine("#include <base/memory/ref_counted.h>"); text.AddLine("#include <brillo/any.h>"); text.AddLine("#include <brillo/dbus/dbus_method_invoker.h>"); text.AddLine("#include <brillo/dbus/dbus_property.h>"); text.AddLine("#include <brillo/dbus/dbus_signal_handler.h>"); text.AddLine("#include <brillo/errors/error.h>"); text.AddLine("#include <brillo/variant_dictionary.h>"); text.AddLine("#include <dbus/bus.h>"); text.AddLine("#include <dbus/message.h>"); text.AddLine("#include <dbus/object_manager.h>"); text.AddLine("#include <dbus/object_path.h>"); text.AddLine("#include <dbus/object_proxy.h>"); text.AddBlankLine(); if (!config.object_manager.name.empty()) { // Add forward-declaration for Object Manager proxy class. NameParser parser{config.object_manager.name}; parser.AddOpenNamespaces(&text, false); text.AddLine(StringPrintf("class %s;", parser.MakeProxyName(false).c_str())); parser.AddCloseNamespaces(&text, false); text.AddBlankLine(); } for (const auto& interface : interfaces) { GenerateInterfaceProxyInterface(config, interface, &text); GenerateInterfaceProxy(config, interface, &text); } ObjectManager::GenerateProxy(config, interfaces, &text); text.AddLine(StringPrintf("#endif // %s", header_guard.c_str())); return WriteTextToFile(output_file, text); } // static bool ProxyGenerator::GenerateMocks(const ServiceConfig& config, const std::vector<Interface>& interfaces, const base::FilePath& mock_file, const base::FilePath& proxy_file, bool use_literal_proxy_file) { IndentedText text; text.AddLine("// Automatic generation of D-Bus interface mock proxies for:"); for (const auto& interface : interfaces) { text.AddLine(StringPrintf("// - %s", interface.name.c_str())); } string header_guard = GenerateHeaderGuard(mock_file); text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str())); text.AddLine(StringPrintf("#define %s", header_guard.c_str())); text.AddLine("#include <string>"); text.AddLine("#include <vector>"); text.AddBlankLine(); text.AddLine("#include <base/callback_forward.h>"); text.AddLine("#include <base/logging.h>"); text.AddLine("#include <base/macros.h>"); text.AddLine("#include <brillo/any.h>"); text.AddLine("#include <brillo/errors/error.h>"); text.AddLine("#include <brillo/variant_dictionary.h>"); text.AddLine("#include <gmock/gmock.h>"); text.AddBlankLine(); if (!proxy_file.empty()) { // If we have a proxy header file, it would have the proxy interfaces we // need to base our mocks on, so we need to include that header file. base::FilePath relative_path; if (use_literal_proxy_file) { relative_path = proxy_file; } else { // Generate a relative path from |mock_file| to |proxy_file|. // First, get the path components for both source and destination paths. std::vector<base::FilePath::StringType> src_components; mock_file.DirName().GetComponents(&src_components); std::vector<base::FilePath::StringType> dest_components; proxy_file.DirName().GetComponents(&dest_components); // Find the common root. // I wish we had C++14 and its 4-parameter version of std::mismatch()... auto src_end = src_components.end(); if (src_components.size() > dest_components.size()) src_end = src_components.begin() + dest_components.size(); auto mismatch_pair = std::mismatch(src_components.begin(), src_end, dest_components.begin()); // For each remaining components in the |src_components|, generate the // parent directory references (".."). size_t src_count = std::distance(mismatch_pair.first, src_components.end()); std::vector<base::FilePath::StringType> components{ src_count, base::FilePath::kParentDirectory}; // Append the remaining components from |dest_components|. components.insert(components.end(), mismatch_pair.second, dest_components.end()); // Finally, add the base name of the target file name. components.push_back(proxy_file.BaseName().value()); // Now reconstruct the relative path. relative_path = base::FilePath{base::FilePath::kCurrentDirectory}; for (const auto& component : components) relative_path = relative_path.Append(component); } text.AddLine(StringPrintf("#include \"%s\"", relative_path.value().c_str())); text.AddBlankLine(); } for (const auto& interface : interfaces) { // If we have no proxy file, we need the abstract interfaces generated here. if (proxy_file.empty()) GenerateInterfaceProxyInterface(config, interface, &text); GenerateInterfaceMock(config, interface, &text); } text.AddLine(StringPrintf("#endif // %s", header_guard.c_str())); return WriteTextToFile(mock_file, text); } // static void ProxyGenerator::GenerateInterfaceProxyInterface( const ServiceConfig& config, const Interface& interface, IndentedText* text) { NameParser parser{interface.name}; string proxy_name = parser.MakeProxyName(false); string base_interface_name = proxy_name + "Interface"; parser.AddOpenNamespaces(text, false); text->AddBlankLine(); text->AddLine(StringPrintf("// Abstract interface proxy for %s.", parser.MakeFullCppName().c_str())); text->AddComments(interface.doc_string); text->AddLine(StringPrintf("class %s {", base_interface_name.c_str())); text->AddLineWithOffset("public:", kScopeOffset); text->PushOffset(kBlockOffset); text->AddLine( StringPrintf("virtual ~%s() = default;", base_interface_name.c_str())); for (const auto& method : interface.methods) { AddMethodProxy(method, interface.name, true, text); AddAsyncMethodProxy(method, interface.name, true, text); } for (const auto& signal : interface.signals) { AddSignalHandlerRegistration(signal, interface.name, true, text); } AddProperties(config, interface, true, text); text->AddBlankLine(); text->AddLine("virtual const dbus::ObjectPath& GetObjectPath() const = 0;"); if (!config.object_manager.name.empty() && !interface.properties.empty()) AddPropertyPublicMethods(proxy_name, true, text); text->PopOffset(); text->AddLine("};"); text->AddBlankLine(); parser.AddCloseNamespaces(text, false); text->AddBlankLine(); } // static void ProxyGenerator::GenerateInterfaceProxy(const ServiceConfig& config, const Interface& interface, IndentedText* text) { NameParser parser{interface.name}; string proxy_name = parser.MakeProxyName(false); string base_interface_name = proxy_name + "Interface"; parser.AddOpenNamespaces(text, false); text->AddBlankLine(); text->AddLine(StringPrintf("// Interface proxy for %s.", parser.MakeFullCppName().c_str())); text->AddComments(interface.doc_string); text->AddLine(StringPrintf("class %s final : public %s {", proxy_name.c_str(), base_interface_name.c_str())); text->AddLineWithOffset("public:", kScopeOffset); text->PushOffset(kBlockOffset); AddPropertySet(config, interface, text); AddConstructor(config, interface, proxy_name, text); AddDestructor(proxy_name, text); for (const auto& signal : interface.signals) { AddSignalHandlerRegistration(signal, interface.name, false, text); } AddReleaseObjectProxy(text); AddGetObjectPath(text); AddGetObjectProxy(text); if (!config.object_manager.name.empty() && !interface.properties.empty()) AddPropertyPublicMethods(proxy_name, false, text); for (const auto& method : interface.methods) { AddMethodProxy(method, interface.name, false, text); AddAsyncMethodProxy(method, interface.name, false, text); } AddProperties(config, interface, false, text); text->PopOffset(); text->AddBlankLine(); text->AddLineWithOffset("private:", kScopeOffset); text->PushOffset(kBlockOffset); if (!config.object_manager.name.empty() && !interface.properties.empty()) AddOnPropertyChanged(text); text->AddLine("scoped_refptr<dbus::Bus> bus_;"); if (config.service_name.empty()) { text->AddLine("std::string service_name_;"); } else { text->AddLine(StringPrintf("const std::string service_name_{\"%s\"};", config.service_name.c_str())); } if (interface.path.empty()) { text->AddLine("dbus::ObjectPath object_path_;"); } else { text->AddLine(StringPrintf("const dbus::ObjectPath object_path_{\"%s\"};", interface.path.c_str())); } if (!config.object_manager.name.empty() && !interface.properties.empty()) { text->AddLine("PropertySet* property_set_;"); text->AddLine( StringPrintf("base::Callback<void(%sInterface*, const std::string&)> " "on_property_changed_;", proxy_name.c_str())); } text->AddLine("dbus::ObjectProxy* dbus_object_proxy_;"); text->AddBlankLine(); if (!config.object_manager.name.empty() && !interface.properties.empty()) { text->AddLine(StringPrintf( "friend class %s;", NameParser{config.object_manager.name}.MakeProxyName(true).c_str())); } text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", proxy_name.c_str())); text->PopOffset(); text->AddLine("};"); text->AddBlankLine(); parser.AddCloseNamespaces(text, false); text->AddBlankLine(); } // static void ProxyGenerator::GenerateInterfaceMock(const ServiceConfig& config, const Interface& interface, IndentedText* text) { NameParser parser{interface.name}; string proxy_name = parser.MakeProxyName(false); string base_interface_name = proxy_name + "Interface"; string mock_name = proxy_name + "Mock"; parser.AddOpenNamespaces(text, false); text->AddBlankLine(); text->AddLine(StringPrintf("// Mock object for %s.", base_interface_name.c_str())); text->AddLine(StringPrintf("class %s : public %s {", mock_name.c_str(), base_interface_name.c_str())); text->AddLineWithOffset("public:", kScopeOffset); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("%s() = default;", mock_name.c_str())); text->AddBlankLine(); for (const auto& method : interface.methods) { AddMethodMock(method, interface.name, text); AddAsyncMethodMock(method, interface.name, text); } for (const auto& signal : interface.signals) { AddSignalHandlerRegistrationMock(signal, text); } DbusSignature signature; for (const auto& prop : interface.properties) { string type; CHECK(signature.Parse(prop.type, &type)); MakeConstReferenceIfNeeded(&type); string name = NameParser{prop.name}.MakeVariableName(); text->AddLine(StringPrintf("MOCK_CONST_METHOD0(%s, %s());", name.c_str(), type.c_str())); if (prop.access == "readwrite") { text->AddLine(StringPrintf("MOCK_METHOD2(set_%s, void(%s, " "const base::Callback<bool>&));", name.c_str(), type.c_str())); } } text->AddLine( "MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&());"); if (!config.object_manager.name.empty() && !interface.properties.empty()) { text->AddLineAndPushOffsetTo( "MOCK_METHOD1(SetPropertyChangedCallback,", 1, '('); text->AddLine(StringPrintf( "void(const base::Callback<void(%sInterface*, const std::string&)>&));", proxy_name.c_str())); text->PopOffset(); } text->PopOffset(); text->AddBlankLine(); text->AddLineWithOffset("private:", kScopeOffset); text->AddLineWithOffset(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", mock_name.c_str()), kBlockOffset); text->AddLine("};"); parser.AddCloseNamespaces(text, false); text->AddBlankLine(); } // static void ProxyGenerator::AddConstructor(const ServiceConfig& config, const Interface& interface, const string& class_name, IndentedText* text) { IndentedText block; vector<ParamDef> args{{"scoped_refptr<dbus::Bus>", "bus", true}}; if (config.service_name.empty()) args.emplace_back("std::string", "service_name", true); if (interface.path.empty()) args.emplace_back("dbus::ObjectPath", "object_path", true); if (!config.object_manager.name.empty() && !interface.properties.empty()) args.emplace_back("PropertySet", "property_set", false); if (args.size() == 1) { block.AddLine(StringPrintf("%s(%s) :", class_name.c_str(), GetParamString(args.front()).c_str())); } else { block.AddLine(StringPrintf("%s(", class_name.c_str())); block.PushOffset(kLineContinuationOffset); for (size_t i = 0; i < args.size() - 1; i++) { block.AddLine(StringPrintf("%s,", GetParamString(args[i]).c_str())); } block.AddLine(StringPrintf("%s) :", GetParamString(args.back()).c_str())); } block.PushOffset(kLineContinuationOffset); for (const auto& arg : args) { block.AddLine(StringPrintf("%s_{%s},", arg.name.c_str(), arg.name.c_str())); } block.AddLine("dbus_object_proxy_{"); block.AddLineWithOffset( "bus_->GetObjectProxy(service_name_, object_path_)} {", kLineContinuationOffset); block.PopOffset(); if (args.size() > 1) block.PopOffset(); block.AddLine("}"); block.AddBlankLine(); text->AddBlock(block); } // static void ProxyGenerator::AddDestructor(const string& class_name, IndentedText* text) { IndentedText block; block.AddLine(StringPrintf("~%s() override {", class_name.c_str())); block.AddLine("}"); text->AddBlock(block); } // static void ProxyGenerator::AddReleaseObjectProxy(IndentedText* text) { text->AddBlankLine(); text->AddLine("void ReleaseObjectProxy(const base::Closure& callback) {"); text->AddLineWithOffset( "bus_->RemoveObjectProxy(service_name_, object_path_, callback);", kBlockOffset); text->AddLine("}"); } // static void ProxyGenerator::AddGetObjectPath(IndentedText* text) { text->AddBlankLine(); text->AddLine("const dbus::ObjectPath& GetObjectPath() const override {"); text->AddLineWithOffset("return object_path_;", kBlockOffset); text->AddLine("}"); } // static void ProxyGenerator::AddGetObjectProxy(IndentedText* text) { text->AddBlankLine(); text->AddLine("dbus::ObjectProxy* GetObjectProxy() const { " "return dbus_object_proxy_; }"); } // static void ProxyGenerator::AddPropertyPublicMethods(const string& class_name, bool declaration_only, IndentedText* text) { text->AddBlankLine(); text->AddLine(StringPrintf("%svoid SetPropertyChangedCallback(", declaration_only ? "virtual " : "")); text->AddLineWithOffset( StringPrintf("const base::Callback<void(%sInterface*, " "const std::string&)>& callback) %s", class_name.c_str(), declaration_only ? "= 0;" : "override {"), kLineContinuationOffset); if (!declaration_only) { text->AddLineWithOffset("on_property_changed_ = callback;", kBlockOffset); text->AddLine("}"); text->AddBlankLine(); text->AddLine( "const PropertySet* GetProperties() const { return property_set_; }"); text->AddLine("PropertySet* GetProperties() { return property_set_; }"); } } // static void ProxyGenerator::AddOnPropertyChanged(IndentedText* text) { text->AddLine("void OnPropertyChanged(const std::string& property_name) {"); text->PushOffset(kBlockOffset); text->AddLine("if (!on_property_changed_.is_null())"); text->PushOffset(kBlockOffset); text->AddLine("on_property_changed_.Run(this, property_name);"); text->PopOffset(); text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::AddSignalHandlerRegistration( const Interface::Signal& signal, const string& interface_name, bool declaration_only, IndentedText* text) { IndentedText block; block.AddBlankLine(); block.AddLine(StringPrintf("%svoid Register%sSignalHandler(", declaration_only ? "virtual " : "", signal.name.c_str())); block.PushOffset(kLineContinuationOffset); AddSignalCallbackArg(signal, false, &block); block.AddLine(StringPrintf( "dbus::ObjectProxy::OnConnectedCallback on_connected_callback)%s", declaration_only ? " = 0;" : " override {")); if (!declaration_only) { block.PopOffset(); // Method signature arguments block.PushOffset(kBlockOffset); block.AddLine("brillo::dbus_utils::ConnectToSignal("); block.PushOffset(kLineContinuationOffset); block.AddLine("dbus_object_proxy_,"); block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); block.AddLine(StringPrintf("\"%s\",", signal.name.c_str())); block.AddLine("signal_callback,"); block.AddLine("on_connected_callback);"); block.PopOffset(); // Function call line continuation block.PopOffset(); // Method body block.AddLine("}"); } text->AddBlock(block); } // static void ProxyGenerator::AddPropertySet(const ServiceConfig& config, const Interface& interface, IndentedText* text) { // Must have ObjectManager in order for property system to work correctly. if (config.object_manager.name.empty()) return; IndentedText block; block.AddLine("class PropertySet : public dbus::PropertySet {"); block.AddLineWithOffset("public:", kScopeOffset); block.PushOffset(kBlockOffset); block.AddLineAndPushOffsetTo("PropertySet(dbus::ObjectProxy* object_proxy,", 1, '('); block.AddLine("const PropertyChangedCallback& callback)"); block.PopOffset(); block.PushOffset(kLineContinuationOffset); block.AddLineAndPushOffsetTo(": dbus::PropertySet{object_proxy,", 1, '{'); block.AddLine(StringPrintf("\"%s\",", interface.name.c_str())); block.AddLine("callback} {"); block.PopOffset(); block.PopOffset(); block.PushOffset(kBlockOffset); for (const auto& prop : interface.properties) { block.AddLine( StringPrintf("RegisterProperty(%sName(), &%s);", prop.name.c_str(), NameParser{prop.name}.MakeVariableName().c_str())); } block.PopOffset(); block.AddLine("}"); block.AddBlankLine(); DbusSignature signature; for (const auto& prop : interface.properties) { string type; CHECK(signature.Parse(prop.type, &type)); block.AddLine( StringPrintf("brillo::dbus_utils::Property<%s> %s;", type.c_str(), NameParser{prop.name}.MakeVariableName().c_str())); } block.AddBlankLine(); block.PopOffset(); block.AddLineWithOffset("private:", kScopeOffset); block.AddLineWithOffset("DISALLOW_COPY_AND_ASSIGN(PropertySet);", kBlockOffset); block.AddLine("};"); block.AddBlankLine(); text->AddBlock(block); } // static void ProxyGenerator::AddProperties(const ServiceConfig& config, const Interface& interface, bool declaration_only, IndentedText* text) { // Must have ObjectManager in order for property system to work correctly. if (config.object_manager.name.empty()) return; if (declaration_only && !interface.properties.empty()) text->AddBlankLine(); DbusSignature signature; for (const auto& prop : interface.properties) { if (declaration_only) { text->AddLine( StringPrintf("static const char* %sName() { return \"%s\"; }", prop.name.c_str(), prop.name.c_str())); } string type; CHECK(signature.Parse(prop.type, &type)); MakeConstReferenceIfNeeded(&type); string name = NameParser{prop.name}.MakeVariableName(); if (!declaration_only) text->AddBlankLine(); text->AddLine( StringPrintf("%s%s %s() const%s", declaration_only ? "virtual " : "", type.c_str(), name.c_str(), declaration_only ? " = 0;" : " override {")); if (!declaration_only) { text->AddLineWithOffset( StringPrintf("return property_set_->%s.value();", name.c_str()), kBlockOffset); text->AddLine("}"); } if (prop.access == "readwrite") { if (!declaration_only) text->AddBlankLine(); text->AddLineAndPushOffsetTo( StringPrintf("%svoid set_%s(%s value,", declaration_only ? "virtual " : "", name.c_str(), type.c_str()), 1, '('); text->AddLine( StringPrintf("const base::Callback<void(bool)>& callback)%s", declaration_only ? " = 0;" : " override {")); text->PopOffset(); if (!declaration_only) { text->AddLineWithOffset( StringPrintf("property_set_->%s.Set(value, callback);", name.c_str()), kBlockOffset); text->AddLine("}"); } } } } // static void ProxyGenerator::AddMethodProxy(const Interface::Method& method, const string& interface_name, bool declaration_only, IndentedText* text) { IndentedText block; DbusSignature signature; block.AddBlankLine(); block.AddComments(method.doc_string); block.AddLine(StringPrintf("%sbool %s(", declaration_only ? "virtual " : "", method.name.c_str())); block.PushOffset(kLineContinuationOffset); vector<string> argument_names; int argument_number = 0; for (const auto& argument : method.input_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); string argument_name = GetArgName("in", argument.name, ++argument_number); argument_names.push_back(argument_name); block.AddLine(StringPrintf( "%s %s,", argument_type.c_str(), argument_name.c_str())); } vector<string> out_param_names{"response.get()", "error"}; for (const auto& argument : method.output_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); string argument_name = GetArgName("out", argument.name, ++argument_number); out_param_names.push_back(argument_name); block.AddLine(StringPrintf( "%s* %s,", argument_type.c_str(), argument_name.c_str())); } block.AddLine("brillo::ErrorPtr* error,"); block.AddLine( StringPrintf("int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)%s", declaration_only ? " = 0;" : " override {")); block.PopOffset(); if (!declaration_only) { block.PushOffset(kBlockOffset); block.AddLine( "auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout("); block.PushOffset(kLineContinuationOffset); block.AddLine("timeout_ms,"); block.AddLine("dbus_object_proxy_,"); block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); block.AddLine(StringPrintf("\"%s\",", method.name.c_str())); string last_argument = "error"; for (const auto& argument_name : argument_names) { block.AddLine(StringPrintf("%s,", last_argument.c_str())); last_argument = argument_name; } block.AddLine(StringPrintf("%s);", last_argument.c_str())); block.PopOffset(); block.AddLine("return response && " "brillo::dbus_utils::ExtractMethodCallResults("); block.PushOffset(kLineContinuationOffset); block.AddLine(brillo::string_utils::Join(", ", out_param_names) + ");"); block.PopOffset(); block.PopOffset(); block.AddLine("}"); } text->AddBlock(block); } // static void ProxyGenerator::AddAsyncMethodProxy(const Interface::Method& method, const string& interface_name, bool declaration_only, IndentedText* text) { IndentedText block; DbusSignature signature; block.AddBlankLine(); block.AddComments(method.doc_string); block.AddLine(StringPrintf("%svoid %sAsync(", declaration_only ? "virtual " : "", method.name.c_str())); block.PushOffset(kLineContinuationOffset); vector<string> argument_names; int argument_number = 0; for (const auto& argument : method.input_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); string argument_name = GetArgName("in", argument.name, ++argument_number); argument_names.push_back(argument_name); block.AddLine(StringPrintf( "%s %s,", argument_type.c_str(), argument_name.c_str())); } vector<string> out_params; for (const auto& argument : method.output_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); if (!argument.name.empty()) base::StringAppendF(&argument_type, " /*%s*/", argument.name.c_str()); out_params.push_back(argument_type); } block.AddLine(StringPrintf( "const base::Callback<void(%s)>& success_callback,", brillo::string_utils::Join(", ", out_params).c_str())); block.AddLine( "const base::Callback<void(brillo::Error*)>& error_callback,"); block.AddLine( StringPrintf("int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)%s", declaration_only ? " = 0;" : " override {")); block.PopOffset(); if (!declaration_only) { block.PushOffset(kBlockOffset); block.AddLine("brillo::dbus_utils::CallMethodWithTimeout("); block.PushOffset(kLineContinuationOffset); block.AddLine("timeout_ms,"); block.AddLine("dbus_object_proxy_,"); block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); block.AddLine(StringPrintf("\"%s\",", method.name.c_str())); block.AddLine("success_callback,"); string last_argument = "error_callback"; for (const auto& argument_name : argument_names) { block.AddLine(StringPrintf("%s,", last_argument.c_str())); last_argument = argument_name; } block.AddLine(StringPrintf("%s);", last_argument.c_str())); block.PopOffset(); block.PopOffset(); block.AddLine("}"); } text->AddBlock(block); } // static void ProxyGenerator::AddMethodMock(const Interface::Method& method, const string& /* interface_name */, IndentedText* text) { DbusSignature signature; vector<string> arguments; for (const auto& argument : method.input_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); if (!argument.name.empty()) base::StringAppendF(&argument_type, " /*in_%s*/", argument.name.c_str()); arguments.push_back(argument_type); } for (const auto& argument : method.output_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); argument_type += '*'; if (!argument.name.empty()) base::StringAppendF(&argument_type, " /*out_%s*/", argument.name.c_str()); arguments.push_back(argument_type); } arguments.push_back("brillo::ErrorPtr* /*error*/"); arguments.push_back("int /*timeout_ms*/"); AddMockMethodDeclaration(method.name, "bool", arguments, text); } // static void ProxyGenerator::AddAsyncMethodMock(const Interface::Method& method, const string& /* interface_name */, IndentedText* text) { DbusSignature signature; vector<string> arguments; for (const auto& argument : method.input_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); if (!argument.name.empty()) base::StringAppendF(&argument_type, " /*in_%s*/", argument.name.c_str()); arguments.push_back(argument_type); } vector<string> out_params; for (const auto& argument : method.output_arguments) { string argument_type; CHECK(signature.Parse(argument.type, &argument_type)); MakeConstReferenceIfNeeded(&argument_type); if (!argument.name.empty()) base::StringAppendF(&argument_type, " /*%s*/", argument.name.c_str()); out_params.push_back(argument_type); } arguments.push_back(StringPrintf( "const base::Callback<void(%s)>& /*success_callback*/", brillo::string_utils::Join(", ", out_params).c_str())); arguments.push_back( "const base::Callback<void(brillo::Error*)>& /*error_callback*/"); arguments.push_back("int /*timeout_ms*/"); AddMockMethodDeclaration(method.name + "Async", "void", arguments, text); } void ProxyGenerator::AddMockMethodDeclaration(const string& method_name, const string& return_type, const vector<string>& arguments, IndentedText* text) { IndentedText block; // GMOCK doesn't go all the way up to 11, so we need to handle methods with // 11 arguments or more in a different way. if (arguments.size() >= 11) { block.AddLineAndPushOffsetTo( StringPrintf("%s %s(%s,", return_type.c_str(), method_name.c_str(), arguments.front().c_str()), 1, '('); for (size_t i = 1; i < arguments.size() - 1; i++) block.AddLine(StringPrintf("%s,", arguments[i].c_str())); block.AddLine(StringPrintf("%s) override {", arguments.back().c_str())); block.PopOffset(); block.PushOffset(kBlockOffset); block.AddLine(StringPrintf( "LOG(WARNING) << \"%s(): gmock can't handle methods with %" PRIuS " arguments. You can override this method in a subclass if you need" " to.\";", method_name.c_str(), arguments.size())); if (return_type == "void") { // No return added here. } else if (return_type == "bool") { block.AddLine("return false;"); } else { LOG(FATAL) << "The return type is not supported."; } block.PopOffset(); block.AddLine("}"); } else { block.AddLineAndPushOffsetTo( StringPrintf("MOCK_METHOD%zu(%s,", arguments.size(), method_name.c_str()), 1, '('); block.AddLineAndPushOffsetTo( StringPrintf("%s(%s,", return_type.c_str(), arguments.front().c_str()), 1, '('); for (size_t i = 1; i < arguments.size() - 1; i++) block.AddLine(StringPrintf("%s,", arguments[i].c_str())); block.AddLine(StringPrintf("%s));", arguments.back().c_str())); block.PopOffset(); block.PopOffset(); } text->AddBlock(block); } // static void ProxyGenerator::AddSignalHandlerRegistrationMock( const Interface::Signal& signal, IndentedText* text) { IndentedText callback_arg_text; AddSignalCallbackArg(signal, true, &callback_arg_text); vector<string> arg_lines = callback_arg_text.GetLines(); IndentedText block; block.AddLineAndPushOffsetTo( StringPrintf("MOCK_METHOD2(Register%sSignalHandler,", signal.name.c_str()), 1, '('); for (size_t i = 0; i < arg_lines.size(); ++i) { if (i == 0) block.AddLineAndPushOffsetTo("void(" + arg_lines[i], 1, '('); else block.AddLine(arg_lines[i]); } block.AddLine( "dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));"); text->AddBlock(block); } // static void ProxyGenerator::AddSignalCallbackArg(const Interface::Signal& signal, bool comment_arg_name, IndentedText* block) { DbusSignature signature; string signal_callback = StringPrintf("%ssignal_callback%s", comment_arg_name ? "/*" : "", comment_arg_name ? "*/" : ""); if (signal.arguments.empty()) { block->AddLine(StringPrintf("const base::Closure& %s,", signal_callback.c_str())); } else { string last_argument; string prefix{"const base::Callback<void("}; for (const auto argument : signal.arguments) { if (!last_argument.empty()) { if (!prefix.empty()) { block->AddLineAndPushOffsetTo( StringPrintf("%s%s,", prefix.c_str(), last_argument.c_str()), 1, '('); prefix.clear(); } else { block->AddLine(StringPrintf("%s,", last_argument.c_str())); } } CHECK(signature.Parse(argument.type, &last_argument)); MakeConstReferenceIfNeeded(&last_argument); } block->AddLine(StringPrintf("%s%s)>& %s,", prefix.c_str(), last_argument.c_str(), signal_callback.c_str())); if (prefix.empty()) { block->PopOffset(); } } } // static void ProxyGenerator::ObjectManager::GenerateProxy( const ServiceConfig& config, const std::vector<Interface>& interfaces, IndentedText* text) { if (config.object_manager.name.empty()) return; NameParser object_manager{config.object_manager.name}; object_manager.AddOpenNamespaces(text, false); text->AddBlankLine(); string class_name = object_manager.type_name + "Proxy"; text->AddLine(StringPrintf("class %s : " "public dbus::ObjectManager::Interface {", class_name.c_str())); text->AddLineWithOffset("public:", kScopeOffset); text->PushOffset(kBlockOffset); AddConstructor(config, class_name, interfaces, text); AddDestructor(class_name, interfaces, text); AddGetObjectManagerProxy(text); for (const auto& itf : interfaces) { AddInterfaceAccessors(itf, text); } text->PopOffset(); text->AddLineWithOffset("private:", kScopeOffset); text->PushOffset(kBlockOffset); AddOnPropertyChanged(interfaces, text); AddObjectAdded(config, interfaces, text); AddObjectRemoved(interfaces, text); AddCreateProperties(interfaces, class_name, text); AddDataMembers(config, interfaces, class_name, text); text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", class_name.c_str())); text->PopOffset(); text->AddLine("};"); text->AddBlankLine(); object_manager.AddCloseNamespaces(text, false); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddConstructor( const ServiceConfig& config, const std::string& class_name, const std::vector<Interface>& interfaces, IndentedText* text) { if (config.service_name.empty()) { text->AddLineAndPushOffsetTo( StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus,", class_name.c_str()), 1, '('); text->AddLine("const std::string& service_name)"); text->PopOffset(); } else { text->AddLine(StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus)", class_name.c_str())); } text->PushOffset(kLineContinuationOffset); text->AddLine(": bus_{bus},"); text->PushOffset(kBlockOffset); if (config.service_name.empty()) { text->AddLine("service_name_{service_name},"); } text->AddLine("dbus_object_manager_{bus->GetObjectManager("); text->PushOffset(kLineContinuationOffset); if (config.service_name.empty()) { text->AddLine("service_name,"); } else { text->AddLine(StringPrintf("\"%s\",", config.service_name.c_str())); } text->AddLine(StringPrintf("dbus::ObjectPath{\"%s\"})} {", config.object_manager.object_path.c_str())); text->PopOffset(); text->PopOffset(); text->PopOffset(); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { text->AddLine( StringPrintf("dbus_object_manager_->RegisterInterface(\"%s\", this);", itf.name.c_str())); } text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddDestructor( const std::string& class_name, const std::vector<Interface>& interfaces, IndentedText* text) { text->AddLine(StringPrintf("~%s() override {", class_name.c_str())); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { text->AddLine( StringPrintf("dbus_object_manager_->UnregisterInterface(\"%s\");", itf.name.c_str())); } text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddGetObjectManagerProxy( IndentedText* text) { text->AddLine("dbus::ObjectManager* GetObjectManagerProxy() const {"); text->AddLineWithOffset("return dbus_object_manager_;", kBlockOffset); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddInterfaceAccessors( const Interface& interface, IndentedText* text) { NameParser itf_name{interface.name}; string map_name = itf_name.MakeVariableName() + "_instances_"; // GetProxy(). if (interface.path.empty()) { // We have no fixed path, so there could be multiple instances of this itf. text->AddLine(StringPrintf("%sInterface* Get%s(", itf_name.MakeProxyName(true).c_str(), itf_name.MakeProxyName(false).c_str())); text->PushOffset(kLineContinuationOffset); text->AddLine("const dbus::ObjectPath& object_path) {"); text->PopOffset(); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("auto p = %s.find(object_path);", map_name.c_str())); text->AddLine(StringPrintf("if (p != %s.end())", map_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine("return p->second.get();"); text->PopOffset(); text->AddLine("return nullptr;"); text->PopOffset(); text->AddLine("}"); } else { // We have a fixed path, so the object could be considered a "singleton". // Skip the object_path parameter and return the first available instance. text->AddLine(StringPrintf("%sInterface* Get%s() {", itf_name.MakeProxyName(true).c_str(), itf_name.MakeProxyName(false).c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("if (%s.empty())", map_name.c_str())); text->AddLineWithOffset("return nullptr;", kBlockOffset); text->AddLine(StringPrintf("return %s.begin()->second.get();", map_name.c_str())); text->PopOffset(); text->AddLine("}"); } // GetInstances(). text->AddLine( StringPrintf("std::vector<%sInterface*> Get%sInstances() const {", itf_name.MakeProxyName(true).c_str(), itf_name.type_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("std::vector<%sInterface*> values;", itf_name.MakeProxyName(true).c_str())); text->AddLine(StringPrintf("values.reserve(%s.size());", map_name.c_str())); text->AddLine(StringPrintf("for (const auto& pair : %s)", map_name.c_str())); text->AddLineWithOffset("values.push_back(pair.second.get());", kBlockOffset); text->AddLine("return values;"); text->PopOffset(); text->AddLine("}"); // SetAddedCallback(). text->AddLine(StringPrintf("void Set%sAddedCallback(", itf_name.type_name.c_str())); text->PushOffset(kLineContinuationOffset); text->AddLine( StringPrintf("const base::Callback<void(%sInterface*)>& callback) {", itf_name.MakeProxyName(true).c_str())); text->PopOffset(); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("on_%s_added_ = callback;", itf_name.MakeVariableName().c_str())); text->PopOffset(); text->AddLine("}"); // SetRemovedCallback(). text->AddLine(StringPrintf("void Set%sRemovedCallback(", itf_name.type_name.c_str())); text->PushOffset(kLineContinuationOffset); text->AddLine("const base::Callback<void(const dbus::ObjectPath&)>& " "callback) {"); text->PopOffset(); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("on_%s_removed_ = callback;", itf_name.MakeVariableName().c_str())); text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddOnPropertyChanged( const std::vector<Interface>& interfaces, IndentedText* text) { // If there are no interfaces with properties, comment out parameter // names for OnPropertyChanged() to prevent compiler warnings on unused // function parameters. auto has_props = [](const Interface& itf) { return !itf.properties.empty(); }; auto itf_with_props = std::find_if(interfaces.begin(), interfaces.end(), has_props); if (itf_with_props == interfaces.end()) { text->AddLineAndPushOffsetTo("void OnPropertyChanged(" "const dbus::ObjectPath& /* object_path */,", 1, '('); text->AddLine("const std::string& /* interface_name */,"); text->AddLine("const std::string& /* property_name */) {}"); text->PopOffset(); text->AddBlankLine(); return; } text->AddLineAndPushOffsetTo("void OnPropertyChanged(" "const dbus::ObjectPath& object_path,", 1, '('); text->AddLine("const std::string& interface_name,"); text->AddLine("const std::string& property_name) {"); text->PopOffset(); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { if (itf.properties.empty()) continue; NameParser itf_name{itf.name}; text->AddLine(StringPrintf("if (interface_name == \"%s\") {", itf.name.c_str())); text->PushOffset(kBlockOffset); string map_name = itf_name.MakeVariableName() + "_instances_"; text->AddLine(StringPrintf("auto p = %s.find(object_path);", map_name.c_str())); text->AddLine(StringPrintf("if (p == %s.end())", map_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine("return;"); text->PopOffset(); text->AddLine("p->second->OnPropertyChanged(property_name);"); text->AddLine("return;"); text->PopOffset(); text->AddLine("}"); } text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddObjectAdded( const ServiceConfig& config, const std::vector<Interface>& interfaces, IndentedText* text) { text->AddLine("void ObjectAdded("); text->PushOffset(kLineContinuationOffset); text->AddLine("const dbus::ObjectPath& object_path,"); text->AddLine("const std::string& interface_name) override {"); text->PopOffset(); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { NameParser itf_name{itf.name}; string var_name = itf_name.MakeVariableName(); text->AddLine(StringPrintf("if (interface_name == \"%s\") {", itf.name.c_str())); text->PushOffset(kBlockOffset); if (!itf.properties.empty()) { text->AddLine("auto property_set ="); text->PushOffset(kLineContinuationOffset); text->AddLine(StringPrintf("static_cast<%s::PropertySet*>(", itf_name.MakeProxyName(true).c_str())); text->PushOffset(kLineContinuationOffset); text->AddLine("dbus_object_manager_->GetProperties(object_path, " "interface_name));"); text->PopOffset(); text->PopOffset(); } text->AddLine(StringPrintf("std::unique_ptr<%s> %s_proxy{", itf_name.MakeProxyName(true).c_str(), var_name.c_str())); text->PushOffset(kBlockOffset); string new_instance = StringPrintf("new %s{bus_", itf_name.MakeProxyName(true).c_str()); if (config.service_name.empty()) { new_instance += ", service_name_"; } if (itf.path.empty()) new_instance += ", object_path"; if (!itf.properties.empty()) new_instance += ", property_set"; new_instance += "}"; text->AddLine(new_instance); text->PopOffset(); text->AddLine("};"); text->AddLine(StringPrintf("auto p = %s_instances_.emplace(object_path, " "std::move(%s_proxy));", var_name.c_str(), var_name.c_str())); text->AddLine(StringPrintf("if (!on_%s_added_.is_null())", var_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("on_%s_added_.Run(p.first->second.get());", var_name.c_str())); text->PopOffset(); text->AddLine("return;"); text->PopOffset(); text->AddLine("}"); } text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddObjectRemoved( const std::vector<Interface>& interfaces, IndentedText* text) { text->AddLine("void ObjectRemoved("); text->PushOffset(kLineContinuationOffset); text->AddLine("const dbus::ObjectPath& object_path,"); text->AddLine("const std::string& interface_name) override {"); text->PopOffset(); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { NameParser itf_name{itf.name}; string var_name = itf_name.MakeVariableName(); text->AddLine(StringPrintf("if (interface_name == \"%s\") {", itf.name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("auto p = %s_instances_.find(object_path);", var_name.c_str())); text->AddLine(StringPrintf("if (p != %s_instances_.end()) {", var_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("if (!on_%s_removed_.is_null())", var_name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("on_%s_removed_.Run(object_path);", var_name.c_str())); text->PopOffset(); text->AddLine(StringPrintf("%s_instances_.erase(p);", var_name.c_str())); text->PopOffset(); text->AddLine("}"); text->AddLine("return;"); text->PopOffset(); text->AddLine("}"); } text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddCreateProperties( const std::vector<Interface>& interfaces, const std::string& class_name, IndentedText* text) { text->AddLine("dbus::PropertySet* CreateProperties("); text->PushOffset(kLineContinuationOffset); text->AddLine("dbus::ObjectProxy* object_proxy,"); text->AddLine("const dbus::ObjectPath& object_path,"); text->AddLine("const std::string& interface_name) override {"); text->PopOffset(); text->PushOffset(kBlockOffset); for (const auto& itf : interfaces) { NameParser itf_name{itf.name}; text->AddLine(StringPrintf("if (interface_name == \"%s\") {", itf.name.c_str())); text->PushOffset(kBlockOffset); text->AddLine(StringPrintf("return new %s::PropertySet{", itf_name.MakeProxyName(true).c_str())); text->PushOffset(kLineContinuationOffset); text->AddLine("object_proxy,"); text->AddLineAndPushOffsetTo( StringPrintf("base::Bind(&%s::OnPropertyChanged,", class_name.c_str()), 1, '('); text->AddLine("weak_ptr_factory_.GetWeakPtr(),"); text->AddLine("object_path,"); text->AddLine("interface_name)"); text->PopOffset(); text->PopOffset(); text->AddLine("};"); text->PopOffset(); text->AddLine("}"); } text->AddLineAndPushOffsetTo("LOG(FATAL) << \"Creating properties for " "unsupported interface \"", 1, ' '); text->AddLine("<< interface_name;"); text->PopOffset(); text->AddLine("return nullptr;"); text->PopOffset(); text->AddLine("}"); text->AddBlankLine(); } void ProxyGenerator::ObjectManager::AddDataMembers( const ServiceConfig& config, const std::vector<Interface>& interfaces, const std::string& class_name, IndentedText* text) { text->AddLine("scoped_refptr<dbus::Bus> bus_;"); if (config.service_name.empty()) { text->AddLine("std::string service_name_;"); } text->AddLine("dbus::ObjectManager* dbus_object_manager_;"); for (const auto& itf : interfaces) { NameParser itf_name{itf.name}; string var_name = itf_name.MakeVariableName(); text->AddLineAndPushOffsetTo("std::map<dbus::ObjectPath,", 1, '<'); text->AddLine(StringPrintf("std::unique_ptr<%s>> %s_instances_;", itf_name.MakeProxyName(true).c_str(), var_name.c_str())); text->PopOffset(); text->AddLine( StringPrintf("base::Callback<void(%sInterface*)> on_%s_added_;", itf_name.MakeProxyName(true).c_str(), var_name.c_str())); text->AddLine(StringPrintf("base::Callback<void(const dbus::ObjectPath&)> " "on_%s_removed_;", var_name.c_str())); } text->AddLine( StringPrintf("base::WeakPtrFactory<%s> weak_ptr_factory_{this};", class_name.c_str())); text->AddBlankLine(); } // static string ProxyGenerator::GetHandlerNameForSignal(const string& signal) { return StringPrintf("On%sSignal", signal.c_str()); } } // namespace chromeos_dbus_bindings