diff options
author | rockot <rockot@chromium.org> | 2015-11-12 17:33:59 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-13 01:34:47 +0000 |
commit | 85dce086001825a2faa4e75755a669f5e08a1cad (patch) | |
tree | 722de1d974f799b3d1ee1ca4c81bb8b0fa75a95d /mojo/public/cpp | |
parent | 415b73b1a400a994a86e6f29709aa0271e895dd5 (diff) | |
download | chromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.zip chromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.tar.gz chromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.tar.bz2 |
Move third_party/mojo/src/mojo/public to mojo/public
BUG=None
NOPRESUBMIT=true
Review URL: https://codereview.chromium.org/1410053006
Cr-Commit-Position: refs/heads/master@{#359461}
Diffstat (limited to 'mojo/public/cpp')
151 files changed, 20418 insertions, 0 deletions
diff --git a/mojo/public/cpp/README.md b/mojo/public/cpp/README.md new file mode 100644 index 0000000..4404c24 --- /dev/null +++ b/mojo/public/cpp/README.md @@ -0,0 +1,71 @@ +Mojo Public C++ API +=================== + +This directory contains C++ language bindings for the Mojo Public API. + +A number of subdirectories provide wrappers for the lower-level C APIs (in +subdirectories of the same name, under mojo/public/c/). Typically, these +wrappers provide increased convenience and/or type-safety. + +Other subdirectories provide support (static) libraries of various sorts. In +this case, the organization is to have the public interface for the library +defined in header files in the subdirectory itself and the implementation of the +library at a lower level, under a lib (sub)subdirectory. A developer should be +able to substitute their own implementation of any such support library, and +expect other support libraries, which may depend on that library, to work +properly. + +Bindings +-------- + +The bindings/ subdirectory contains a support (static) library needed by the +code generated by the bindings generator tool (in mojo/public/tools/bindings/), +which translates Mojo IDL (.mojom) files into idiomatic C++ (among other +languages). + +This library depends on the Environment library. + +Environment +----------- + +The environment/ subdirectory contains a support (static) library that +represents shared state needed to support the Bindings and GLES2 libraries. + +This library depends on the Utility library. + + +GLES2 +----- + +The gles2/ subdirectory contains C++ wrappers (and some additional helpers) of +the API defined in mojo/public/c/gles2/ (which provides access to GLES2). + +These wrappers depend on the Environment library. + +Shell +----- + +The shell/ subdirectory contains a support (static) library that aids in writing +Mojo applications and interacting with the Shell service. + +System +------ + +The system/ subdirectory contains C++ wrappers (and some additional helpers) of +the API defined in mojo/public/c/system/, which defines the basic, "core" API, +especially used to communicate with Mojo services. + +Test Support +------------ + +The test_support/ subdirectory contains C++ wrappers of the test-only API +defined in mojo/public/c/test_support/. It is not meant for general use by Mojo +applications. + +Utility +------- + +The utility/ subdirectory contains a support (static) library that provides +various basic functionality. Most notably, it provides an implementation of a +RunLoop based on MojoWaitMany() that applications may use as the basis for +asynchronous message processing. diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn new file mode 100644 index 0000000..89679f2 --- /dev/null +++ b/mojo/public/cpp/bindings/BUILD.gn @@ -0,0 +1,95 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../mojo_sdk.gni") + +mojo_sdk_source_set("bindings") { + sources = [ + "array.h", + "associated_interface_ptr_info.h", + "associated_interface_request.h", + "binding.h", + "interface_ptr.h", + "interface_ptr_info.h", + "interface_request.h", + "lib/array_internal.cc", + "lib/array_internal.h", + "lib/array_serialization.h", + "lib/bindings_internal.h", + "lib/bindings_serialization.cc", + "lib/bindings_serialization.h", + "lib/bounds_checker.cc", + "lib/bounds_checker.h", + "lib/buffer.h", + "lib/connector.cc", + "lib/connector.h", + "lib/control_message_handler.cc", + "lib/control_message_handler.h", + "lib/control_message_proxy.cc", + "lib/control_message_proxy.h", + "lib/filter_chain.cc", + "lib/filter_chain.h", + "lib/fixed_buffer.cc", + "lib/fixed_buffer.h", + "lib/interface_ptr_internal.h", + "lib/map_data_internal.h", + "lib/map_internal.h", + "lib/map_serialization.h", + "lib/message.cc", + "lib/message_builder.cc", + "lib/message_builder.h", + "lib/message_filter.cc", + "lib/message_header_validator.cc", + "lib/message_header_validator.h", + "lib/message_internal.h", + "lib/no_interface.cc", + "lib/router.cc", + "lib/router.h", + "lib/string_serialization.cc", + "lib/string_serialization.h", + "lib/union_accessor.h", + "lib/validate_params.h", + "lib/validation_errors.cc", + "lib/validation_errors.h", + "lib/validation_util.cc", + "lib/validation_util.h", + "lib/value_traits.h", + "map.h", + "message.h", + "message_filter.h", + "no_interface.h", + "string.h", + "strong_binding.h", + "struct_ptr.h", + "type_converter.h", + ] + + public_deps = [ + ":callback", + ] + + mojo_sdk_public_deps = [ + "mojo/public/cpp/system", + ] + + mojo_sdk_deps = [ + "mojo/public/cpp/environment", + "mojo/public/interfaces/bindings:bindings_cpp_sources", + ] +} + +mojo_sdk_source_set("callback") { + sources = [ + "callback.h", + "lib/callback_internal.h", + "lib/shared_data.h", + "lib/shared_ptr.h", + "lib/template_util.h", + "lib/thread_checker.h", + "lib/thread_checker_posix.cc", + "lib/thread_checker_posix.h", + ] + + mojo_sdk_deps = [ "mojo/public/cpp/system" ] +} diff --git a/mojo/public/cpp/bindings/array.h b/mojo/public/cpp/bindings/array.h new file mode 100644 index 0000000..f7d3921 --- /dev/null +++ b/mojo/public/cpp/bindings/array.h @@ -0,0 +1,249 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ + +#include <string.h> + +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/value_traits.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace mojo { + +// Represents a moveable array with contents of type |T|. The array can be null, +// meaning that no value has been assigned to it. Null is distinct from empty. +template <typename T> +class Array { + MOJO_MOVE_ONLY_TYPE(Array) + public: + typedef internal::ArrayTraits<T, internal::IsMoveOnlyType<T>::value> Traits; + typedef typename Traits::ConstRefType ConstRefType; + typedef typename Traits::RefType RefType; + typedef typename Traits::StorageType StorageType; + typedef typename Traits::ForwardType ForwardType; + + typedef internal::Array_Data<typename internal::WrapperTraits<T>::DataType> + Data_; + + // Constructs a new array that is null. + Array() : is_null_(true) {} + + // Constructs a new non-null array of the specified size. The elements will + // be value-initialized (meaning that they will be initialized by their + // default constructor, if any, or else zero-initialized). + explicit Array(size_t size) : vec_(size), is_null_(false) { + Traits::Initialize(&vec_); + } + ~Array() { Traits::Finalize(&vec_); } + + // Moves the contents of |other| into this array. + Array(Array&& other) : is_null_(true) { Take(&other); } + Array& operator=(Array&& other) { + Take(&other); + return *this; + } + + // Creates a non-null array of the specified size. The elements will be + // value-initialized (meaning that they will be initialized by their default + // constructor, if any, or else zero-initialized). + static Array New(size_t size) { return Array(size).Pass(); } + + // Creates a new array with a copy of the contents of |other|. + template <typename U> + static Array From(const U& other) { + return TypeConverter<Array, U>::Convert(other); + } + + // Copies the contents of this array to a new object of type |U|. + template <typename U> + U To() const { + return TypeConverter<U, Array>::Convert(*this); + } + + // Resets the contents of this array back to null. + void reset() { + if (!vec_.empty()) { + Traits::Finalize(&vec_); + vec_.clear(); + } + is_null_ = true; + } + + // Indicates whether the array is null (which is distinct from empty). + bool is_null() const { return is_null_; } + + // Returns a reference to the first element of the array. Calling this on a + // null or empty array causes undefined behavior. + ConstRefType front() const { return vec_.front(); } + RefType front() { return vec_.front(); } + + // Returns the size of the array, which will be zero if the array is null. + size_t size() const { return vec_.size(); } + + // Returns a reference to the element at zero-based |offset|. Calling this on + // an array with size less than |offset|+1 causes undefined behavior. + ConstRefType at(size_t offset) const { return Traits::at(&vec_, offset); } + ConstRefType operator[](size_t offset) const { return at(offset); } + RefType at(size_t offset) { return Traits::at(&vec_, offset); } + RefType operator[](size_t offset) { return at(offset); } + + // Pushes |value| onto the back of the array. If this array was null, it will + // become non-null with a size of 1. + void push_back(ForwardType value) { + is_null_ = false; + Traits::PushBack(&vec_, value); + } + + // Resizes the array to |size| and makes it non-null. Otherwise, works just + // like the resize method of |std::vector|. + void resize(size_t size) { + is_null_ = false; + Traits::Resize(&vec_, size); + } + + // Returns a const reference to the |std::vector| managed by this class. If + // the array is null, this will be an empty vector. + const std::vector<StorageType>& storage() const { return vec_; } + operator const std::vector<StorageType>&() const { return vec_; } + + // Swaps the contents of this array with the |other| array, including + // nullness. + void Swap(Array* other) { + std::swap(is_null_, other->is_null_); + vec_.swap(other->vec_); + } + + // Swaps the contents of this array with the specified vector, making this + // array non-null. Since the vector cannot represent null, it will just be + // made empty if this array is null. + void Swap(std::vector<StorageType>* other) { + is_null_ = false; + vec_.swap(*other); + } + + // Returns a copy of the array where each value of the new array has been + // "cloned" from the corresponding value of this array. If this array contains + // primitive data types, this is equivalent to simply copying the contents. + // However, if the array contains objects, then each new element is created by + // calling the |Clone| method of the source element, which should make a copy + // of the element. + // + // Please note that calling this method will fail compilation if the element + // type cannot be cloned (which usually means that it is a Mojo handle type or + // a type contains Mojo handles). + Array Clone() const { + Array result; + result.is_null_ = is_null_; + Traits::Clone(vec_, &result.vec_); + return result.Pass(); + } + + // Indicates whether the contents of this array are equal to |other|. A null + // array is only equal to another null array. Elements are compared using the + // |ValueTraits::Equals| method, which in most cases calls the |Equals| method + // of the element. + bool Equals(const Array& other) const { + if (is_null() != other.is_null()) + return false; + if (size() != other.size()) + return false; + for (size_t i = 0; i < size(); ++i) { + if (!internal::ValueTraits<T>::Equals(at(i), other.at(i))) + return false; + } + return true; + } + + private: + typedef std::vector<StorageType> Array::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &Array::vec_; } + + private: + // Forbid the == and != operators explicitly, otherwise Array will be + // converted to Testable to do == or != comparison. + template <typename U> + bool operator==(const Array<U>& other) const = delete; + template <typename U> + bool operator!=(const Array<U>& other) const = delete; + + void Take(Array* other) { + reset(); + Swap(other); + } + + std::vector<StorageType> vec_; + bool is_null_; +}; + +// A |TypeConverter| that will create an |Array<T>| containing a copy of the +// contents of an |std::vector<E>|, using |TypeConverter<T, E>| to copy each +// element. The returned array will always be non-null. +template <typename T, typename E> +struct TypeConverter<Array<T>, std::vector<E>> { + static Array<T> Convert(const std::vector<E>& input) { + Array<T> result(input.size()); + for (size_t i = 0; i < input.size(); ++i) + result[i] = TypeConverter<T, E>::Convert(input[i]); + return result.Pass(); + } +}; + +// A |TypeConverter| that will create an |std::vector<E>| containing a copy of +// the contents of an |Array<T>|, using |TypeConverter<E, T>| to copy each +// element. If the input array is null, the output vector will be empty. +template <typename E, typename T> +struct TypeConverter<std::vector<E>, Array<T>> { + static std::vector<E> Convert(const Array<T>& input) { + std::vector<E> result; + if (!input.is_null()) { + result.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + result[i] = TypeConverter<E, T>::Convert(input[i]); + } + return result; + } +}; + +// A |TypeConverter| that will create an |Array<T>| containing a copy of the +// contents of an |std::set<E>|, using |TypeConverter<T, E>| to copy each +// element. The returned array will always be non-null. +template <typename T, typename E> +struct TypeConverter<Array<T>, std::set<E>> { + static Array<T> Convert(const std::set<E>& input) { + Array<T> result(0u); + for (auto i : input) + result.push_back(TypeConverter<T, E>::Convert(i)); + return result.Pass(); + } +}; + +// A |TypeConverter| that will create an |std::set<E>| containing a copy of +// the contents of an |Array<T>|, using |TypeConverter<E, T>| to copy each +// element. If the input array is null, the output set will be empty. +template <typename E, typename T> +struct TypeConverter<std::set<E>, Array<T>> { + static std::set<E> Convert(const Array<T>& input) { + std::set<E> result; + if (!input.is_null()) { + for (size_t i = 0; i < input.size(); ++i) + result.insert(TypeConverter<E, T>::Convert(input[i])); + } + return result; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_ptr_info.h b/mojo/public/cpp/bindings/associated_interface_ptr_info.h new file mode 100644 index 0000000..55f9c4a --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_ptr_info.h @@ -0,0 +1,32 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// AssociatedInterfacePtrInfo stores necessary information to construct an +// associated interface pointer. +// TODO(yzshen): implement it. +template <typename Interface> +class AssociatedInterfacePtrInfo { + MOJO_MOVE_ONLY_TYPE(AssociatedInterfacePtrInfo); + + public: + AssociatedInterfacePtrInfo() {} + AssociatedInterfacePtrInfo(AssociatedInterfacePtrInfo&& other) {} + + AssociatedInterfacePtrInfo& operator=(AssociatedInterfacePtrInfo&& other) { + return *this; + } + + bool is_valid() const { return false; } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_request.h b/mojo/public/cpp/bindings/associated_interface_request.h new file mode 100644 index 0000000..9882b89 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_request.h @@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// Represents an associated interface request. +// TODO(yzshen): implement it. +template <typename Interface> +class AssociatedInterfaceRequest { + MOJO_MOVE_ONLY_TYPE(AssociatedInterfaceRequest) + + public: + AssociatedInterfaceRequest() {} + AssociatedInterfaceRequest(AssociatedInterfaceRequest&& other) {} + + AssociatedInterfaceRequest& operator=(AssociatedInterfaceRequest&& other) { + return *this; + } + + bool is_pending() const { return false; } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h new file mode 100644 index 0000000..de9159f --- /dev/null +++ b/mojo/public/cpp/bindings/binding.h @@ -0,0 +1,239 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/environment/logging.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// Represents the binding of an interface implementation to a message pipe. +// When the |Binding| object is destroyed, the binding between the message pipe +// and the interface is torn down and the message pipe is closed, leaving the +// interface implementation in an unbound state. +// +// Example: +// +// #include "foo.mojom.h" +// +// class FooImpl : public Foo { +// public: +// explicit FooImpl(InterfaceRequest<Foo> request) +// : binding_(this, request.Pass()) {} +// +// // Foo implementation here. +// +// private: +// Binding<Foo> binding_; +// }; +// +// class MyFooFactory : public InterfaceFactory<Foo> { +// public: +// void Create(..., InterfaceRequest<Foo> request) override { +// auto f = new FooImpl(request.Pass()); +// // Do something to manage the lifetime of |f|. Use StrongBinding<> to +// // delete FooImpl on connection errors. +// } +// }; +// +// The caller may specify a |MojoAsyncWaiter| to be used by the connection when +// waiting for calls to arrive. Normally it is fine to use the default waiter. +// However, the caller may provide their own implementation if needed. The +// |Binding| will not take ownership of the waiter, and the waiter must outlive +// the |Binding|. The provided waiter must be able to signal the implementation +// which generally means it needs to be able to schedule work on the thread the +// implementation runs on. If writing library code that has to work on different +// types of threads callers may need to provide different waiter +// implementations. +template <typename Interface> +class Binding { + public: + // Constructs an incomplete binding that will use the implementation |impl|. + // The binding may be completed with a subsequent call to the |Bind| method. + // Does not take ownership of |impl|, which must outlive the binding. + explicit Binding(Interface* impl) : impl_(impl) { stub_.set_sink(impl_); } + + // Constructs a completed binding of message pipe |handle| to implementation + // |impl|. Does not take ownership of |impl|, which must outlive the binding. + // See class comment for definition of |waiter|. + Binding(Interface* impl, + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : Binding(impl) { + Bind(handle.Pass(), waiter); + } + + // Constructs a completed binding of |impl| to a new message pipe, passing the + // client end to |ptr|, which takes ownership of it. The caller is expected to + // pass |ptr| on to the client of the service. Does not take ownership of any + // of the parameters. |impl| must outlive the binding. |ptr| only needs to + // last until the constructor returns. See class comment for definition of + // |waiter|. + Binding(Interface* impl, + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : Binding(impl) { + Bind(ptr, waiter); + } + + // Constructs a completed binding of |impl| to the message pipe endpoint in + // |request|, taking ownership of the endpoint. Does not take ownership of + // |impl|, which must outlive the binding. See class comment for definition of + // |waiter|. + Binding(Interface* impl, + InterfaceRequest<Interface> request, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : Binding(impl) { + Bind(request.PassMessagePipe(), waiter); + } + + // Tears down the binding, closing the message pipe and leaving the interface + // implementation unbound. + ~Binding() { + if (internal_router_) + Close(); + } + + // Completes a binding that was constructed with only an interface + // implementation. Takes ownership of |handle| and binds it to the previously + // specified implementation. See class comment for definition of |waiter|. + void Bind( + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + MOJO_DCHECK(!internal_router_); + internal::FilterChain filters; + filters.Append<internal::MessageHeaderValidator>(); + filters.Append<typename Interface::RequestValidator_>(); + + internal_router_ = + new internal::Router(handle.Pass(), filters.Pass(), waiter); + internal_router_->set_incoming_receiver(&stub_); + internal_router_->set_connection_error_handler( + [this]() { connection_error_handler_.Run(); }); + } + + // Completes a binding that was constructed with only an interface + // implementation by creating a new message pipe, binding one end of it to the + // previously specified implementation, and passing the other to |ptr|, which + // takes ownership of it. The caller is expected to pass |ptr| on to the + // eventual client of the service. Does not take ownership of |ptr|. See + // class comment for definition of |waiter|. + void Bind( + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + MessagePipe pipe; + ptr->Bind( + InterfacePtrInfo<Interface>(pipe.handle0.Pass(), Interface::Version_), + waiter); + Bind(pipe.handle1.Pass(), waiter); + } + + // Completes a binding that was constructed with only an interface + // implementation by removing the message pipe endpoint from |request| and + // binding it to the previously specified implementation. See class comment + // for definition of |waiter|. + void Bind( + InterfaceRequest<Interface> request, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + Bind(request.PassMessagePipe(), waiter); + } + + // Stops processing incoming messages until + // ResumeIncomingMethodCallProcessing(), or WaitForIncomingMethodCall(). + // Outgoing messages are still sent. + // + // No errors are detected on the message pipe while paused. + void PauseIncomingMethodCallProcessing() { + MOJO_DCHECK(internal_router_); + internal_router_->PauseIncomingMethodCallProcessing(); + } + void ResumeIncomingMethodCallProcessing() { + MOJO_DCHECK(internal_router_); + internal_router_->ResumeIncomingMethodCallProcessing(); + } + + // Blocks the calling thread until either a call arrives on the previously + // bound message pipe, the deadline is exceeded, or an error occurs. Returns + // true if a method was successfully read and dispatched. + bool WaitForIncomingMethodCall( + MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) { + MOJO_DCHECK(internal_router_); + return internal_router_->WaitForIncomingMessage(deadline); + } + + // Closes the message pipe that was previously bound. Put this object into a + // state where it can be rebound to a new pipe. + void Close() { + MOJO_DCHECK(internal_router_); + internal_router_->CloseMessagePipe(); + DestroyRouter(); + } + + // Unbinds the underlying pipe from this binding and returns it so it can be + // used in another context, such as on another thread or with a different + // implementation. Put this object into a state where it can be rebound to a + // new pipe. + InterfaceRequest<Interface> Unbind() { + InterfaceRequest<Interface> request = + MakeRequest<Interface>(internal_router_->PassMessagePipe()); + DestroyRouter(); + // TODO(vtl): The |.Pass()| below is only needed due to an MSVS bug; remove + // it once that's fixed. + return request.Pass(); + } + + // Sets an error handler that will be called if a connection error occurs on + // the bound message pipe. + void set_connection_error_handler(const Closure& error_handler) { + connection_error_handler_ = error_handler; + } + + // Returns the interface implementation that was previously specified. Caller + // does not take ownership. + Interface* impl() { return impl_; } + + // Indicates whether the binding has been completed (i.e., whether a message + // pipe has been bound to the implementation). + bool is_bound() const { return !!internal_router_; } + + // Returns the value of the handle currently bound to this Binding which can + // be used to make explicit Wait/WaitMany calls. Requires that the Binding be + // bound. Ownership of the handle is retained by the Binding, it is not + // transferred to the caller. + MessagePipeHandle handle() const { + MOJO_DCHECK(is_bound()); + return internal_router_->handle(); + } + + // Exposed for testing, should not generally be used. + internal::Router* internal_router() { return internal_router_; } + + private: + void DestroyRouter() { + internal_router_->set_connection_error_handler(Closure()); + delete internal_router_; + internal_router_ = nullptr; + } + + internal::Router* internal_router_ = nullptr; + typename Interface::Stub_ stub_; + Interface* impl_; + Closure connection_error_handler_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Binding); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ diff --git a/mojo/public/cpp/bindings/callback.h b/mojo/public/cpp/bindings/callback.h new file mode 100644 index 0000000..beec1a1 --- /dev/null +++ b/mojo/public/cpp/bindings/callback.h @@ -0,0 +1,115 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ + +#include "mojo/public/cpp/bindings/lib/callback_internal.h" +#include "mojo/public/cpp/bindings/lib/shared_ptr.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template <typename Sig> +class Callback; + +// Represents a callback with any number of parameters and no return value. The +// callback is executed by calling its Run() method. The callback may be "null", +// meaning it does nothing. +template <typename... Args> +class Callback<void(Args...)> { + public: + // An interface that may be implemented to define the Run() method. + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + // ForwardType ensures String is passed as a const reference. + typename internal::Callback_ParamTraits<Args>::ForwardType...) + const = 0; + }; + + // Constructs a "null" callback that does nothing. + Callback() {} + + // Constructs a callback that will run |runnable|. The callback takes + // ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // As above, but can take an object that isn't derived from Runnable, so long + // as it has a compatible operator() or Run() method. operator() will be + // preferred if the type has both. + template <typename Sink> + Callback(const Sink& sink) { + using sink_type = typename internal::Conditional< + internal::HasCompatibleCallOperator<Sink, Args...>::value, + FunctorAdapter<Sink>, RunnableAdapter<Sink>>::type; + sink_ = internal::SharedPtr<Runnable>(new sink_type(sink)); + } + + // As above, but can take a compatible function pointer. + Callback(void (*function_ptr)( + typename internal::Callback_ParamTraits<Args>::ForwardType...)) + : sink_(new FunctionPtrAdapter(function_ptr)) {} + + // Executes the callback function, invoking Pass() on move-only types. + void Run(typename internal::Callback_ParamTraits<Args>::ForwardType... args) + const { + if (sink_.get()) + sink_->Run(internal::Forward(args)...); + } + + bool is_null() const { return !sink_.get(); } + + // Resets the callback to the "null" state. + void reset() { sink_.reset(); } + + private: + // Adapts a class that has a Run() method but is not derived from Runnable to + // be callable by Callback. + template <typename Sink> + struct RunnableAdapter : public Runnable { + explicit RunnableAdapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<Args>::ForwardType... args) + const override { + sink.Run(internal::Forward(args)...); + } + Sink sink; + }; + + // Adapts a class that has a compatible operator() to be callable by Callback. + template <typename Sink> + struct FunctorAdapter : public Runnable { + explicit FunctorAdapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<Args>::ForwardType... args) + const override { + sink.operator()(internal::Forward(args)...); + } + Sink sink; + }; + + // Adapts a function pointer. + struct FunctionPtrAdapter : public Runnable { + explicit FunctionPtrAdapter(void (*function_ptr)( + typename internal::Callback_ParamTraits<Args>::ForwardType...)) + : function_ptr(function_ptr) {} + virtual void Run( + typename internal::Callback_ParamTraits<Args>::ForwardType... args) + const override { + (*function_ptr)(internal::Forward(args)...); + } + void (*function_ptr)( + typename internal::Callback_ParamTraits<Args>::ForwardType...); + }; + + internal::SharedPtr<Runnable> sink_; +}; + +// A specialization of Callback which takes no parameters. +typedef Callback<void()> Closure; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr.h b/mojo/public/cpp/bindings/interface_ptr.h new file mode 100644 index 0000000..dc44667 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr.h @@ -0,0 +1,197 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ + +#include <algorithm> + +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/interface_ptr_internal.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// A pointer to a local proxy of a remote Interface implementation. Uses a +// message pipe to communicate with the remote implementation, and automatically +// closes the pipe and deletes the proxy on destruction. The pointer must be +// bound to a message pipe before the interface methods can be called. +// +// This class is thread hostile, as is the local proxy it manages. All calls to +// this class or the proxy should be from the same thread that created it. If +// you need to move the proxy to a different thread, extract the +// InterfacePtrInfo (containing just the message pipe and any version +// information) using PassInterface(), pass it to a different thread, and +// create and bind a new InterfacePtr from that thread. +template <typename Interface> +class InterfacePtr { + MOJO_MOVE_ONLY_TYPE(InterfacePtr) + public: + // Constructs an unbound InterfacePtr. + InterfacePtr() {} + InterfacePtr(decltype(nullptr)) {} + + // Takes over the binding of another InterfacePtr. + InterfacePtr(InterfacePtr&& other) { + internal_state_.Swap(&other.internal_state_); + } + + // Takes over the binding of another InterfacePtr, and closes any message pipe + // already bound to this pointer. + InterfacePtr& operator=(InterfacePtr&& other) { + reset(); + internal_state_.Swap(&other.internal_state_); + return *this; + } + + // Assigning nullptr to this class causes it to close the currently bound + // message pipe (if any) and returns the pointer to the unbound state. + InterfacePtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + // Closes the bound message pipe (if any) on destruction. + ~InterfacePtr() {} + + // Binds the InterfacePtr to a remote implementation of Interface. The + // |waiter| is used for receiving notifications when there is data to read + // from the message pipe. For most callers, the default |waiter| will be + // sufficient. + // + // Calling with an invalid |info| (containing an invalid message pipe handle) + // has the same effect as reset(). In this case, the InterfacePtr is not + // considered as bound. + void Bind( + InterfacePtrInfo<Interface> info, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + reset(); + if (info.is_valid()) + internal_state_.Bind(info.Pass(), waiter); + } + + // Returns whether or not this InterfacePtr is bound to a message pipe. + bool is_bound() const { return internal_state_.is_bound(); } + + // Returns a raw pointer to the local proxy. Caller does not take ownership. + // Note that the local proxy is thread hostile, as stated above. + Interface* get() const { return internal_state_.instance(); } + + // Functions like a pointer to Interface. Must already be bound. + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + // Returns the version number of the interface that the remote side supports. + uint32_t version() const { return internal_state_.version(); } + + // Queries the max version that the remote side supports. On completion, the + // result will be returned as the input of |callback|. The version number of + // this interface pointer will also be updated. + void QueryVersion(const Callback<void(uint32_t)>& callback) { + internal_state_.QueryVersion(callback); + } + + // If the remote side doesn't support the specified version, it will close its + // end of the message pipe asynchronously. This does nothing if it's already + // known that the remote side supports the specified version, i.e., if + // |version <= this->version()|. + // + // After calling RequireVersion() with a version not supported by the remote + // side, all subsequent calls to interface methods will be ignored. + void RequireVersion(uint32_t version) { + internal_state_.RequireVersion(version); + } + + // Closes the bound message pipe (if any) and returns the pointer to the + // unbound state. + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // Blocks the current thread until the next incoming response callback arrives + // or an error occurs. Returns |true| if a response arrived, or |false| in + // case of error. + // + // This method may only be called after the InterfacePtr has been bound to a + // message pipe. + bool WaitForIncomingResponse() { + return internal_state_.WaitForIncomingResponse(); + } + + // Indicates whether the message pipe has encountered an error. If true, + // method calls made on this interface will be dropped (and may already have + // been dropped). + bool encountered_error() const { return internal_state_.encountered_error(); } + + // Registers a handler to receive error notifications. The handler will be + // called from the thread that owns this InterfacePtr. + // + // This method may only be called after the InterfacePtr has been bound to a + // message pipe. + void set_connection_error_handler(const Closure& error_handler) { + internal_state_.set_connection_error_handler(error_handler); + } + + // Unbinds the InterfacePtr and returns the information which could be used + // to setup an InterfacePtr again. This method may be used to move the proxy + // to a different thread (see class comments for details). + // + // It is an error to call PassInterface() while there are pending responses. + // TODO: fix this restriction, it's not always obvious when there is a + // pending response. + InterfacePtrInfo<Interface> PassInterface() { + MOJO_DCHECK(!internal_state_.has_pending_callbacks()); + State state; + internal_state_.Swap(&state); + + return state.PassInterface(); + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::InterfacePtrState<Interface>* internal_state() { + return &internal_state_; + } + + // Allow InterfacePtr<> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + typedef internal::InterfacePtrState<Interface> InterfacePtr::*Testable; + + public: + operator Testable() const { + return internal_state_.is_bound() ? &InterfacePtr::internal_state_ + : nullptr; + } + + private: + // Forbid the == and != operators explicitly, otherwise InterfacePtr will be + // converted to Testable to do == or != comparison. + template <typename T> + bool operator==(const InterfacePtr<T>& other) const = delete; + template <typename T> + bool operator!=(const InterfacePtr<T>& other) const = delete; + + typedef internal::InterfacePtrState<Interface> State; + mutable State internal_state_; +}; + +// If |info| is valid (containing a valid message pipe handle), returns an +// InterfacePtr bound to it. Otherwise, returns an unbound InterfacePtr. The +// specified |waiter| will be used as in the InterfacePtr::Bind() method. +template <typename Interface> +InterfacePtr<Interface> MakeProxy( + InterfacePtrInfo<Interface> info, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + InterfacePtr<Interface> ptr; + if (info.is_valid()) + ptr.Bind(info.Pass(), waiter); + return ptr.Pass(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr_info.h b/mojo/public/cpp/bindings/interface_ptr_info.h new file mode 100644 index 0000000..4f61915 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr_info.h @@ -0,0 +1,58 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ + +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// InterfacePtrInfo stores necessary information to communicate with a remote +// interface implementation, which could be used to construct an InterfacePtr. +template <typename Interface> +class InterfacePtrInfo { + MOJO_MOVE_ONLY_TYPE(InterfacePtrInfo); + + public: + InterfacePtrInfo() : version_(0u) {} + + InterfacePtrInfo(ScopedMessagePipeHandle handle, uint32_t version) + : handle_(handle.Pass()), version_(version) {} + + InterfacePtrInfo(InterfacePtrInfo&& other) + : handle_(other.handle_.Pass()), version_(other.version_) { + other.version_ = 0u; + } + + ~InterfacePtrInfo() {} + + InterfacePtrInfo& operator=(InterfacePtrInfo&& other) { + if (this != &other) { + handle_ = other.handle_.Pass(); + version_ = other.version_; + other.version_ = 0u; + } + + return *this; + } + + bool is_valid() const { return handle_.is_valid(); } + + ScopedMessagePipeHandle PassHandle() { return handle_.Pass(); } + const ScopedMessagePipeHandle& handle() const { return handle_; } + void set_handle(ScopedMessagePipeHandle handle) { handle_ = handle.Pass(); } + + uint32_t version() const { return version_; } + void set_version(uint32_t version) { version_ = version; } + + private: + ScopedMessagePipeHandle handle_; + uint32_t version_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h new file mode 100644 index 0000000..c139306 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_request.h @@ -0,0 +1,118 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ + +#include "mojo/public/cpp/bindings/interface_ptr.h" + +namespace mojo { + +// Represents a request from a remote client for an implementation of Interface +// over a specified message pipe. The implementor of the interface should +// remove the message pipe by calling PassMessagePipe() and bind it to the +// implementation. If this is not done, the InterfaceRequest will automatically +// close the pipe on destruction. Can also represent the absence of a request +// if the client did not provide a message pipe. +template <typename Interface> +class InterfaceRequest { + MOJO_MOVE_ONLY_TYPE(InterfaceRequest) + public: + // Constructs an empty InterfaceRequest, representing that the client is not + // requesting an implementation of Interface. + InterfaceRequest() {} + InterfaceRequest(decltype(nullptr)) {} + + // Takes the message pipe from another InterfaceRequest. + InterfaceRequest(InterfaceRequest&& other) { handle_ = other.handle_.Pass(); } + InterfaceRequest& operator=(InterfaceRequest&& other) { + handle_ = other.handle_.Pass(); + return *this; + } + + // Assigning to nullptr resets the InterfaceRequest to an empty state, + // closing the message pipe currently bound to it (if any). + InterfaceRequest& operator=(decltype(nullptr)) { + handle_.reset(); + return *this; + } + + // Binds the request to a message pipe over which Interface is to be + // requested. If the request is already bound to a message pipe, the current + // message pipe will be closed. + void Bind(ScopedMessagePipeHandle handle) { handle_ = handle.Pass(); } + + // Indicates whether the request currently contains a valid message pipe. + bool is_pending() const { return handle_.is_valid(); } + + // Removes the message pipe from the request and returns it. + ScopedMessagePipeHandle PassMessagePipe() { return handle_.Pass(); } + + private: + ScopedMessagePipeHandle handle_; +}; + +// Makes an InterfaceRequest bound to the specified message pipe. If |handle| +// is empty or invalid, the resulting InterfaceRequest will represent the +// absence of a request. +template <typename Interface> +InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) { + InterfaceRequest<Interface> request; + request.Bind(handle.Pass()); + return request.Pass(); +} + +// Creates a new message pipe over which Interface is to be served. Binds the +// specified InterfacePtr to one end of the message pipe, and returns an +// InterfaceRequest bound to the other. The InterfacePtr should be passed to +// the client, and the InterfaceRequest should be passed to whatever will +// provide the implementation. The implementation should typically be bound to +// the InterfaceRequest using the Binding or StrongBinding classes. The client +// may begin to issue calls even before an implementation has been bound, since +// messages sent over the pipe will just queue up until they are consumed by +// the implementation. +// +// Example #1: Requesting a remote implementation of an interface. +// =============================================================== +// +// Given the following interface: +// +// interface Database { +// OpenTable(Table& table); +// } +// +// The client would have code similar to the following: +// +// DatabasePtr database = ...; // Connect to database. +// TablePtr table; +// database->OpenTable(GetProxy(&table)); +// +// Upon return from GetProxy, |table| is ready to have methods called on it. +// +// Example #2: Registering a local implementation with a remote service. +// ===================================================================== +// +// Given the following interface +// interface Collector { +// RegisterSource(Source source); +// } +// +// The client would have code similar to the following: +// +// CollectorPtr collector = ...; // Connect to Collector. +// SourcePtr source; +// InterfaceRequest<Source> source_request = GetProxy(&source); +// collector->RegisterSource(source.Pass()); +// CreateSource(source_request.Pass()); // Create implementation locally. +// +template <typename Interface> +InterfaceRequest<Interface> GetProxy(InterfacePtr<Interface>* ptr) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo<Interface>(pipe.handle0.Pass(), 0u)); + return MakeRequest<Interface>(pipe.handle1.Pass()); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/lib/TODO b/mojo/public/cpp/bindings/lib/TODO new file mode 100644 index 0000000..ea4ce81 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/TODO @@ -0,0 +1,4 @@ +TODOs: + - Optimize Buffer classes? + - Add compile-time asserts to verify object packing and padding. + - Investigate making arrays of objects not be arrays of pointers. diff --git a/mojo/public/cpp/bindings/lib/array_internal.cc b/mojo/public/cpp/bindings/lib/array_internal.cc new file mode 100644 index 0000000..61e4b0d --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.cc @@ -0,0 +1,74 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/lib/array_internal.h" + +#include <sstream> + +namespace mojo { +namespace internal { + +std::string MakeMessageWithArrayIndex(const char* message, + size_t size, + size_t index) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; index - " << index; + return stream.str(); +} + +std::string MakeMessageWithExpectedArraySize(const char* message, + size_t size, + size_t expected_size) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; expected size - " + << expected_size; + return stream.str(); +} + +ArrayDataTraits<bool>::BitRef::~BitRef() { +} + +ArrayDataTraits<bool>::BitRef::BitRef(uint8_t* storage, uint8_t mask) + : storage_(storage), mask_(mask) { +} + +ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=( + bool value) { + if (value) { + *storage_ |= mask_; + } else { + *storage_ &= ~mask_; + } + return *this; +} + +ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=( + const BitRef& value) { + return (*this) = static_cast<bool>(value); +} + +ArrayDataTraits<bool>::BitRef::operator bool() const { + return (*storage_ & mask_) != 0; +} + +// static +void ArraySerializationHelper<Handle, true>::EncodePointersAndHandles( + const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + EncodeHandle(&elements[i], handles); +} + +// static +void ArraySerializationHelper<Handle, true>::DecodePointersAndHandles( + const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + DecodeHandle(&elements[i], handles); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h new file mode 100644 index 0000000..41ca174 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.h @@ -0,0 +1,534 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ + +#include <new> +#include <vector> + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +template <typename T> +class Array; +class String; + +namespace internal { + +// std::numeric_limits<uint32_t>::max() is not a compile-time constant (until +// C++11). +const uint32_t kMaxUint32 = 0xFFFFFFFF; + +std::string MakeMessageWithArrayIndex(const char* message, + size_t size, + size_t index); + +std::string MakeMessageWithExpectedArraySize(const char* message, + size_t size, + size_t expected_size); + +template <typename T> +struct ArrayDataTraits { + typedef T StorageType; + typedef T& Ref; + typedef T const& ConstRef; + + static const uint32_t kMaxNumElements = + (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType); + + static uint32_t GetStorageSize(uint32_t num_elements) { + MOJO_DCHECK(num_elements <= kMaxNumElements); + return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset]; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset]; + } +}; + +template <typename P> +struct ArrayDataTraits<P*> { + typedef StructPointer<P> StorageType; + typedef P*& Ref; + typedef P* const& ConstRef; + + static const uint32_t kMaxNumElements = + (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType); + + static uint32_t GetStorageSize(uint32_t num_elements) { + MOJO_DCHECK(num_elements <= kMaxNumElements); + return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset].ptr; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset].ptr; + } +}; + +template <typename T> +struct ArrayDataTraits<Array_Data<T>*> { + typedef ArrayPointer<T> StorageType; + typedef Array_Data<T>*& Ref; + typedef Array_Data<T>* const& ConstRef; + + static const uint32_t kMaxNumElements = + (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType); + + static uint32_t GetStorageSize(uint32_t num_elements) { + MOJO_DCHECK(num_elements <= kMaxNumElements); + return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset].ptr; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset].ptr; + } +}; + +// Specialization of Arrays for bools, optimized for space. It has the +// following differences from a generalized Array: +// * Each element takes up a single bit of memory. +// * Accessing a non-const single element uses a helper class |BitRef|, which +// emulates a reference to a bool. +template <> +struct ArrayDataTraits<bool> { + // Helper class to emulate a reference to a bool, used for direct element + // access. + class BitRef { + public: + ~BitRef(); + BitRef& operator=(bool value); + BitRef& operator=(const BitRef& value); + operator bool() const; + + private: + friend struct ArrayDataTraits<bool>; + BitRef(uint8_t* storage, uint8_t mask); + BitRef(); + uint8_t* storage_; + uint8_t mask_; + }; + + // Because each element consumes only 1/8 byte. + static const uint32_t kMaxNumElements = kMaxUint32; + + typedef uint8_t StorageType; + typedef BitRef Ref; + typedef bool ConstRef; + + static uint32_t GetStorageSize(uint32_t num_elements) { + return sizeof(ArrayHeader) + ((num_elements + 7) / 8); + } + static BitRef ToRef(StorageType* storage, size_t offset) { + return BitRef(&storage[offset / 8], 1 << (offset % 8)); + } + static bool ToConstRef(const StorageType* storage, size_t offset) { + return (storage[offset / 8] & (1 << (offset % 8))) != 0; + } +}; + +// What follows is code to support the serialization of Array_Data<T>. There +// are two interesting cases: arrays of primitives and arrays of objects. +// Arrays of objects are represented as arrays of pointers to objects. + +template <typename T, bool is_handle> +struct ArraySerializationHelper; + +template <typename T> +struct ArraySerializationHelper<T, false> { + typedef typename ArrayDataTraits<T>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) {} + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) {} + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + MOJO_DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + return true; + } +}; + +template <> +struct ArraySerializationHelper<Handle, true> { + typedef ArrayDataTraits<Handle>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles); + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles); + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params->element_validate_params) + << "Handle type should not have array validate params"; + + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && + elements[i].value() == kEncodedInvalidHandleValue) { + ReportValidationError( + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + MakeMessageWithArrayIndex( + "invalid handle in array expecting valid handles", + header->num_elements, + i).c_str()); + return false; + } + if (!bounds_checker->ClaimHandle(elements[i])) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_HANDLE); + return false; + } + } + return true; + } +}; + +template <typename H> +struct ArraySerializationHelper<H, true> { + typedef typename ArrayDataTraits<H>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + ArraySerializationHelper<Handle, true>::EncodePointersAndHandles( + header, elements, handles); + } + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + ArraySerializationHelper<Handle, true>::DecodePointersAndHandles( + header, elements, handles); + } + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + return ArraySerializationHelper<Handle, true>::ValidateElements( + header, elements, bounds_checker, validate_params); + } +}; + +template <typename P> +struct ArraySerializationHelper<P*, false> { + typedef typename ArrayDataTraits<P*>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + Encode(&elements[i], handles); + } + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + Decode(&elements[i], handles); + } + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && !elements[i].offset) { + ReportValidationError( + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + header->num_elements, + i).c_str()); + return false; + } + if (!ValidateEncodedPointer(&elements[i].offset)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_POINTER); + return false; + } + if (!ValidateCaller<P>::Run(DecodePointerRaw(&elements[i].offset), + bounds_checker, + validate_params->element_validate_params)) { + return false; + } + } + return true; + } + + private: + template <typename T, + bool is_union = IsUnionDataType<T>::value> + struct ValidateCaller {}; + + template <typename T> + struct ValidateCaller<T, false> { + static bool Run(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params) + << "Struct type should not have array validate params"; + + return T::Validate(data, bounds_checker); + } + }; + + template <typename T> + struct ValidateCaller<T, true> { + static bool Run(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params) + << "Union type should not have array validate params"; + + return T::Validate(data, bounds_checker, true); + } + }; + + template <typename Key, typename Value> + struct ValidateCaller<Map_Data<Key, Value>, false> { + static bool Run(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + return Map_Data<Key, Value>::Validate(data, bounds_checker, + validate_params); + } + }; + + template <typename T> + struct ValidateCaller<Array_Data<T>, false> { + static bool Run(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + return Array_Data<T>::Validate(data, bounds_checker, validate_params); + } + }; +}; + +template <typename T> +class Array_Data { + public: + typedef ArrayDataTraits<T> Traits; + typedef typename Traits::StorageType StorageType; + typedef typename Traits::Ref Ref; + typedef typename Traits::ConstRef ConstRef; + typedef ArraySerializationHelper<T, IsHandle<T>::value> Helper; + + // Returns null if |num_elements| or the corresponding storage size cannot be + // stored in uint32_t. + static Array_Data<T>* New(size_t num_elements, Buffer* buf) { + if (num_elements > Traits::kMaxNumElements) + return nullptr; + + uint32_t num_bytes = + Traits::GetStorageSize(static_cast<uint32_t>(num_elements)); + return new (buf->Allocate(num_bytes)) + Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements)); + } + + static bool Validate(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* validate_params) { + if (!data) + return true; + if (!IsAligned(data)) { + ReportValidationError(VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!bounds_checker->IsValidRange(data, sizeof(ArrayHeader))) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + const ArrayHeader* header = static_cast<const ArrayHeader*>(data); + if (header->num_elements > Traits::kMaxNumElements || + header->num_bytes < Traits::GetStorageSize(header->num_elements)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + return false; + } + if (validate_params->expected_num_elements != 0 && + header->num_elements != validate_params->expected_num_elements) { + ReportValidationError( + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", + header->num_elements, + validate_params->expected_num_elements).c_str()); + return false; + } + if (!bounds_checker->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const Array_Data<T>* object = static_cast<const Array_Data<T>*>(data); + return Helper::ValidateElements(&object->header_, object->storage(), + bounds_checker, validate_params); + } + + size_t size() const { return header_.num_elements; } + + Ref at(size_t offset) { + MOJO_DCHECK(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToRef(storage(), offset); + } + + ConstRef at(size_t offset) const { + MOJO_DCHECK(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToConstRef(storage(), offset); + } + + StorageType* storage() { + return reinterpret_cast<StorageType*>(reinterpret_cast<char*>(this) + + sizeof(*this)); + } + + const StorageType* storage() const { + return reinterpret_cast<const StorageType*>( + reinterpret_cast<const char*>(this) + sizeof(*this)); + } + + void EncodePointersAndHandles(std::vector<Handle>* handles) { + Helper::EncodePointersAndHandles(&header_, storage(), handles); + } + + void DecodePointersAndHandles(std::vector<Handle>* handles) { + Helper::DecodePointersAndHandles(&header_, storage(), handles); + } + + private: + Array_Data(uint32_t num_bytes, uint32_t num_elements) { + header_.num_bytes = num_bytes; + header_.num_elements = num_elements; + } + ~Array_Data() = delete; + + internal::ArrayHeader header_; + + // Elements of type internal::ArrayDataTraits<T>::StorageType follow. +}; +static_assert(sizeof(Array_Data<char>) == 8, "Bad sizeof(Array_Data)"); + +// UTF-8 encoded +typedef Array_Data<char> String_Data; + +template <typename T, bool kIsMoveOnlyType> +struct ArrayTraits {}; + +template <typename T> +struct ArrayTraits<T, false> { + typedef T StorageType; + typedef typename std::vector<T>::reference RefType; + typedef typename std::vector<T>::const_reference ConstRefType; + typedef ConstRefType ForwardType; + static inline void Initialize(std::vector<T>* vec) {} + static inline void Finalize(std::vector<T>* vec) {} + static inline ConstRefType at(const std::vector<T>* vec, size_t offset) { + return vec->at(offset); + } + static inline RefType at(std::vector<T>* vec, size_t offset) { + return vec->at(offset); + } + static inline void Resize(std::vector<T>* vec, size_t size) { + vec->resize(size); + } + static inline void PushBack(std::vector<T>* vec, ForwardType value) { + vec->push_back(value); + } + static inline void Clone(const std::vector<T>& src_vec, + std::vector<T>* dest_vec) { + dest_vec->assign(src_vec.begin(), src_vec.end()); + } +}; + +template <typename T> +struct ArrayTraits<T, true> { + struct StorageType { + char buf[sizeof(T) + (8 - (sizeof(T) % 8)) % 8]; // Make 8-byte aligned. + }; + typedef T& RefType; + typedef const T& ConstRefType; + typedef T ForwardType; + static inline void Initialize(std::vector<StorageType>* vec) { + for (size_t i = 0; i < vec->size(); ++i) + new (vec->at(i).buf) T(); + } + static inline void Finalize(std::vector<StorageType>* vec) { + for (size_t i = 0; i < vec->size(); ++i) + reinterpret_cast<T*>(vec->at(i).buf)->~T(); + } + static inline ConstRefType at(const std::vector<StorageType>* vec, + size_t offset) { + return *reinterpret_cast<const T*>(vec->at(offset).buf); + } + static inline RefType at(std::vector<StorageType>* vec, size_t offset) { + return *reinterpret_cast<T*>(vec->at(offset).buf); + } + static inline void Resize(std::vector<StorageType>* vec, size_t size) { + size_t old_size = vec->size(); + for (size_t i = size; i < old_size; i++) + reinterpret_cast<T*>(vec->at(i).buf)->~T(); + ResizeStorage(vec, size); + for (size_t i = old_size; i < vec->size(); i++) + new (vec->at(i).buf) T(); + } + static inline void PushBack(std::vector<StorageType>* vec, RefType value) { + size_t old_size = vec->size(); + ResizeStorage(vec, old_size + 1); + new (vec->at(old_size).buf) T(value.Pass()); + } + static inline void ResizeStorage(std::vector<StorageType>* vec, size_t size) { + if (size <= vec->capacity()) { + vec->resize(size); + return; + } + std::vector<StorageType> new_storage(size); + for (size_t i = 0; i < vec->size(); i++) + new (new_storage.at(i).buf) T(at(vec, i).Pass()); + vec->swap(new_storage); + Finalize(&new_storage); + } + static inline void Clone(const std::vector<StorageType>& src_vec, + std::vector<StorageType>* dest_vec) { + Resize(dest_vec, src_vec.size()); + for (size_t i = 0; i < src_vec.size(); ++i) + at(dest_vec, i) = at(&src_vec, i).Clone(); + } +}; + +template <> +struct WrapperTraits<String, false> { + typedef String_Data* DataType; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h new file mode 100644 index 0000000..9d0e7bb --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_serialization.h @@ -0,0 +1,338 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ + +#include <string.h> // For |memcpy()|. + +#include <vector> + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/map_serialization.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { + +template <typename E> +inline size_t GetSerializedSize_(const Array<E>& input); + +template <typename E, typename F> +inline void SerializeArray_( + Array<E> input, + internal::Buffer* buf, + internal::Array_Data<F>** output, + const internal::ArrayValidateParams* validate_params); + +template <typename E, typename F> +inline void Deserialize_(internal::Array_Data<F>* data, Array<E>* output); + +namespace internal { + +template <typename E, + typename F, + bool is_union = + IsUnionDataType<typename RemovePointer<F>::type>::value> +struct ArraySerializer; + +// Handles serialization and deserialization of arrays of pod types. +template <typename E, typename F> +struct ArraySerializer<E, F, false> { + static_assert(sizeof(E) == sizeof(F), "Incorrect array serializer"); + static size_t GetSerializedSize(const Array<E>& input) { + return sizeof(Array_Data<F>) + Align(input.size() * sizeof(E)); + } + + static void SerializeElements(Array<E> input, + Buffer* buf, + Array_Data<F>* output, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + MOJO_DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + if (input.size()) + memcpy(output->storage(), &input.storage()[0], input.size() * sizeof(E)); + } + static void DeserializeElements(Array_Data<F>* input, Array<E>* output) { + std::vector<E> result(input->size()); + if (input->size()) + memcpy(&result[0], input->storage(), input->size() * sizeof(E)); + output->Swap(&result); + } +}; + +// Serializes and deserializes arrays of bools. +template <> +struct ArraySerializer<bool, bool, false> { + static size_t GetSerializedSize(const Array<bool>& input) { + return sizeof(Array_Data<bool>) + Align((input.size() + 7) / 8); + } + + static void SerializeElements(Array<bool> input, + Buffer* buf, + Array_Data<bool>* output, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + MOJO_DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + // TODO(darin): Can this be a memcpy somehow instead of a bit-by-bit copy? + for (size_t i = 0; i < input.size(); ++i) + output->at(i) = input[i]; + } + static void DeserializeElements(Array_Data<bool>* input, + Array<bool>* output) { + Array<bool> result(input->size()); + // TODO(darin): Can this be a memcpy somehow instead of a bit-by-bit copy? + for (size_t i = 0; i < input->size(); ++i) + result.at(i) = input->at(i); + output->Swap(&result); + } +}; + +// Serializes and deserializes arrays of handles. +template <typename H> +struct ArraySerializer<ScopedHandleBase<H>, H, false> { + static size_t GetSerializedSize(const Array<ScopedHandleBase<H>>& input) { + return sizeof(Array_Data<H>) + Align(input.size() * sizeof(H)); + } + + static void SerializeElements(Array<ScopedHandleBase<H>> input, + Buffer* buf, + Array_Data<H>* output, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params->element_validate_params) + << "Handle type should not have array validate params"; + + for (size_t i = 0; i < input.size(); ++i) { + output->at(i) = input[i].release(); // Transfer ownership of the handle. + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && !output->at(i).is_valid(), + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + MakeMessageWithArrayIndex( + "invalid handle in array expecting valid handles", input.size(), + i)); + } + } + static void DeserializeElements(Array_Data<H>* input, + Array<ScopedHandleBase<H>>* output) { + Array<ScopedHandleBase<H>> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) + result.at(i) = MakeScopedHandle(FetchAndReset(&input->at(i))); + output->Swap(&result); + } +}; + +// This template must only apply to pointer mojo entity (structs and arrays). +// This is done by ensuring that WrapperTraits<S>::DataType is a pointer. +template <typename S> +struct ArraySerializer< + S, + typename EnableIf<IsPointer<typename WrapperTraits<S>::DataType>::value, + typename WrapperTraits<S>::DataType>::type, + false> { + typedef + typename RemovePointer<typename WrapperTraits<S>::DataType>::type S_Data; + static size_t GetSerializedSize(const Array<S>& input) { + size_t size = sizeof(Array_Data<S_Data*>) + + input.size() * sizeof(StructPointer<S_Data>); + for (size_t i = 0; i < input.size(); ++i) + size += GetSerializedSize_(input[i]); + return size; + } + + static void SerializeElements(Array<S> input, + Buffer* buf, + Array_Data<S_Data*>* output, + const ArrayValidateParams* validate_params) { + for (size_t i = 0; i < input.size(); ++i) { + S_Data* element; + SerializeCaller<S>::Run(input[i].Pass(), buf, &element, + validate_params->element_validate_params); + output->at(i) = element; + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && !element, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + input.size(), i)); + } + } + static void DeserializeElements(Array_Data<S_Data*>* input, + Array<S>* output) { + Array<S> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) { + Deserialize_(input->at(i), &result[i]); + } + output->Swap(&result); + } + + private: + template <typename T> + struct SerializeCaller { + static void Run(T input, + Buffer* buf, + typename WrapperTraits<T>::DataType* output, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK(!validate_params) + << "Struct type should not have array validate params"; + + Serialize_(input.Pass(), buf, output); + } + }; + + template <typename T> + struct SerializeCaller<Array<T>> { + static void Run(Array<T> input, + Buffer* buf, + typename Array<T>::Data_** output, + const ArrayValidateParams* validate_params) { + SerializeArray_(input.Pass(), buf, output, validate_params); + } + }; + + template <typename T, typename U> + struct SerializeCaller<Map<T, U>> { + static void Run(Map<T, U> input, + Buffer* buf, + typename Map<T, U>::Data_** output, + const ArrayValidateParams* validate_params) { + SerializeMap_(input.Pass(), buf, output, validate_params); + } + }; +}; + +// Handles serialization and deserialization of arrays of unions. +template <typename U, typename U_Data> +struct ArraySerializer<U, U_Data, true> { + static size_t GetSerializedSize(const Array<U>& input) { + size_t size = sizeof(Array_Data<U_Data>); + for (size_t i = 0; i < input.size(); ++i) { + // GetSerializedSize_ will account for both the data in the union and the + // space in the array used to hold the union. + size += GetSerializedSize_(input[i], false); + } + return size; + } + + static void SerializeElements(Array<U> input, + Buffer* buf, + Array_Data<U_Data>* output, + const ArrayValidateParams* validate_params) { + for (size_t i = 0; i < input.size(); ++i) { + U_Data* result = output->storage() + i; + SerializeUnion_(input[i].Pass(), buf, &result, true); + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && output->at(i).is_null(), + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid unions", + input.size(), i)); + } + } + + static void DeserializeElements(Array_Data<U_Data>* input, Array<U>* output) { + Array<U> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) { + Deserialize_(&input->at(i), &result[i]); + } + output->Swap(&result); + } +}; + +// Handles serialization and deserialization of arrays of strings. +template <> +struct ArraySerializer<String, String_Data*> { + static size_t GetSerializedSize(const Array<String>& input) { + size_t size = + sizeof(Array_Data<String_Data*>) + input.size() * sizeof(StringPointer); + for (size_t i = 0; i < input.size(); ++i) + size += GetSerializedSize_(input[i]); + return size; + } + + static void SerializeElements(Array<String> input, + Buffer* buf, + Array_Data<String_Data*>* output, + const ArrayValidateParams* validate_params) { + MOJO_DCHECK( + validate_params->element_validate_params && + !validate_params->element_validate_params->element_validate_params && + !validate_params->element_validate_params->element_is_nullable && + validate_params->element_validate_params->expected_num_elements == 0) + << "String type has unexpected array validate params"; + + for (size_t i = 0; i < input.size(); ++i) { + String_Data* element; + Serialize_(input[i], buf, &element); + output->at(i) = element; + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && !element, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid strings", + input.size(), i)); + } + } + static void DeserializeElements(Array_Data<String_Data*>* input, + Array<String>* output) { + Array<String> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) + Deserialize_(input->at(i), &result[i]); + output->Swap(&result); + } +}; + +} // namespace internal + +template <typename E> +inline size_t GetSerializedSize_(const Array<E>& input) { + if (!input) + return 0; + typedef typename internal::WrapperTraits<E>::DataType F; + return internal::ArraySerializer<E, F>::GetSerializedSize(input); +} + +template <typename E, typename F> +inline void SerializeArray_( + Array<E> input, + internal::Buffer* buf, + internal::Array_Data<F>** output, + const internal::ArrayValidateParams* validate_params) { + if (input) { + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + validate_params->expected_num_elements != 0 && + input.size() != validate_params->expected_num_elements, + internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + internal::MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", input.size(), + validate_params->expected_num_elements)); + + internal::Array_Data<F>* result = + internal::Array_Data<F>::New(input.size(), buf); + if (result) { + internal::ArraySerializer<E, F>::SerializeElements( + internal::Forward(input), buf, result, validate_params); + } + *output = result; + } else { + *output = nullptr; + } +} + +template <typename E, typename F> +inline void Deserialize_(internal::Array_Data<F>* input, Array<E>* output) { + if (input) { + internal::ArraySerializer<E, F>::DeserializeElements(input, output); + } else { + output->reset(); + } +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h new file mode 100644 index 0000000..436f580 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bindings_internal.h @@ -0,0 +1,134 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +template <typename T> +class Array_Data; + +#pragma pack(push, 1) + +struct StructHeader { + uint32_t num_bytes; + uint32_t version; +}; +static_assert(sizeof(StructHeader) == 8, "Bad sizeof(StructHeader)"); + +struct ArrayHeader { + uint32_t num_bytes; + uint32_t num_elements; +}; +static_assert(sizeof(ArrayHeader) == 8, "Bad_sizeof(ArrayHeader)"); + +template <typename T> +union StructPointer { + uint64_t offset; + T* ptr; +}; +static_assert(sizeof(StructPointer<char>) == 8, "Bad_sizeof(StructPointer)"); + +template <typename T> +union ArrayPointer { + uint64_t offset; + Array_Data<T>* ptr; +}; +static_assert(sizeof(ArrayPointer<char>) == 8, "Bad_sizeof(ArrayPointer)"); + +union StringPointer { + uint64_t offset; + Array_Data<char>* ptr; +}; +static_assert(sizeof(StringPointer) == 8, "Bad_sizeof(StringPointer)"); + + +template <typename T> +union UnionPointer { + uint64_t offset; + T* ptr; +}; +static_assert(sizeof(UnionPointer<char>) == 8, "Bad_sizeof(UnionPointer)"); + +struct Interface_Data { + MessagePipeHandle handle; + uint32_t version; +}; +static_assert(sizeof(Interface_Data) == 8, "Bad_sizeof(Interface_Data)"); + +struct AssociatedInterface_Data { + uint32_t interface_id; + uint32_t version; +}; +static_assert(sizeof(AssociatedInterface_Data) == 8, + "Bad_sizeof(AssociatedInterface_Data)"); + +using AssociatedInterfaceRequest_Data = uint32_t; + +#pragma pack(pop) + +template <typename T> +void ResetIfNonNull(T* ptr) { + if (ptr) + *ptr = T(); +} + +template <typename T> +T FetchAndReset(T* ptr) { + T temp = *ptr; + *ptr = T(); + return temp; +} + +template <typename H> +struct IsHandle { + enum { value = IsBaseOf<Handle, H>::value }; +}; + +template <typename T> +struct IsUnionDataType { + template <typename U> + static YesType Test(const typename U::MojomUnionDataType*); + + template <typename U> + static NoType Test(...); + + static const bool value = + sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value; +}; + +template <typename T, bool move_only = IsMoveOnlyType<T>::value> +struct WrapperTraits; + +template <typename T> +struct WrapperTraits<T, false> { + typedef T DataType; +}; +template <typename H> +struct WrapperTraits<ScopedHandleBase<H>, true> { + typedef H DataType; +}; +template <typename S> +struct WrapperTraits<StructPtr<S>, true> { + typedef typename S::Data_* DataType; +}; +template <typename S> +struct WrapperTraits<InlinedStructPtr<S>, true> { + typedef typename S::Data_* DataType; +}; +template <typename S> +struct WrapperTraits<S, true> { + typedef typename S::Data_* DataType; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/bindings_serialization.cc b/mojo/public/cpp/bindings/lib/bindings_serialization.cc new file mode 100644 index 0000000..791afe2 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bindings_serialization.cc @@ -0,0 +1,90 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/lib/bindings_serialization.h" + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +namespace { + +const size_t kAlignment = 8; + +template <typename T> +T AlignImpl(T t) { + return t + (kAlignment - (t % kAlignment)) % kAlignment; +} + +} // namespace + +size_t Align(size_t size) { + return AlignImpl(size); +} + +char* AlignPointer(char* ptr) { + return reinterpret_cast<char*>(AlignImpl(reinterpret_cast<uintptr_t>(ptr))); +} + +bool IsAligned(const void* ptr) { + return !(reinterpret_cast<uintptr_t>(ptr) % kAlignment); +} + +void EncodePointer(const void* ptr, uint64_t* offset) { + if (!ptr) { + *offset = 0; + return; + } + + const char* p_obj = reinterpret_cast<const char*>(ptr); + const char* p_slot = reinterpret_cast<const char*>(offset); + MOJO_DCHECK(p_obj > p_slot); + + *offset = static_cast<uint64_t>(p_obj - p_slot); +} + +const void* DecodePointerRaw(const uint64_t* offset) { + if (!*offset) + return nullptr; + return reinterpret_cast<const char*>(offset) + *offset; +} + +void EncodeHandle(Handle* handle, std::vector<Handle>* handles) { + if (handle->is_valid()) { + handles->push_back(*handle); + handle->set_value(static_cast<MojoHandle>(handles->size() - 1)); + } else { + handle->set_value(kEncodedInvalidHandleValue); + } +} + +void EncodeHandle(Interface_Data* data, std::vector<Handle>* handles) { + EncodeHandle(&data->handle, handles); +} + +void EncodeHandle(MojoHandle* handle, std::vector<Handle>* handles) { + EncodeHandle(reinterpret_cast<Handle*>(handle), handles); +} + +void DecodeHandle(Handle* handle, std::vector<Handle>* handles) { + if (handle->value() == kEncodedInvalidHandleValue) { + *handle = Handle(); + return; + } + MOJO_DCHECK(handle->value() < handles->size()); + // Just leave holes in the vector so we don't screw up other indices. + *handle = FetchAndReset(&handles->at(handle->value())); +} + +void DecodeHandle(Interface_Data* data, std::vector<Handle>* handles) { + DecodeHandle(&data->handle, handles); +} + +void DecodeHandle(MojoHandle* handle, std::vector<Handle>* handles) { + DecodeHandle(reinterpret_cast<Handle*>(handle), handles); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/bindings_serialization.h b/mojo/public/cpp/bindings/lib/bindings_serialization.h new file mode 100644 index 0000000..5e2f9a7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bindings_serialization.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ + +#include <vector> + +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +// Please note that this is a different value than |mojo::kInvalidHandleValue|, +// which is the "decoded" invalid handle. +const MojoHandle kEncodedInvalidHandleValue = static_cast<MojoHandle>(-1); + +size_t Align(size_t size); +char* AlignPointer(char* ptr); + +bool IsAligned(const void* ptr); + +// Pointers are encoded as relative offsets. The offsets are relative to the +// address of where the offset value is stored, such that the pointer may be +// recovered with the expression: +// +// ptr = reinterpret_cast<char*>(offset) + *offset +// +// A null pointer is encoded as an offset value of 0. +// +void EncodePointer(const void* ptr, uint64_t* offset); +// Note: This function doesn't validate the encoded pointer value. +const void* DecodePointerRaw(const uint64_t* offset); + +// Note: This function doesn't validate the encoded pointer value. +template <typename T> +inline void DecodePointer(const uint64_t* offset, T** ptr) { + *ptr = reinterpret_cast<T*>(const_cast<void*>(DecodePointerRaw(offset))); +} + +// Handles are encoded as indices into a vector of handles. These functions +// manipulate the value of |handle|, mapping it to and from an index. + +void EncodeHandle(Handle* handle, std::vector<Handle>* handles); +void EncodeHandle(Interface_Data* data, std::vector<Handle>* handles); +void EncodeHandle(MojoHandle* handle, std::vector<Handle>* handles); +// Note: The following three functions don't validate the encoded handle value. +void DecodeHandle(Handle* handle, std::vector<Handle>* handles); +void DecodeHandle(Interface_Data* data, std::vector<Handle>* handles); +void DecodeHandle(MojoHandle* handle, std::vector<Handle>* handles); + +// The following 2 functions are used to encode/decode all objects (structs and +// arrays) in a consistent manner. + +template <typename T> +inline void Encode(T* obj, std::vector<Handle>* handles) { + if (obj->ptr) + obj->ptr->EncodePointersAndHandles(handles); + EncodePointer(obj->ptr, &obj->offset); +} + +// Note: This function doesn't validate the encoded pointer and handle values. +template <typename T> +inline void Decode(T* obj, std::vector<Handle>* handles) { + DecodePointer(&obj->offset, &obj->ptr); + if (obj->ptr) + obj->ptr->DecodePointersAndHandles(handles); +} + +template <typename T> +inline void InterfacePointerToData(InterfacePtr<T> input, + Interface_Data* output) { + InterfacePtrInfo<T> info = input.PassInterface(); + output->handle = info.PassHandle().release(); + output->version = info.version(); +} + +template <typename T> +inline void InterfaceDataToPointer(Interface_Data* input, + InterfacePtr<T>* output) { + output->Bind(InterfacePtrInfo<T>( + MakeScopedHandle(FetchAndReset(&input->handle)), input->version)); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/bounds_checker.cc b/mojo/public/cpp/bindings/lib/bounds_checker.cc new file mode 100644 index 0000000..5b96b2d --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bounds_checker.cc @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/bounds_checker.h" + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/environment/logging.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +BoundsChecker::BoundsChecker(const void* data, + uint32_t data_num_bytes, + size_t num_handles) + : data_begin_(reinterpret_cast<uintptr_t>(data)), + data_end_(data_begin_ + data_num_bytes), + handle_begin_(0), + handle_end_(static_cast<uint32_t>(num_handles)) { + if (data_end_ < data_begin_) { + // The calculation of |data_end_| overflowed. + // It shouldn't happen but if it does, set the range to empty so + // IsValidRange() and ClaimMemory() always fail. + MOJO_DCHECK(false) << "Not reached"; + data_end_ = data_begin_; + } + if (handle_end_ < num_handles) { + // Assigning |num_handles| to |handle_end_| overflowed. + // It shouldn't happen but if it does, set the handle index range to empty. + MOJO_DCHECK(false) << "Not reached"; + handle_end_ = 0; + } +} + +BoundsChecker::~BoundsChecker() { +} + +bool BoundsChecker::ClaimMemory(const void* position, uint32_t num_bytes) { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + if (!InternalIsValidRange(begin, end)) + return false; + + data_begin_ = end; + return true; +} + +bool BoundsChecker::ClaimHandle(const Handle& encoded_handle) { + uint32_t index = encoded_handle.value(); + if (index == kEncodedInvalidHandleValue) + return true; + + if (index < handle_begin_ || index >= handle_end_) + return false; + + // |index| + 1 shouldn't overflow, because |index| is not the max value of + // uint32_t (it is less than |handle_end_|). + handle_begin_ = index + 1; + return true; +} + +bool BoundsChecker::IsValidRange(const void* position, + uint32_t num_bytes) const { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + return InternalIsValidRange(begin, end); +} + +bool BoundsChecker::InternalIsValidRange(uintptr_t begin, uintptr_t end) const { + return end > begin && begin >= data_begin_ && end <= data_end_; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/bounds_checker.h b/mojo/public/cpp/bindings/lib/bounds_checker.h new file mode 100644 index 0000000..f0520be --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bounds_checker.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +class Handle; + +namespace internal { + +// BoundsChecker is used to validate object sizes, pointers and handle indices +// for payload of incoming messages. +class BoundsChecker { + public: + // [data, data + data_num_bytes) specifies the initial valid memory range. + // [0, num_handles) specifies the initial valid range of handle indices. + BoundsChecker(const void* data, uint32_t data_num_bytes, size_t num_handles); + + ~BoundsChecker(); + + // Claims the specified memory range. + // The method succeeds if the range is valid to claim. (Please see + // the comments for IsValidRange().) + // On success, the valid memory range is shrinked to begin right after the end + // of the claimed range. + bool ClaimMemory(const void* position, uint32_t num_bytes); + + // Claims the specified encoded handle (which is basically a handle index). + // The method succeeds if: + // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|. + // - the handle is contained inside the valid range of handle indices. In this + // case, the valid range is shinked to begin right after the claimed handle. + bool ClaimHandle(const Handle& encoded_handle); + + // Returns true if the specified range is not empty, and the range is + // contained inside the valid memory range. + bool IsValidRange(const void* position, uint32_t num_bytes) const; + + private: + bool InternalIsValidRange(uintptr_t begin, uintptr_t end) const; + + // [data_begin_, data_end_) is the valid memory range. + uintptr_t data_begin_; + uintptr_t data_end_; + + // [handle_begin_, handle_end_) is the valid handle index range. + uint32_t handle_begin_; + uint32_t handle_end_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChecker); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ diff --git a/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h new file mode 100644 index 0000000..c3b570e --- /dev/null +++ b/mojo/public/cpp/bindings/lib/buffer.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ + +#include <stddef.h> + +namespace mojo { +namespace internal { + +// Buffer provides a way to allocate memory. Allocations are 8-byte aligned and +// zero-initialized. Allocations remain valid for the lifetime of the Buffer. +class Buffer { + public: + virtual ~Buffer() {} + virtual void* Allocate(size_t num_bytes) = 0; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/callback_internal.h b/mojo/public/cpp/bindings/lib/callback_internal.h new file mode 100644 index 0000000..9df5b40a7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/callback_internal.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +class String; + +namespace internal { + +template <typename T> +struct Callback_ParamTraits { + typedef T ForwardType; +}; + +template <> +struct Callback_ParamTraits<String> { + typedef const String& ForwardType; +}; + +template <typename T, typename... Args> +struct HasCompatibleCallOperator { + // This template's second parameter is the signature of the operator() + // overload we want to try to detect: + // void operator()(Args...) const; + template <typename U, + void (U::*)( + typename internal::Callback_ParamTraits<Args>::ForwardType...) + const> + struct TestType {}; + + // This matches type U if it has a call operator with the + // expected signature. + template <typename U> + static YesType Test(TestType<U, &U::operator()>*); + + // This matches anything else. + template <typename U> + static NoType Test(...); + + // HasCompatibleCallOperator<T, Args...>::value will be true if T has a + // compatible call operator. + enum { value = (sizeof(Test<T>(nullptr)) == sizeof(YesType)) }; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc new file mode 100644 index 0000000..5bd94eb --- /dev/null +++ b/mojo/public/cpp/bindings/lib/connector.cc @@ -0,0 +1,259 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/lib/connector.h" + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +// ---------------------------------------------------------------------------- + +Connector::Connector(ScopedMessagePipeHandle message_pipe, + const MojoAsyncWaiter* waiter) + : waiter_(waiter), + message_pipe_(message_pipe.Pass()), + incoming_receiver_(nullptr), + async_wait_id_(0), + error_(false), + drop_writes_(false), + enforce_errors_from_incoming_receiver_(true), + paused_(false), + destroyed_flag_(nullptr) { + // Even though we don't have an incoming receiver, we still want to monitor + // the message pipe to know if is closed or encounters an error. + WaitToReadMore(); +} + +Connector::~Connector() { + if (destroyed_flag_) + *destroyed_flag_ = true; + + CancelWait(); +} + +void Connector::CloseMessagePipe() { + CancelWait(); + Close(message_pipe_.Pass()); +} + +ScopedMessagePipeHandle Connector::PassMessagePipe() { + CancelWait(); + return message_pipe_.Pass(); +} + +void Connector::RaiseError() { + HandleError(true, true); +} + +bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { + if (error_) + return false; + + ResumeIncomingMethodCallProcessing(); + + MojoResult rv = + Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, deadline, nullptr); + if (rv == MOJO_RESULT_SHOULD_WAIT || rv == MOJO_RESULT_DEADLINE_EXCEEDED) + return false; + if (rv != MOJO_RESULT_OK) { + // Users that call WaitForIncomingMessage() should expect their code to be + // re-entered, so we call the error handler synchronously. + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + mojo_ignore_result(ReadSingleMessage(&rv)); + return (rv == MOJO_RESULT_OK); +} + +void Connector::PauseIncomingMethodCallProcessing() { + if (paused_) + return; + + paused_ = true; + CancelWait(); +} + +void Connector::ResumeIncomingMethodCallProcessing() { + if (!paused_) + return; + + paused_ = false; + WaitToReadMore(); +} + +bool Connector::Accept(Message* message) { + if (error_) + return false; + + MOJO_CHECK(message_pipe_.is_valid()); + if (drop_writes_) + return true; + + MojoResult rv = + WriteMessageRaw(message_pipe_.get(), + message->data(), + message->data_num_bytes(), + message->mutable_handles()->empty() + ? nullptr + : reinterpret_cast<const MojoHandle*>( + &message->mutable_handles()->front()), + static_cast<uint32_t>(message->mutable_handles()->size()), + MOJO_WRITE_MESSAGE_FLAG_NONE); + + switch (rv) { + case MOJO_RESULT_OK: + // The handles were successfully transferred, so we don't need the message + // to track their lifetime any longer. + message->mutable_handles()->clear(); + break; + case MOJO_RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any backlog + // of incoming messages before regarding the message pipe as closed. + drop_writes_ = true; + break; + case MOJO_RESULT_BUSY: + // We'd get a "busy" result if one of the message's handles is: + // - |message_pipe_|'s own handle; + // - simultaneously being used on another thread; or + // - in a "busy" state that prohibits it from being transferred (e.g., + // a data pipe handle in the middle of a two-phase read/write, + // regardless of which thread that two-phase read/write is happening + // on). + // TODO(vtl): I wonder if this should be a |MOJO_DCHECK()|. (But, until + // crbug.com/389666, etc. are resolved, this will make tests fail quickly + // rather than hanging.) + MOJO_CHECK(false) << "Race condition or other bug detected"; + return false; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; +} + +// static +void Connector::CallOnHandleReady(void* closure, MojoResult result) { + Connector* self = static_cast<Connector*>(closure); + self->OnHandleReady(result); +} + +void Connector::OnHandleReady(MojoResult result) { + MOJO_CHECK(async_wait_id_ != 0); + async_wait_id_ = 0; + if (result != MOJO_RESULT_OK) { + HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false); + return; + } + ReadAllAvailableMessages(); + // At this point, this object might have been deleted. Return. +} + +void Connector::WaitToReadMore() { + MOJO_CHECK(!async_wait_id_); + async_wait_id_ = waiter_->AsyncWait(message_pipe_.get().value(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + &Connector::CallOnHandleReady, + this); +} + +bool Connector::ReadSingleMessage(MojoResult* read_result) { + bool receiver_result = false; + + // Detect if |this| was destroyed during message dispatch. Allow for the + // possibility of re-entering ReadMore() through message dispatch. + bool was_destroyed_during_dispatch = false; + bool* previous_destroyed_flag = destroyed_flag_; + destroyed_flag_ = &was_destroyed_during_dispatch; + + MojoResult rv = ReadAndDispatchMessage( + message_pipe_.get(), incoming_receiver_, &receiver_result); + if (read_result) + *read_result = rv; + + if (was_destroyed_during_dispatch) { + if (previous_destroyed_flag) + *previous_destroyed_flag = true; // Propagate flag. + return false; + } + destroyed_flag_ = previous_destroyed_flag; + + if (rv == MOJO_RESULT_SHOULD_WAIT) + return true; + + if (rv != MOJO_RESULT_OK) { + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + + if (enforce_errors_from_incoming_receiver_ && !receiver_result) { + HandleError(true, false); + return false; + } + return true; +} + +void Connector::ReadAllAvailableMessages() { + while (!error_) { + MojoResult rv; + + // Return immediately if |this| was destroyed. Do not touch any members! + if (!ReadSingleMessage(&rv)) + return; + + if (rv == MOJO_RESULT_SHOULD_WAIT) { + WaitToReadMore(); + break; + } + } +} + +void Connector::CancelWait() { + if (!async_wait_id_) + return; + + waiter_->CancelWait(async_wait_id_); + async_wait_id_ = 0; +} + +void Connector::HandleError(bool force_pipe_reset, bool force_async_handler) { + if (error_ || !message_pipe_.is_valid()) + return; + + if (!force_pipe_reset && force_async_handler) + force_pipe_reset = true; + + if (paused_) { + // If the user has paused receiving messages, we shouldn't call the error + // handler right away. We need to wait until the user starts receiving + // messages again. + force_async_handler = true; + } + + if (force_pipe_reset) { + CloseMessagePipe(); + MessagePipe dummy_pipe; + message_pipe_ = dummy_pipe.handle0.Pass(); + } else { + CancelWait(); + } + + if (force_async_handler) { + // |dummy_pipe.handle1| has been destructed. Reading the pipe will + // eventually cause a read error on |message_pipe_| and set error state. + if (!paused_) + WaitToReadMore(); + } else { + error_ = true; + connection_error_handler_.Run(); + } +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/connector.h b/mojo/public/cpp/bindings/lib/connector.h new file mode 100644 index 0000000..185a984 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/connector.h @@ -0,0 +1,142 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +class ErrorHandler; + +namespace internal { + +// The Connector class is responsible for performing read/write operations on a +// MessagePipe. It writes messages it receives through the MessageReceiver +// interface that it subclasses, and it forwards messages it reads through the +// MessageReceiver interface assigned as its incoming receiver. +// +// NOTE: MessagePipe I/O is non-blocking. +// +class Connector : public MessageReceiver { + public: + // The Connector takes ownership of |message_pipe|. + explicit Connector( + ScopedMessagePipeHandle message_pipe, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()); + ~Connector() override; + + // Sets the receiver to handle messages read from the message pipe. The + // Connector will read messages from the pipe regardless of whether or not an + // incoming receiver has been set. + void set_incoming_receiver(MessageReceiver* receiver) { + incoming_receiver_ = receiver; + } + + // Errors from incoming receivers will force the connector into an error + // state, where no more messages will be processed. This method is used + // during testing to prevent that from happening. + void set_enforce_errors_from_incoming_receiver(bool enforce) { + enforce_errors_from_incoming_receiver_ = enforce; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_connection_error_handler(const Closure& error_handler) { + connection_error_handler_ = error_handler; + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { return error_; } + + // Closes the pipe. The connector is put into a quiescent state. + // + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe(); + + // Releases the pipe. Connector is put into a quiescent state. + ScopedMessagePipeHandle PassMessagePipe(); + + // Enters the error state. The upper layer may do this for unrecoverable + // issues such as invalid messages are received. If a connection error handler + // has been set, it will be called asynchronously. + // + // It is a no-op if the connector is already in the error state or there isn't + // a bound message pipe. Otherwise, it closes the message pipe, which notifies + // the other end and also prevents potential danger (say, the caller raises + // an error because it believes the other end is malicious). In order to + // appear to the user that the connector still binds to a message pipe, it + // creates a new message pipe, closes one end and binds to the other. + void RaiseError(); + + // Is the connector bound to a MessagePipe handle? + bool is_valid() const { return message_pipe_.is_valid(); } + + // Waits for the next message on the pipe, blocking until one arrives, + // |deadline| elapses, or an error happens. Returns |true| if a message has + // been delivered, |false| otherwise. + bool WaitForIncomingMessage(MojoDeadline deadline); + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + MessagePipeHandle handle() const { return message_pipe_.get(); } + + private: + static void CallOnHandleReady(void* closure, MojoResult result); + void OnHandleReady(MojoResult result); + + void WaitToReadMore(); + + // Returns false if |this| was destroyed during message dispatch. + MOJO_WARN_UNUSED_RESULT bool ReadSingleMessage(MojoResult* read_result); + + // |this| can be destroyed during message dispatch. + void ReadAllAvailableMessages(); + + // If |force_pipe_reset| is true, this method replaces the existing + // |message_pipe_| with a dummy message pipe handle (whose peer is closed). + // If |force_async_handler| is true, |connection_error_handler_| is called + // asynchronously. + void HandleError(bool force_pipe_reset, bool force_async_handler); + + // Cancels any calls made to |waiter_|. + void CancelWait(); + + Closure connection_error_handler_; + const MojoAsyncWaiter* waiter_; + + ScopedMessagePipeHandle message_pipe_; + MessageReceiver* incoming_receiver_; + + MojoAsyncWaitID async_wait_id_; + bool error_; + bool drop_writes_; + bool enforce_errors_from_incoming_receiver_; + + bool paused_; + + // If non-null, this will be set to true when the Connector is destroyed. We + // use this flag to allow for the Connector to be destroyed as a side-effect + // of dispatching an incoming message. + bool* destroyed_flag_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Connector); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc new file mode 100644 index 0000000..5113bb0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc @@ -0,0 +1,81 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/lib/control_message_handler.h" + +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/environment/logging.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +// static +bool ControlMessageHandler::IsControlMessage(const Message* message) { + return message->header()->name == kRunMessageId || + message->header()->name == kRunOrClosePipeMessageId; +} + +ControlMessageHandler::ControlMessageHandler(uint32_t interface_version) + : interface_version_(interface_version) { +} + +ControlMessageHandler::~ControlMessageHandler() { +} + +bool ControlMessageHandler::Accept(Message* message) { + if (message->header()->name == kRunOrClosePipeMessageId) + return RunOrClosePipe(message); + + MOJO_NOTREACHED(); + return false; +} + +bool ControlMessageHandler::AcceptWithResponder( + Message* message, + MessageReceiverWithStatus* responder) { + if (message->header()->name == kRunMessageId) + return Run(message, responder); + + MOJO_NOTREACHED(); + return false; +} + +bool ControlMessageHandler::Run(Message* message, + MessageReceiverWithStatus* responder) { + RunResponseMessageParamsPtr response_params_ptr( + RunResponseMessageParams::New()); + response_params_ptr->reserved0 = 16u; + response_params_ptr->reserved1 = 0u; + response_params_ptr->query_version_result = QueryVersionResult::New(); + response_params_ptr->query_version_result->version = interface_version_; + + size_t size = GetSerializedSize_(response_params_ptr); + ResponseMessageBuilder builder(kRunMessageId, size, message->request_id()); + + RunResponseMessageParams_Data* response_params = nullptr; + Serialize_(response_params_ptr.Pass(), builder.buffer(), &response_params); + response_params->EncodePointersAndHandles( + builder.message()->mutable_handles()); + bool ok = responder->Accept(builder.message()); + MOJO_ALLOW_UNUSED_LOCAL(ok); + delete responder; + + return true; +} + +bool ControlMessageHandler::RunOrClosePipe(Message* message) { + RunOrClosePipeMessageParams_Data* params = + reinterpret_cast<RunOrClosePipeMessageParams_Data*>( + message->mutable_payload()); + params->DecodePointersAndHandles(message->mutable_handles()); + + RunOrClosePipeMessageParamsPtr params_ptr; + Deserialize_(params, ¶ms_ptr); + + return interface_version_ >= params_ptr->require_version->version; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h new file mode 100644 index 0000000..4a2fed5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// Handlers for request messages defined in interface_control_messages.mojom. +class ControlMessageHandler : public MessageReceiverWithResponderStatus { + public: + static bool IsControlMessage(const Message* message); + + explicit ControlMessageHandler(uint32_t interface_version); + ~ControlMessageHandler() override; + + // Call the following methods only if IsControlMessage() returned true. + bool Accept(Message* message) override; + // Takes ownership of |responder|. + bool AcceptWithResponder(Message* message, + MessageReceiverWithStatus* responder) override; + + private: + bool Run(Message* message, MessageReceiverWithStatus* responder); + bool RunOrClosePipe(Message* message); + + uint32_t interface_version_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ControlMessageHandler); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc new file mode 100644 index 0000000..ad729c5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc @@ -0,0 +1,100 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/lib/control_message_proxy.h" + +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +namespace { + +using RunCallback = Callback<void(QueryVersionResultPtr)>; + +class RunResponseForwardToCallback : public MessageReceiver { + public: + RunResponseForwardToCallback(const RunCallback& callback) + : callback_(callback) {} + bool Accept(Message* message) override; + + private: + RunCallback callback_; + MOJO_DISALLOW_COPY_AND_ASSIGN(RunResponseForwardToCallback); +}; + +bool RunResponseForwardToCallback::Accept(Message* message) { + RunResponseMessageParams_Data* params = + reinterpret_cast<RunResponseMessageParams_Data*>( + message->mutable_payload()); + params->DecodePointersAndHandles(message->mutable_handles()); + + RunResponseMessageParamsPtr params_ptr; + Deserialize_(params, ¶ms_ptr); + + callback_.Run(params_ptr->query_version_result.Pass()); + return true; +} + +void SendRunMessage(MessageReceiverWithResponder* receiver, + QueryVersionPtr query_version, + const RunCallback& callback) { + RunMessageParamsPtr params_ptr(RunMessageParams::New()); + params_ptr->reserved0 = 16u; + params_ptr->reserved1 = 0u; + params_ptr->query_version = query_version.Pass(); + + size_t size = GetSerializedSize_(params_ptr); + RequestMessageBuilder builder(kRunMessageId, size); + + RunMessageParams_Data* params = nullptr; + Serialize_(params_ptr.Pass(), builder.buffer(), ¶ms); + params->EncodePointersAndHandles(builder.message()->mutable_handles()); + MessageReceiver* responder = new RunResponseForwardToCallback(callback); + if (!receiver->AcceptWithResponder(builder.message(), responder)) + delete responder; +} + +void SendRunOrClosePipeMessage(MessageReceiverWithResponder* receiver, + RequireVersionPtr require_version) { + RunOrClosePipeMessageParamsPtr params_ptr(RunOrClosePipeMessageParams::New()); + params_ptr->reserved0 = 16u; + params_ptr->reserved1 = 0u; + params_ptr->require_version = require_version.Pass(); + + size_t size = GetSerializedSize_(params_ptr); + MessageBuilder builder(kRunOrClosePipeMessageId, size); + + RunOrClosePipeMessageParams_Data* params = nullptr; + Serialize_(params_ptr.Pass(), builder.buffer(), ¶ms); + params->EncodePointersAndHandles(builder.message()->mutable_handles()); + bool ok = receiver->Accept(builder.message()); + MOJO_ALLOW_UNUSED_LOCAL(ok); +} + +} // namespace + +ControlMessageProxy::ControlMessageProxy(MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +void ControlMessageProxy::QueryVersion( + const Callback<void(uint32_t)>& callback) { + auto run_callback = [callback](QueryVersionResultPtr query_version_result) { + callback.Run(query_version_result->version); + }; + SendRunMessage(receiver_, QueryVersion::New(), run_callback); +} + +void ControlMessageProxy::RequireVersion(uint32_t version) { + RequireVersionPtr require_version(RequireVersion::New()); + require_version->version = version; + SendRunOrClosePipeMessage(receiver_, require_version.Pass()); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.h b/mojo/public/cpp/bindings/lib/control_message_proxy.h new file mode 100644 index 0000000..5b0f018 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.h @@ -0,0 +1,38 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +class MessageReceiverWithResponder; + +namespace internal { + +// Proxy for request messages defined in interface_control_messages.mojom. +class ControlMessageProxy { + public: + // Doesn't take ownership of |receiver|. It must outlive this object. + explicit ControlMessageProxy(MessageReceiverWithResponder* receiver); + + void QueryVersion(const Callback<void(uint32_t)>& callback); + void RequireVersion(uint32_t version); + + protected: + // Not owned. + MessageReceiverWithResponder* receiver_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ControlMessageProxy); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ diff --git a/mojo/public/cpp/bindings/lib/filter_chain.cc b/mojo/public/cpp/bindings/lib/filter_chain.cc new file mode 100644 index 0000000..d32eb78 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/filter_chain.cc @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/filter_chain.h" + +#include <algorithm> + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) { +} + +FilterChain::FilterChain(FilterChain&& other) : sink_(other.sink_) { + other.sink_ = nullptr; + filters_.swap(other.filters_); +} + +FilterChain& FilterChain::operator=(FilterChain&& other) { + std::swap(sink_, other.sink_); + filters_.swap(other.filters_); + return *this; +} + +FilterChain::~FilterChain() { + for (std::vector<MessageFilter*>::iterator iter = filters_.begin(); + iter != filters_.end(); + ++iter) { + delete *iter; + } +} + +void FilterChain::SetSink(MessageReceiver* sink) { + MOJO_DCHECK(!sink_); + sink_ = sink; + if (!filters_.empty()) + filters_.back()->set_sink(sink); +} + +MessageReceiver* FilterChain::GetHead() { + MOJO_DCHECK(sink_); + return filters_.empty() ? sink_ : filters_.front(); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/filter_chain.h b/mojo/public/cpp/bindings/lib/filter_chain.h new file mode 100644 index 0000000..bd7f9f5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/filter_chain.h @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ + +#include <vector> + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +class FilterChain { + MOJO_MOVE_ONLY_TYPE(FilterChain) + + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit FilterChain(MessageReceiver* sink = nullptr); + + FilterChain(FilterChain&& other); + FilterChain& operator=(FilterChain&& other); + ~FilterChain(); + + template <typename FilterType> + inline void Append(); + + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + void SetSink(MessageReceiver* sink); + + // Returns a receiver to accept messages. Messages flow through all filters in + // the same order as they were appended to the chain. If all filters allow a + // message to pass, it will be forwarded to |sink_|. + // The returned value is invalidated when this object goes away. + MessageReceiver* GetHead(); + + private: + // Owned by this object. + std::vector<MessageFilter*> filters_; + + MessageReceiver* sink_; +}; + +template <typename FilterType> +inline void FilterChain::Append() { + FilterType* filter = new FilterType(sink_); + if (!filters_.empty()) + filters_.back()->set_sink(filter); + filters_.push_back(filter); +} + +template <> +inline void FilterChain::Append<PassThroughFilter>() { +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc new file mode 100644 index 0000000..c81fc6e --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.cc @@ -0,0 +1,60 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/fixed_buffer.h" + +#include <stdlib.h> + +#include <algorithm> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +FixedBuffer::FixedBuffer() : ptr_(nullptr), cursor_(0), size_(0) {} + +void FixedBuffer::Initialize(void* memory, size_t size) { + MOJO_DCHECK(size == internal::Align(size)); + + ptr_ = static_cast<char*>(memory); + cursor_ = 0; + size_ = size; +} + +void* FixedBuffer::Allocate(size_t delta) { + delta = internal::Align(delta); + + if (delta == 0 || delta > size_ - cursor_) { + MOJO_DCHECK(false) << "Not reached"; + return nullptr; + } + + char* result = ptr_ + cursor_; + cursor_ += delta; + + return result; +} + +FixedBufferForTesting::FixedBufferForTesting(size_t size) { + size_ = internal::Align(size); + // Use calloc here to ensure all message memory is zero'd out. + ptr_ = static_cast<char*>(calloc(size_, 1)); +} + +FixedBufferForTesting::~FixedBufferForTesting() { + free(ptr_); +} + +void* FixedBufferForTesting::Leak() { + char* ptr = ptr_; + ptr_ = nullptr; + cursor_ = 0; + size_ = 0; + return ptr; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.h b/mojo/public/cpp/bindings/lib/fixed_buffer.h new file mode 100644 index 0000000..83eaf97 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.h @@ -0,0 +1,78 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ + +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// FixedBuffer provides a simple way to allocate objects within a fixed chunk +// of memory. Objects are allocated by calling the |Allocate| method, which +// extends the buffer accordingly. Objects allocated in this way are not freed +// explicitly. Instead, they remain valid so long as the FixedBuffer remains +// valid. The Leak method may be used to steal the underlying memory from the +// FixedBuffer. +// +// Typical usage: +// +// { +// FixedBuffer buf(8 + 8); +// +// int* a = static_cast<int*>(buf->Allocate(sizeof(int))); +// *a = 2; +// +// double* b = static_cast<double*>(buf->Allocate(sizeof(double))); +// *b = 3.14f; +// +// void* data = buf.Leak(); +// Process(data); +// +// free(data); +// } + +class FixedBuffer : public Buffer { + public: + FixedBuffer(); + + // |size| should be aligned using internal::Align. + void Initialize(void* memory, size_t size); + + size_t size() const { return size_; } + + // Grows the buffer by |num_bytes| and returns a pointer to the start of the + // addition. The resulting address is 8-byte aligned, and the content of the + // memory is zero-filled. + void* Allocate(size_t num_bytes) override; + + protected: + char* ptr_; + size_t cursor_; + size_t size_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(FixedBuffer); +}; + +class FixedBufferForTesting : public FixedBuffer { + public: + explicit FixedBufferForTesting(size_t size); + ~FixedBufferForTesting() override; + + // Returns the internal memory owned by the Buffer to the caller. The Buffer + // relinquishes its pointer, effectively resetting the state of the Buffer + // and leaving the caller responsible for freeing the returned memory address + // when no longer needed. + void* Leak(); + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(FixedBufferForTesting); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_internal.h b/mojo/public/cpp/bindings/lib/interface_ptr_internal.h new file mode 100644 index 0000000..1c2cad8 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/interface_ptr_internal.h @@ -0,0 +1,174 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ + +#include <algorithm> // For |std::swap()|. + +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/environment/logging.h" + +struct MojoAsyncWaiter; + +namespace mojo { +namespace internal { + +template <typename Interface> +class InterfacePtrState { + public: + InterfacePtrState() + : proxy_(nullptr), router_(nullptr), waiter_(nullptr), version_(0u) {} + + ~InterfacePtrState() { + // Destruction order matters here. We delete |proxy_| first, even though + // |router_| may have a reference to it, so that destructors for any request + // callbacks still pending can interact with the InterfacePtr. + delete proxy_; + delete router_; + } + + Interface* instance() { + ConfigureProxyIfNecessary(); + + // This will be null if the object is not bound. + return proxy_; + } + + uint32_t version() const { return version_; } + + void QueryVersion(const Callback<void(uint32_t)>& callback) { + ConfigureProxyIfNecessary(); + + // It is safe to capture |this| because the callback won't be run after this + // object goes away. + auto callback_wrapper = [this, callback](uint32_t version) { + this->version_ = version; + callback.Run(version); + }; + + // Do a static cast in case the interface contains methods with the same + // name. + static_cast<ControlMessageProxy*>(proxy_)->QueryVersion(callback_wrapper); + } + + void RequireVersion(uint32_t version) { + ConfigureProxyIfNecessary(); + + if (version <= version_) + return; + + version_ = version; + // Do a static cast in case the interface contains methods with the same + // name. + static_cast<ControlMessageProxy*>(proxy_)->RequireVersion(version); + } + + void Swap(InterfacePtrState* other) { + using std::swap; + swap(other->proxy_, proxy_); + swap(other->router_, router_); + handle_.swap(other->handle_); + swap(other->waiter_, waiter_); + swap(other->version_, version_); + } + + void Bind(InterfacePtrInfo<Interface> info, const MojoAsyncWaiter* waiter) { + MOJO_DCHECK(!proxy_); + MOJO_DCHECK(!router_); + MOJO_DCHECK(!handle_.is_valid()); + MOJO_DCHECK(!waiter_); + MOJO_DCHECK(version_ == 0u); + MOJO_DCHECK(info.is_valid()); + + handle_ = info.PassHandle(); + waiter_ = waiter; + version_ = info.version(); + } + + bool WaitForIncomingResponse() { + ConfigureProxyIfNecessary(); + + MOJO_DCHECK(router_); + return router_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + } + + // After this method is called, the object is in an invalid state and + // shouldn't be reused. + InterfacePtrInfo<Interface> PassInterface() { + return InterfacePtrInfo<Interface>( + router_ ? router_->PassMessagePipe() : handle_.Pass(), version_); + } + + bool is_bound() const { return handle_.is_valid() || router_; } + + bool encountered_error() const { + return router_ ? router_->encountered_error() : false; + } + + void set_connection_error_handler(const Closure& error_handler) { + ConfigureProxyIfNecessary(); + + MOJO_DCHECK(router_); + router_->set_connection_error_handler(error_handler); + } + + // Returns true if bound and awaiting a response to a message. + bool has_pending_callbacks() const { + return router_ && router_->has_pending_responders(); + } + + Router* router_for_testing() { + ConfigureProxyIfNecessary(); + return router_; + } + + private: + using Proxy = typename Interface::Proxy_; + + void ConfigureProxyIfNecessary() { + // The proxy has been configured. + if (proxy_) { + MOJO_DCHECK(router_); + return; + } + // The object hasn't been bound. + if (!waiter_) { + MOJO_DCHECK(!handle_.is_valid()); + return; + } + + FilterChain filters; + filters.Append<MessageHeaderValidator>(); + filters.Append<typename Interface::ResponseValidator_>(); + + router_ = new Router(handle_.Pass(), filters.Pass(), waiter_); + waiter_ = nullptr; + + proxy_ = new Proxy(router_); + } + + Proxy* proxy_; + Router* router_; + + // |proxy_| and |router_| are not initialized until read/write with the + // message pipe handle is needed. |handle_| and |waiter_| are valid between + // the Bind() call and the initialization of |proxy_| and |router_|. + ScopedMessagePipeHandle handle_; + const MojoAsyncWaiter* waiter_; + + uint32_t version_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(InterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/map_data_internal.h b/mojo/public/cpp/bindings/lib/map_data_internal.h new file mode 100644 index 0000000..8315dbc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_data_internal.h @@ -0,0 +1,141 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { + +inline const ArrayValidateParams* GetMapKeyValidateParamsDefault() { + // The memory allocated here never gets released to not cause an exit time + // destructor. + static const ArrayValidateParams* validate_params = + new ArrayValidateParams(0, false, nullptr); + return validate_params; +} + +inline const ArrayValidateParams* GetMapKeyValidateParamsForStrings() { + // The memory allocated here never gets released to not cause an exit time + // destructor. + static const ArrayValidateParams* validate_params = new ArrayValidateParams( + 0, false, new ArrayValidateParams(0, false, nullptr)); + return validate_params; +} + +template <typename MapKey> +struct MapKeyValidateParamsFactory { + static const ArrayValidateParams* Get() { + return GetMapKeyValidateParamsDefault(); + } +}; + +// For non-nullable strings only. (Which is OK; map keys can't be null.) +template <> +struct MapKeyValidateParamsFactory<mojo::internal::Array_Data<char>*> { + static const ArrayValidateParams* Get() { + return GetMapKeyValidateParamsForStrings(); + } +}; + +// Map serializes into a struct which has two arrays as struct fields, the keys +// and the values. +template <typename Key, typename Value> +class Map_Data { + public: + static Map_Data* New(Buffer* buf) { + return new (buf->Allocate(sizeof(Map_Data))) Map_Data(); + } + + static bool Validate(const void* data, + BoundsChecker* bounds_checker, + const ArrayValidateParams* value_validate_params) { + if (!data) + return true; + + if (!ValidateStructHeaderAndClaimMemory(data, bounds_checker)) + return false; + + const Map_Data* object = static_cast<const Map_Data*>(data); + if (object->header_.num_bytes != sizeof(Map_Data) || + object->header_.version != 0) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!ValidateEncodedPointer(&object->keys.offset)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_POINTER); + return false; + } + if (!object->keys.offset) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null key array in map struct"); + return false; + } + const ArrayValidateParams* key_validate_params = + MapKeyValidateParamsFactory<Key>::Get(); + if (!Array_Data<Key>::Validate(DecodePointerRaw(&object->keys.offset), + bounds_checker, key_validate_params)) { + return false; + } + + if (!ValidateEncodedPointer(&object->values.offset)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_POINTER); + return false; + } + if (!object->values.offset) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null value array in map struct"); + return false; + } + if (!Array_Data<Value>::Validate(DecodePointerRaw(&object->values.offset), + bounds_checker, value_validate_params)) { + return false; + } + + const ArrayHeader* key_header = + static_cast<const ArrayHeader*>(DecodePointerRaw(&object->keys.offset)); + const ArrayHeader* value_header = static_cast<const ArrayHeader*>( + DecodePointerRaw(&object->values.offset)); + if (key_header->num_elements != value_header->num_elements) { + ReportValidationError(VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP); + return false; + } + + return true; + } + + StructHeader header_; + + ArrayPointer<Key> keys; + ArrayPointer<Value> values; + + void EncodePointersAndHandles(std::vector<mojo::Handle>* handles) { + Encode(&keys, handles); + Encode(&values, handles); + } + + void DecodePointersAndHandles(std::vector<mojo::Handle>* handles) { + Decode(&keys, handles); + Decode(&values, handles); + } + + private: + Map_Data() { + header_.num_bytes = sizeof(*this); + header_.version = 0; + } + ~Map_Data() = delete; +}; +static_assert(sizeof(Map_Data<char, char>) == 24, "Bad sizeof(Map_Data)"); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/map_internal.h b/mojo/public/cpp/bindings/lib/map_internal.h new file mode 100644 index 0000000..84f927c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_internal.h @@ -0,0 +1,220 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_INTERNAL_H_ + +#include <map> + +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template <typename Key, typename Value, bool kValueIsMoveOnlyType> +struct MapTraits {}; + +// Defines traits of a map for which Value is not a move-only type. +template <typename Key, typename Value> +struct MapTraits<Key, Value, false> { + // Map keys can't be move only types. + static_assert(!internal::IsMoveOnlyType<Key>::value, + "Map keys cannot be move only types."); + + typedef Key KeyStorageType; + typedef Key& KeyRefType; + typedef const Key& KeyConstRefType; + typedef KeyConstRefType KeyForwardType; + + typedef Value ValueStorageType; + typedef Value& ValueRefType; + typedef const Value& ValueConstRefType; + typedef ValueConstRefType ValueForwardType; + + static inline void InitializeFrom( + std::map<KeyStorageType, ValueStorageType>* m, + mojo::Array<Key> keys, + mojo::Array<Value> values) { + for (size_t i = 0; i < keys.size(); ++i) + Insert(m, keys[i], values[i]); + } + static inline void Decompose(std::map<KeyStorageType, ValueStorageType>* m, + mojo::Array<Key>* keys, + mojo::Array<Value>* values) { + keys->resize(m->size()); + values->resize(m->size()); + int i = 0; + for (typename std::map<KeyStorageType, ValueStorageType>::iterator + it = m->begin(); + it != m->end(); + ++it, ++i) { + (*keys)[i] = it->first; + (*values)[i] = it->second; + } + } + static inline void Finalize(std::map<KeyStorageType, ValueStorageType>* m) {} + static inline ValueRefType at(std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // We don't have C++11 library support yet, so we have to emulate the crash + // on a non-existent key. + auto it = m->find(key); + MOJO_CHECK(it != m->end()); + return it->second; + } + static inline ValueConstRefType at( + const std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // We don't have C++11 library support yet, so we have to emulate the crash + // on a non-existent key. + auto it = m->find(key); + MOJO_CHECK(it != m->end()); + return it->second; + } + static inline ValueRefType GetOrInsert( + std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // This is the backing for the index operator (operator[]). + return (*m)[key]; + } + static inline void Insert(std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key, + ValueForwardType value) { + m->insert(std::make_pair(key, value)); + } + static inline KeyConstRefType GetKey( + const typename std::map<KeyStorageType, ValueStorageType>::const_iterator& + it) { + return it->first; + } + static inline ValueConstRefType GetValue( + const typename std::map<KeyStorageType, ValueStorageType>::const_iterator& + it) { + return it->second; + } + static inline ValueRefType GetValue( + const typename std::map<KeyStorageType, ValueStorageType>::iterator& it) { + return it->second; + } + static inline void Clone( + const std::map<KeyStorageType, ValueStorageType>& src, + std::map<KeyStorageType, ValueStorageType>* dst) { + dst->clear(); + for (auto it = src.begin(); it != src.end(); ++it) + dst->insert(*it); + } +}; + +// Defines traits of a map for which Value is a move-only type. +template <typename Key, typename Value> +struct MapTraits<Key, Value, true> { + // Map keys can't be move only types. + static_assert(!internal::IsMoveOnlyType<Key>::value, + "Map keys cannot be move only types."); + + typedef Key KeyStorageType; + typedef Key& KeyRefType; + typedef const Key& KeyConstRefType; + typedef KeyConstRefType KeyForwardType; + + struct ValueStorageType { + // Make 8-byte aligned. + char buf[sizeof(Value) + (8 - (sizeof(Value) % 8)) % 8]; + }; + typedef Value& ValueRefType; + typedef const Value& ValueConstRefType; + typedef Value ValueForwardType; + + static inline void InitializeFrom( + std::map<KeyStorageType, ValueStorageType>* m, + mojo::Array<Key> keys, + mojo::Array<Value> values) { + for (size_t i = 0; i < keys.size(); ++i) + Insert(m, keys[i], values[i]); + } + static inline void Decompose(std::map<KeyStorageType, ValueStorageType>* m, + mojo::Array<Key>* keys, + mojo::Array<Value>* values) { + keys->resize(m->size()); + values->resize(m->size()); + int i = 0; + for (typename std::map<KeyStorageType, ValueStorageType>::iterator + it = m->begin(); + it != m->end(); + ++it, ++i) { + (*keys)[i] = it->first; + (*values)[i] = GetValue(it).Pass(); + } + } + static inline void Finalize(std::map<KeyStorageType, ValueStorageType>* m) { + for (auto& pair : *m) + reinterpret_cast<Value*>(pair.second.buf)->~Value(); + } + static inline ValueRefType at(std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // We don't have C++11 library support yet, so we have to emulate the crash + // on a non-existent key. + auto it = m->find(key); + MOJO_CHECK(it != m->end()); + return GetValue(it); + } + static inline ValueConstRefType at( + const std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // We don't have C++11 library support yet, so we have to emulate the crash + // on a non-existent key. + auto it = m->find(key); + MOJO_CHECK(it != m->end()); + return GetValue(it); + } + static inline ValueRefType GetOrInsert( + std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key) { + // This is the backing for the index operator (operator[]). + auto it = m->find(key); + if (it == m->end()) { + it = m->insert(std::make_pair(key, ValueStorageType())).first; + new (it->second.buf) Value(); + } + + return GetValue(it); + } + static inline void Insert(std::map<KeyStorageType, ValueStorageType>* m, + KeyForwardType key, + ValueRefType value) { + // STL insert() doesn't insert |value| if |key| is already part of |m|. We + // have to use operator[] to initialize into the storage buffer, but we + // have to do a manual check so that we don't overwrite an existing object. + auto it = m->find(key); + if (it == m->end()) + new ((*m)[key].buf) Value(value.Pass()); + } + static inline KeyConstRefType GetKey( + const typename std::map<KeyStorageType, ValueStorageType>::const_iterator& + it) { + return it->first; + } + static inline ValueConstRefType GetValue( + const typename std::map<KeyStorageType, ValueStorageType>::const_iterator& + it) { + return *reinterpret_cast<const Value*>(it->second.buf); + } + static inline ValueRefType GetValue( + const typename std::map<KeyStorageType, ValueStorageType>::iterator& it) { + return *reinterpret_cast<Value*>(it->second.buf); + } + static inline void Clone( + const std::map<KeyStorageType, ValueStorageType>& src, + std::map<KeyStorageType, ValueStorageType>* dst) { + Finalize(dst); + dst->clear(); + for (auto it = src.begin(); it != src.end(); ++it) + new ((*dst)[it->first].buf) Value(GetValue(it).Clone()); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/map_serialization.h b/mojo/public/cpp/bindings/lib/map_serialization.h new file mode 100644 index 0000000..6014b36 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_serialization.h @@ -0,0 +1,182 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/map_internal.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" +#include "mojo/public/cpp/bindings/map.h" + +namespace mojo { + +template <typename Key, typename Value> +inline size_t GetSerializedSize_(const Map<Key, Value>& input); + +template <typename ValidateParams, typename E, typename F> +inline void SerializeArray_( + Array<E> input, + internal::Buffer* buf, + internal::Array_Data<F>** output, + const internal::ArrayValidateParams* validate_params); + +namespace internal { + +template <typename MapType, + typename DataType, + bool value_is_move_only_type = IsMoveOnlyType<MapType>::value, + bool is_union = + IsUnionDataType<typename RemovePointer<DataType>::type>::value> +struct MapSerializer; + +template <typename MapType, typename DataType> +struct MapSerializer<MapType, DataType, false, false> { + static size_t GetBaseArraySize(size_t count) { + return Align(count * sizeof(DataType)); + } + static size_t GetItemSize(const MapType& item) { return 0; } +}; + +template <> +struct MapSerializer<bool, bool, false, false> { + static size_t GetBaseArraySize(size_t count) { + return Align((count + 7) / 8); + } + static size_t GetItemSize(bool item) { return 0; } +}; + +template <typename H> +struct MapSerializer<ScopedHandleBase<H>, H, true, false> { + static size_t GetBaseArraySize(size_t count) { + return Align(count * sizeof(H)); + } + static size_t GetItemSize(const ScopedHandleBase<H>& item) { return 0; } +}; + +// This template must only apply to pointer mojo entity (structs and arrays). +// This is done by ensuring that WrapperTraits<S>::DataType is a pointer. +template <typename S> +struct MapSerializer< + S, + typename EnableIf<IsPointer<typename WrapperTraits<S>::DataType>::value, + typename WrapperTraits<S>::DataType>::type, + true, + false> { + typedef + typename RemovePointer<typename WrapperTraits<S>::DataType>::type S_Data; + static size_t GetBaseArraySize(size_t count) { + return count * sizeof(StructPointer<S_Data>); + } + static size_t GetItemSize(const S& item) { return GetSerializedSize_(item); } +}; + +template <typename U, typename U_Data> +struct MapSerializer<U, U_Data*, true, true> { + static size_t GetBaseArraySize(size_t count) { + return count * sizeof(U_Data); + } + static size_t GetItemSize(const U& item) { + return GetSerializedSize_(item, true); + } +}; + +template <> +struct MapSerializer<String, String_Data*, false, false> { + static size_t GetBaseArraySize(size_t count) { + return count * sizeof(StringPointer); + } + static size_t GetItemSize(const String& item) { + return GetSerializedSize_(item); + } +}; + +} // namespace internal + +// TODO(erg): This can't go away yet. We still need to calculate out the size +// of a struct header, and two arrays. +template <typename MapKey, typename MapValue> +inline size_t GetSerializedSize_(const Map<MapKey, MapValue>& input) { + if (!input) + return 0; + typedef typename internal::WrapperTraits<MapKey>::DataType DataKey; + typedef typename internal::WrapperTraits<MapValue>::DataType DataValue; + + size_t count = input.size(); + size_t struct_overhead = sizeof(mojo::internal::Map_Data<DataKey, DataValue>); + size_t key_base_size = + sizeof(internal::ArrayHeader) + + internal::MapSerializer<MapKey, DataKey>::GetBaseArraySize(count); + size_t value_base_size = + sizeof(internal::ArrayHeader) + + internal::MapSerializer<MapValue, DataValue>::GetBaseArraySize(count); + + size_t key_data_size = 0; + size_t value_data_size = 0; + for (auto it = input.begin(); it != input.end(); ++it) { + key_data_size += + internal::MapSerializer<MapKey, DataKey>::GetItemSize(it.GetKey()); + value_data_size += + internal::MapSerializer<MapValue, DataValue>::GetItemSize( + it.GetValue()); + } + + return struct_overhead + key_base_size + key_data_size + value_base_size + + value_data_size; +} + +// We don't need an ArrayValidateParams instance for key validation since +// we can deduce it from the Key type. (which can only be primitive types or +// non-nullable strings.) +template <typename MapKey, + typename MapValue, + typename DataKey, + typename DataValue> +inline void SerializeMap_( + Map<MapKey, MapValue> input, + internal::Buffer* buf, + internal::Map_Data<DataKey, DataValue>** output, + const internal::ArrayValidateParams* value_validate_params) { + if (input) { + internal::Map_Data<DataKey, DataValue>* result = + internal::Map_Data<DataKey, DataValue>::New(buf); + if (result) { + Array<MapKey> keys; + Array<MapValue> values; + input.DecomposeMapTo(&keys, &values); + const internal::ArrayValidateParams* key_validate_params = + internal::MapKeyValidateParamsFactory<DataKey>::Get(); + SerializeArray_(keys.Pass(), buf, &result->keys.ptr, key_validate_params); + SerializeArray_(values.Pass(), buf, &result->values.ptr, + value_validate_params); + } + *output = result; + } else { + *output = nullptr; + } +} + +template <typename MapKey, + typename MapValue, + typename DataKey, + typename DataValue> +inline void Deserialize_(internal::Map_Data<DataKey, DataValue>* input, + Map<MapKey, MapValue>* output) { + if (input) { + Array<MapKey> keys; + Array<MapValue> values; + + Deserialize_(input->keys.ptr, &keys); + Deserialize_(input->values.ptr, &values); + + *output = Map<MapKey, MapValue>(keys.Pass(), values.Pass()); + } else { + output->reset(); + } +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc new file mode 100644 index 0000000..6b563e7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message.cc @@ -0,0 +1,105 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/message.h" + +#include <stdlib.h> + +#include <algorithm> + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { + +Message::Message() { + Initialize(); +} + +Message::~Message() { + FreeDataAndCloseHandles(); +} + +void Message::Reset() { + FreeDataAndCloseHandles(); + + handles_.clear(); + Initialize(); +} + +void Message::AllocData(uint32_t num_bytes) { + MOJO_DCHECK(!data_); + data_num_bytes_ = num_bytes; + data_ = static_cast<internal::MessageData*>(calloc(num_bytes, 1)); +} + +void Message::AllocUninitializedData(uint32_t num_bytes) { + MOJO_DCHECK(!data_); + data_num_bytes_ = num_bytes; + data_ = static_cast<internal::MessageData*>(malloc(num_bytes)); +} + +void Message::MoveTo(Message* destination) { + MOJO_DCHECK(this != destination); + + destination->FreeDataAndCloseHandles(); + + // No copy needed. + destination->data_num_bytes_ = data_num_bytes_; + destination->data_ = data_; + std::swap(destination->handles_, handles_); + + handles_.clear(); + Initialize(); +} + +void Message::Initialize() { + data_num_bytes_ = 0; + data_ = nullptr; +} + +void Message::FreeDataAndCloseHandles() { + free(data_); + + for (std::vector<Handle>::iterator it = handles_.begin(); + it != handles_.end(); ++it) { + if (it->is_valid()) + CloseRaw(*it); + } +} + +MojoResult ReadAndDispatchMessage(MessagePipeHandle handle, + MessageReceiver* receiver, + bool* receiver_result) { + MojoResult rv; + + uint32_t num_bytes = 0, num_handles = 0; + rv = ReadMessageRaw(handle, + nullptr, + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv != MOJO_RESULT_RESOURCE_EXHAUSTED) + return rv; + + Message message; + message.AllocUninitializedData(num_bytes); + message.mutable_handles()->resize(num_handles); + + rv = ReadMessageRaw( + handle, + message.mutable_data(), + &num_bytes, + message.mutable_handles()->empty() + ? nullptr + : reinterpret_cast<MojoHandle*>(&message.mutable_handles()->front()), + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (receiver && rv == MOJO_RESULT_OK) + *receiver_result = receiver->Accept(&message); + + return rv; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_builder.cc b/mojo/public/cpp/bindings/lib/message_builder.cc new file mode 100644 index 0000000..25f1862 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.cc @@ -0,0 +1,52 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/lib/message_builder.h" + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +template <typename Header> +void Allocate(Buffer* buf, Header** header) { + *header = static_cast<Header*>(buf->Allocate(sizeof(Header))); + (*header)->num_bytes = sizeof(Header); +} + +MessageBuilder::MessageBuilder(uint32_t name, size_t payload_size) { + Initialize(sizeof(MessageHeader) + payload_size); + + MessageHeader* header; + Allocate(&buf_, &header); + header->version = 0; + header->name = name; +} + +MessageBuilder::~MessageBuilder() { +} + +MessageBuilder::MessageBuilder() {} + +void MessageBuilder::Initialize(size_t size) { + message_.AllocData(static_cast<uint32_t>(Align(size))); + buf_.Initialize(message_.mutable_data(), message_.data_num_bytes()); +} + +MessageWithRequestIDBuilder::MessageWithRequestIDBuilder(uint32_t name, + size_t payload_size, + uint32_t flags, + uint64_t request_id) { + Initialize(sizeof(MessageHeaderWithRequestID) + payload_size); + MessageHeaderWithRequestID* header; + Allocate(&buf_, &header); + header->version = 1; + header->name = name; + header->flags = flags; + header->request_id = request_id; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_builder.h b/mojo/public/cpp/bindings/lib/message_builder.h new file mode 100644 index 0000000..86ae71d --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.h @@ -0,0 +1,68 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +class Message; + +namespace internal { + +class MessageBuilder { + public: + MessageBuilder(uint32_t name, size_t payload_size); + ~MessageBuilder(); + + Buffer* buffer() { return &buf_; } + Message* message() { return &message_; } + + protected: + MessageBuilder(); + void Initialize(size_t size); + + Message message_; + FixedBuffer buf_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MessageBuilder); +}; + +class MessageWithRequestIDBuilder : public MessageBuilder { + public: + MessageWithRequestIDBuilder(uint32_t name, + size_t payload_size, + uint32_t flags, + uint64_t request_id); +}; + +class RequestMessageBuilder : public MessageWithRequestIDBuilder { + public: + RequestMessageBuilder(uint32_t name, size_t payload_size) + : MessageWithRequestIDBuilder(name, + payload_size, + kMessageExpectsResponse, + 0) {} +}; + +class ResponseMessageBuilder : public MessageWithRequestIDBuilder { + public: + ResponseMessageBuilder(uint32_t name, + size_t payload_size, + uint64_t request_id) + : MessageWithRequestIDBuilder(name, + payload_size, + kMessageIsResponse, + request_id) {} +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ diff --git a/mojo/public/cpp/bindings/lib/message_filter.cc b/mojo/public/cpp/bindings/lib/message_filter.cc new file mode 100644 index 0000000..b09f40d --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_filter.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/message_filter.h" + +namespace mojo { + +MessageFilter::MessageFilter(MessageReceiver* sink) : sink_(sink) { +} + +MessageFilter::~MessageFilter() { +} + +PassThroughFilter::PassThroughFilter(MessageReceiver* sink) + : MessageFilter(sink) { +} + +bool PassThroughFilter::Accept(Message* message) { + return sink_->Accept(message); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc new file mode 100644 index 0000000..940b15c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_header_validator.cc @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/message_header_validator.h" + +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { +namespace { + +bool IsValidMessageHeader(const MessageHeader* header) { + // NOTE: Our goal is to preserve support for future extension of the message + // header. If we encounter fields we do not understand, we must ignore them. + + // Extra validation of the struct header: + if (header->version == 0) { + if (header->num_bytes != sizeof(MessageHeader)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } else if (header->version == 1) { + if (header->num_bytes != sizeof(MessageHeaderWithRequestID)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } else if (header->version > 1) { + if (header->num_bytes < sizeof(MessageHeaderWithRequestID)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } + + // Validate flags (allow unknown bits): + + // These flags require a RequestID. + if (header->version < 1 && ((header->flags & kMessageExpectsResponse) || + (header->flags & kMessageIsResponse))) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID); + return false; + } + + // These flags are mutually exclusive. + if ((header->flags & kMessageExpectsResponse) && + (header->flags & kMessageIsResponse)) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + + return true; +} + +} // namespace + +MessageHeaderValidator::MessageHeaderValidator(MessageReceiver* sink) + : MessageFilter(sink) { +} + +bool MessageHeaderValidator::Accept(Message* message) { + // Pass 0 as number of handles because we don't expect any in the header, even + // if |message| contains handles. + BoundsChecker bounds_checker(message->data(), message->data_num_bytes(), 0); + + if (!ValidateStructHeaderAndClaimMemory(message->data(), &bounds_checker)) + return false; + + if (!IsValidMessageHeader(message->header())) + return false; + + return sink_->Accept(message); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.h b/mojo/public/cpp/bindings/lib/message_header_validator.h new file mode 100644 index 0000000..bccef1f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_header_validator.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" + +namespace mojo { +namespace internal { + +class MessageHeaderValidator : public MessageFilter { + public: + explicit MessageHeaderValidator(MessageReceiver* sink = nullptr); + + bool Accept(Message* message) override; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h new file mode 100644 index 0000000..97b9619 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_internal.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { +namespace internal { + +#pragma pack(push, 1) + +enum { kMessageExpectsResponse = 1 << 0, kMessageIsResponse = 1 << 1 }; + +struct MessageHeader : internal::StructHeader { + // Interface ID for identifying multiple interfaces running on the same + // message pipe. + uint32_t interface_id; + // Message name, which is scoped to the interface that the message belongs to. + uint32_t name; + // 0 or either of the enum values defined above. + uint32_t flags; + // Unused padding to make the struct size a multiple of 8 bytes. + uint32_t padding; +}; +static_assert(sizeof(MessageHeader) == 24, "Bad sizeof(MessageHeader)"); + +struct MessageHeaderWithRequestID : MessageHeader { + // Only used if either kMessageExpectsResponse or kMessageIsResponse is set in + // order to match responses with corresponding requests. + uint64_t request_id; +}; +static_assert(sizeof(MessageHeaderWithRequestID) == 32, + "Bad sizeof(MessageHeaderWithRequestID)"); + +struct MessageData { + MessageHeader header; +}; + +static_assert(sizeof(MessageData) == sizeof(MessageHeader), + "Bad sizeof(MessageData)"); + +#pragma pack(pop) + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/no_interface.cc b/mojo/public/cpp/bindings/lib/no_interface.cc new file mode 100644 index 0000000..9e0945c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/no_interface.cc @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/no_interface.h" + +namespace mojo { + +const char* NoInterface::Name_ = "mojo::NoInterface"; + +bool NoInterfaceStub::Accept(Message* message) { + return false; +} + +bool NoInterfaceStub::AcceptWithResponder(Message* message, + MessageReceiver* responder) { + return false; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/router.cc b/mojo/public/cpp/bindings/lib/router.cc new file mode 100644 index 0000000..fff72b4 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/router.cc @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/router.h" + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +// ---------------------------------------------------------------------------- + +class ResponderThunk : public MessageReceiverWithStatus { + public: + explicit ResponderThunk(const SharedData<Router*>& router) + : router_(router), accept_was_invoked_(false) {} + ~ResponderThunk() override { + if (!accept_was_invoked_) { + // The Mojo application handled a message that was expecting a response + // but did not send a response. + Router* router = router_.value(); + if (router) { + // We raise an error to signal the calling application that an error + // condition occurred. Without this the calling application would have + // no way of knowing it should stop waiting for a response. + router->RaiseError(); + } + } + } + + // MessageReceiver implementation: + bool Accept(Message* message) override { + accept_was_invoked_ = true; + MOJO_DCHECK(message->has_flag(kMessageIsResponse)); + + bool result = false; + + Router* router = router_.value(); + if (router) + result = router->Accept(message); + + return result; + } + + // MessageReceiverWithStatus implementation: + bool IsValid() override { + Router* router = router_.value(); + return router && !router->encountered_error() && router->is_valid(); + } + + private: + SharedData<Router*> router_; + bool accept_was_invoked_; +}; + +// ---------------------------------------------------------------------------- + +Router::HandleIncomingMessageThunk::HandleIncomingMessageThunk(Router* router) + : router_(router) { +} + +Router::HandleIncomingMessageThunk::~HandleIncomingMessageThunk() { +} + +bool Router::HandleIncomingMessageThunk::Accept(Message* message) { + return router_->HandleIncomingMessage(message); +} + +// ---------------------------------------------------------------------------- + +Router::Router(ScopedMessagePipeHandle message_pipe, + FilterChain filters, + const MojoAsyncWaiter* waiter) + : thunk_(this), + filters_(filters.Pass()), + connector_(message_pipe.Pass(), waiter), + weak_self_(this), + incoming_receiver_(nullptr), + next_request_id_(0), + testing_mode_(false) { + filters_.SetSink(&thunk_); + connector_.set_incoming_receiver(filters_.GetHead()); +} + +Router::~Router() { + weak_self_.set_value(nullptr); + + for (auto& pair : responders_) + delete pair.second; +} + +bool Router::Accept(Message* message) { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + MOJO_DCHECK(!message->has_flag(kMessageExpectsResponse)); + return connector_.Accept(message); +} + +bool Router::AcceptWithResponder(Message* message, MessageReceiver* responder) { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + MOJO_DCHECK(message->has_flag(kMessageExpectsResponse)); + + // Reserve 0 in case we want it to convey special meaning in the future. + uint64_t request_id = next_request_id_++; + if (request_id == 0) + request_id = next_request_id_++; + + message->set_request_id(request_id); + if (!connector_.Accept(message)) + return false; + + // We assume ownership of |responder|. + responders_[request_id] = responder; + return true; +} + +void Router::EnableTestingMode() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + testing_mode_ = true; + connector_.set_enforce_errors_from_incoming_receiver(false); +} + +bool Router::HandleIncomingMessage(Message* message) { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + if (message->has_flag(kMessageExpectsResponse)) { + if (!incoming_receiver_) + return false; + + MessageReceiverWithStatus* responder = new ResponderThunk(weak_self_); + bool ok = incoming_receiver_->AcceptWithResponder(message, responder); + if (!ok) + delete responder; + return ok; + + } else if (message->has_flag(kMessageIsResponse)) { + uint64_t request_id = message->request_id(); + ResponderMap::iterator it = responders_.find(request_id); + if (it == responders_.end()) { + MOJO_DCHECK(testing_mode_); + return false; + } + MessageReceiver* responder = it->second; + responders_.erase(it); + bool ok = responder->Accept(message); + delete responder; + return ok; + } else { + if (!incoming_receiver_) + return false; + + return incoming_receiver_->Accept(message); + } +} + +// ---------------------------------------------------------------------------- + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/router.h b/mojo/public/cpp/bindings/lib/router.h new file mode 100644 index 0000000..1697610 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/router.h @@ -0,0 +1,141 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ + +#include <map> + +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/lib/connector.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/shared_data.h" +#include "mojo/public/cpp/bindings/lib/thread_checker.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { + +class Router : public MessageReceiverWithResponder { + public: + Router(ScopedMessagePipeHandle message_pipe, + FilterChain filters, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()); + ~Router() override; + + // Sets the receiver to handle messages read from the message pipe that do + // not have the kMessageIsResponse flag set. + void set_incoming_receiver(MessageReceiverWithResponderStatus* receiver) { + incoming_receiver_ = receiver; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_connection_error_handler(const Closure& error_handler) { + connector_.set_connection_error_handler(error_handler); + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.encountered_error(); + } + + // Is the router bound to a MessagePipe handle? + bool is_valid() const { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.is_valid(); + } + + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + connector_.CloseMessagePipe(); + } + + ScopedMessagePipeHandle PassMessagePipe() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.PassMessagePipe(); + } + + void RaiseError() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + connector_.RaiseError(); + } + + // MessageReceiver implementation: + bool Accept(Message* message) override; + bool AcceptWithResponder(Message* message, + MessageReceiver* responder) override; + + // Blocks the current thread until the first incoming method call, i.e., + // either a call to a client method or a callback method, or |deadline|. + bool WaitForIncomingMessage(MojoDeadline deadline) { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.WaitForIncomingMessage(deadline); + } + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + connector_.PauseIncomingMethodCallProcessing(); + } + void ResumeIncomingMethodCallProcessing() { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + connector_.ResumeIncomingMethodCallProcessing(); + } + + // Sets this object to testing mode. + // In testing mode: + // - the object is more tolerant of unrecognized response messages; + // - the connector continues working after seeing errors from its incoming + // receiver. + void EnableTestingMode(); + + MessagePipeHandle handle() const { return connector_.handle(); } + + // Returns true if this Router has any pending callbacks. + bool has_pending_responders() const { + MOJO_DCHECK(thread_checker_.CalledOnValidThread()); + return !responders_.empty(); + } + + private: + typedef std::map<uint64_t, MessageReceiver*> ResponderMap; + + class HandleIncomingMessageThunk : public MessageReceiver { + public: + HandleIncomingMessageThunk(Router* router); + ~HandleIncomingMessageThunk() override; + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + private: + Router* router_; + }; + + bool HandleIncomingMessage(Message* message); + + HandleIncomingMessageThunk thunk_; + FilterChain filters_; + Connector connector_; + SharedData<Router*> weak_self_; + MessageReceiverWithResponderStatus* incoming_receiver_; + // Maps from the id of a response to the MessageReceiver that handles the + // response. + ResponderMap responders_; + uint64_t next_request_id_; + bool testing_mode_; + ThreadChecker thread_checker_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ diff --git a/mojo/public/cpp/bindings/lib/shared_data.h b/mojo/public/cpp/bindings/lib/shared_data.h new file mode 100644 index 0000000..2676224 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/shared_data.h @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ + +#include <assert.h> + +#include "mojo/public/cpp/bindings/lib/thread_checker.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// Used to allocate an instance of T that can be shared via reference counting. +template <typename T> +class SharedData { + public: + ~SharedData() { holder_->Release(); } + + SharedData() : holder_(new Holder()) {} + + explicit SharedData(const T& value) : holder_(new Holder(value)) {} + + SharedData(const SharedData<T>& other) : holder_(other.holder_) { + holder_->Retain(); + } + + SharedData<T>& operator=(const SharedData<T>& other) { + if (other.holder_ == holder_) + return *this; + holder_->Release(); + holder_ = other.holder_; + holder_->Retain(); + return *this; + } + + void reset() { + holder_->Release(); + holder_ = new Holder(); + } + + void reset(const T& value) { + holder_->Release(); + holder_ = new Holder(value); + } + + void set_value(const T& value) { holder_->value = value; } + T* mutable_value() { return &holder_->value; } + const T& value() const { return holder_->value; } + + private: + class Holder { + public: + Holder() : value(), ref_count_(1) {} + Holder(const T& value) : value(value), ref_count_(1) {} + + void Retain() { + assert(thread_checker_.CalledOnValidThread()); + ++ref_count_; + } + void Release() { + assert(thread_checker_.CalledOnValidThread()); + if (--ref_count_ == 0) + delete this; + } + + T value; + + private: + int ref_count_; + ThreadChecker thread_checker_; + MOJO_DISALLOW_COPY_AND_ASSIGN(Holder); + }; + + Holder* holder_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ diff --git a/mojo/public/cpp/bindings/lib/shared_ptr.h b/mojo/public/cpp/bindings/lib/shared_ptr.h new file mode 100644 index 0000000..37c8735 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/shared_ptr.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ + +#include "mojo/public/cpp/bindings/lib/shared_data.h" + +namespace mojo { +namespace internal { + +// Used to manage a heap-allocated instance of P that can be shared via +// reference counting. When the last reference is dropped, the instance is +// deleted. +template <typename P> +class SharedPtr { + public: + SharedPtr() {} + + explicit SharedPtr(P* ptr) { impl_.mutable_value()->ptr = ptr; } + + // Default copy-constructor and assignment operator are OK. + + P* get() { return impl_.value().ptr; } + const P* get() const { return impl_.value().ptr; } + + void reset() { impl_.reset(); } + + P* operator->() { return get(); } + const P* operator->() const { return get(); } + + private: + class Impl { + public: + ~Impl() { + if (ptr) + delete ptr; + } + + Impl() : ptr(nullptr) {} + + Impl(P* ptr) : ptr(ptr) {} + + P* ptr; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(Impl); + }; + + SharedData<Impl> impl_; +}; + +} // namespace mojo +} // namespace internal + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ diff --git a/mojo/public/cpp/bindings/lib/string_serialization.cc b/mojo/public/cpp/bindings/lib/string_serialization.cc new file mode 100644 index 0000000..e29d6f8 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_serialization.cc @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/string_serialization.h" + +#include <string.h> + +namespace mojo { + +size_t GetSerializedSize_(const String& input) { + if (!input) + return 0; + return internal::Align(sizeof(internal::String_Data) + input.size()); +} + +void Serialize_(const String& input, + internal::Buffer* buf, + internal::String_Data** output) { + if (input) { + internal::String_Data* result = + internal::String_Data::New(input.size(), buf); + if (result) + memcpy(result->storage(), input.data(), input.size()); + *output = result; + } else { + *output = nullptr; + } +} + +void Deserialize_(internal::String_Data* input, String* output) { + if (input) { + String result(input->storage(), input->size()); + result.Swap(output); + } else { + output->reset(); + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h new file mode 100644 index 0000000..0118742 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_serialization.h @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/string.h" + +namespace mojo { + +size_t GetSerializedSize_(const String& input); +void Serialize_(const String& input, + internal::Buffer* buffer, + internal::String_Data** output); +void Deserialize_(internal::String_Data* input, String* output); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h new file mode 100644 index 0000000..9a5788c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/template_util.h @@ -0,0 +1,130 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ + +namespace mojo { +namespace internal { + +template <class T, T v> +struct IntegralConstant { + static const T value = v; +}; + +template <class T, T v> +const T IntegralConstant<T, v>::value; + +typedef IntegralConstant<bool, true> TrueType; +typedef IntegralConstant<bool, false> FalseType; + +template <class T> +struct IsConst : FalseType {}; +template <class T> +struct IsConst<const T> : TrueType {}; + +template <class T> +struct IsPointer : FalseType {}; +template <class T> +struct IsPointer<T*> : TrueType {}; + +template <bool B, typename T = void> +struct EnableIf {}; + +template <typename T> +struct EnableIf<true, T> { + typedef T type; +}; + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// A helper template to determine if given type is non-const move-only-type, +// i.e. if a value of the given type should be passed via .Pass() in a +// destructive way. +template <typename T> +struct IsMoveOnlyType { + template <typename U> + static YesType Test(const typename U::MoveOnlyTypeForCPP03*); + + template <typename U> + static NoType Test(...); + + static const bool value = + sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value; +}; + +// Returns a reference to |t| when T is not a move-only type. +template <typename T> +typename EnableIf<!IsMoveOnlyType<T>::value, T>::type& Forward(T& t) { + return t; +} + +// Returns the result of t.Pass() when T is a move-only type. +template <typename T> +typename EnableIf<IsMoveOnlyType<T>::value, T>::type Forward(T& t) { + return t.Pass(); +} + +// This goop is a trick used to implement a template that can be used to +// determine if a given class is the base class of another given class. +template <typename, typename> +struct IsSame { + static bool const value = false; +}; +template <typename A> +struct IsSame<A, A> { + static bool const value = true; +}; +template <typename Base, typename Derived> +struct IsBaseOf { + private: + // This class doesn't work correctly with forward declarations. + // Because sizeof cannot be applied to incomplete types, this line prevents us + // from passing in forward declarations. + typedef char (*EnsureTypesAreComplete)[sizeof(Base) + sizeof(Derived)]; + + static Derived* CreateDerived(); + static char(&Check(Base*))[1]; + static char(&Check(...))[2]; + + public: + static bool const value = sizeof Check(CreateDerived()) == 1 && + !IsSame<Base const, void const>::value; +}; + +template <class T> +struct RemovePointer { + typedef T type; +}; +template <class T> +struct RemovePointer<T*> { + typedef T type; +}; + +template <template <typename...> class Template, typename T> +struct IsSpecializationOf : FalseType {}; + +template <template <typename...> class Template, typename... Args> +struct IsSpecializationOf<Template, Template<Args...>> : TrueType {}; + +template <bool B, typename T, typename F> +struct Conditional { + typedef T type; +}; + +template <typename T, typename F> +struct Conditional<false, T, F> { + typedef F type; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/thread_checker.h b/mojo/public/cpp/bindings/lib/thread_checker.h new file mode 100644 index 0000000..da45d0a --- /dev/null +++ b/mojo/public/cpp/bindings/lib/thread_checker.h @@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_H_ + +#include "mojo/public/cpp/system/macros.h" + +#if !defined(_WIN32) +#include "mojo/public/cpp/bindings/lib/thread_checker_posix.h" +#endif + +namespace mojo { +namespace internal { + +class ThreadCheckerDoNothing { + public: + bool CalledOnValidThread() const MOJO_WARN_UNUSED_RESULT { + return true; + } +}; + +// ThreadChecker is a class used to verify that some methods of a class are +// called from the same thread. It is meant to be a member variable of a class. +// The entire lifecycle of a ThreadChecker must occur on a single thread. +// In Release mode (without dcheck_always_on), ThreadChecker does nothing. +#if !defined(_WIN32) && (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +using ThreadChecker = ThreadCheckerPosix; +#else +using ThreadChecker = ThreadCheckerDoNothing; +#endif + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_H_ diff --git a/mojo/public/cpp/bindings/lib/thread_checker_posix.cc b/mojo/public/cpp/bindings/lib/thread_checker_posix.cc new file mode 100644 index 0000000..c8a16e2 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/thread_checker_posix.cc @@ -0,0 +1,24 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/lib/thread_checker_posix.h" + +#include <assert.h> + +namespace mojo { +namespace internal { + +ThreadCheckerPosix::ThreadCheckerPosix() : attached_thread_id_(pthread_self()) { +} + +ThreadCheckerPosix::~ThreadCheckerPosix() { + assert(CalledOnValidThread()); +} + +bool ThreadCheckerPosix::CalledOnValidThread() const { + return pthread_equal(pthread_self(), attached_thread_id_); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/thread_checker_posix.h b/mojo/public/cpp/bindings/lib/thread_checker_posix.h new file mode 100644 index 0000000..4701b88 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/thread_checker_posix.h @@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_POSIX_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_POSIX_H_ + +#include <pthread.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// An implementation of ThreadChecker for POSIX systems. +class ThreadCheckerPosix { + public: + ThreadCheckerPosix(); + ~ThreadCheckerPosix(); + + bool CalledOnValidThread() const MOJO_WARN_UNUSED_RESULT; + private: + const pthread_t attached_thread_id_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ThreadCheckerPosix); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_THREAD_CHECKER_POSIX_H_ diff --git a/mojo/public/cpp/bindings/lib/union_accessor.h b/mojo/public/cpp/bindings/lib/union_accessor.h new file mode 100644 index 0000000..821aede --- /dev/null +++ b/mojo/public/cpp/bindings/lib/union_accessor.h @@ -0,0 +1,33 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ + +namespace mojo { +namespace internal { + +// When serializing and deserializing Unions, it is necessary to access +// the private fields and methods of the Union. This allows us to do that +// without leaking those same fields and methods in the Union interface. +// All Union wrappers are friends of this class allowing such access. +template <typename U> +class UnionAccessor { + public: + explicit UnionAccessor(U* u) : u_(u) {} + + typename U::Union_* data() { return &(u_->data_); } + + typename U::Tag* tag() { return &(u_->tag_); } + + void SwitchActive(typename U::Tag new_tag) { u_->SwitchActive(new_tag); } + + private: + U* u_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ diff --git a/mojo/public/cpp/bindings/lib/validate_params.h b/mojo/public/cpp/bindings/lib/validate_params.h new file mode 100644 index 0000000..274a012 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validate_params.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +class ArrayValidateParams { + public: + // ArrayValidateParams takes ownership of |in_element_validate params|. + ArrayValidateParams(uint32_t in_expected_num_elements, + bool in_element_is_nullable, + ArrayValidateParams* in_element_validate_params) + : expected_num_elements(in_expected_num_elements), + element_is_nullable(in_element_is_nullable), + element_validate_params(in_element_validate_params) {} + + ~ArrayValidateParams() { + if (element_validate_params) + delete element_validate_params; + } + + // TODO(vtl): The members of this class shouldn't be public. + + // If |expected_num_elements| is not 0, the array is expected to have exactly + // that number of elements. + uint32_t expected_num_elements; + + // Whether the elements are nullable. + bool element_is_nullable; + + // Validation information for elements. It is either a pointer to another + // instance of ArrayValidateParams (if elements are arrays or maps), or + // nullptr. In the case of maps, this is used to validate the value array. + ArrayValidateParams* element_validate_params; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(ArrayValidateParams); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ diff --git a/mojo/public/cpp/bindings/lib/validation_errors.cc b/mojo/public/cpp/bindings/lib/validation_errors.cc new file mode 100644 index 0000000..ee58ed9 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_errors.cc @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/validation_errors.h" + +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace internal { +namespace { + +ValidationErrorObserverForTesting* g_validation_error_observer = nullptr; +SerializationWarningObserverForTesting* g_serialization_warning_observer = + nullptr; + +} // namespace + +const char* ValidationErrorToString(ValidationError error) { + switch (error) { + case VALIDATION_ERROR_NONE: + return "VALIDATION_ERROR_NONE"; + case VALIDATION_ERROR_MISALIGNED_OBJECT: + return "VALIDATION_ERROR_MISALIGNED_OBJECT"; + case VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE: + return "VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE"; + case VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER"; + case VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER"; + case VALIDATION_ERROR_ILLEGAL_HANDLE: + return "VALIDATION_ERROR_ILLEGAL_HANDLE"; + case VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE: + return "VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE"; + case VALIDATION_ERROR_ILLEGAL_POINTER: + return "VALIDATION_ERROR_ILLEGAL_POINTER"; + case VALIDATION_ERROR_UNEXPECTED_NULL_POINTER: + return "VALIDATION_ERROR_UNEXPECTED_NULL_POINTER"; + case VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS: + return "VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS"; + case VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID: + return "VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID"; + case VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD: + return "VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD"; + case VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP: + return "VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP"; + case VALIDATION_ERROR_UNKNOWN_UNION_TAG: + return "VALIDATION_ERROR_UNKNOWN_UNION_TAG"; + } + + return "Unknown error"; +} + +void ReportValidationError(ValidationError error, const char* description) { + if (g_validation_error_observer) { + g_validation_error_observer->set_last_error(error); + } else if (description) { + MOJO_LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error) + << " (" << description << ")"; + } else { + MOJO_LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error); + } +} + +ValidationErrorObserverForTesting::ValidationErrorObserverForTesting() + : last_error_(VALIDATION_ERROR_NONE) { + MOJO_DCHECK(!g_validation_error_observer); + g_validation_error_observer = this; +} + +ValidationErrorObserverForTesting::~ValidationErrorObserverForTesting() { + MOJO_DCHECK(g_validation_error_observer == this); + g_validation_error_observer = nullptr; +} + +bool ReportSerializationWarning(ValidationError error) { + if (g_serialization_warning_observer) { + g_serialization_warning_observer->set_last_warning(error); + return true; + } + + return false; +} + +SerializationWarningObserverForTesting::SerializationWarningObserverForTesting() + : last_warning_(VALIDATION_ERROR_NONE) { + MOJO_DCHECK(!g_serialization_warning_observer); + g_serialization_warning_observer = this; +} + +SerializationWarningObserverForTesting:: + ~SerializationWarningObserverForTesting() { + MOJO_DCHECK(g_serialization_warning_observer == this); + g_serialization_warning_observer = nullptr; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/validation_errors.h b/mojo/public/cpp/bindings/lib/validation_errors.h new file mode 100644 index 0000000..1d15bc9 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_errors.h @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +enum ValidationError { + // There is no validation error. + VALIDATION_ERROR_NONE, + // An object (struct or array) is not 8-byte aligned. + VALIDATION_ERROR_MISALIGNED_OBJECT, + // An object is not contained inside the message data, or it overlaps other + // objects. + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE, + // A struct header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the struct header. + // - |num_bytes| and |version| don't match. + // TODO(yzshen): Consider splitting it into two different error codes. Because + // the former indicates someone is misbehaving badly whereas the latter could + // be due to an inappropriately-modified .mojom file. + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER, + // An array header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the header plus the size required + // to store |num_elements| elements. + // - For fixed-size arrays, |num_elements| is different than the specified + // size. + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + // An encoded handle is illegal. + VALIDATION_ERROR_ILLEGAL_HANDLE, + // A non-nullable handle field is set to invalid handle. + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + // An encoded pointer is illegal. + VALIDATION_ERROR_ILLEGAL_POINTER, + // A non-nullable pointer field is set to null. + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + // |flags| in the message header is invalid. The flags are either + // inconsistent with one another, inconsistent with other parts of the + // message, or unexpected for the message receiver. For example the + // receiver is expecting a request message but the flags indicate that + // the message is a response message. + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS, + // |flags| in the message header indicates that a request ID is required but + // there isn't one. + VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID, + // The |name| field in a message header contains an unexpected value. + VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD, + // Two parallel arrays which are supposed to represent a map have different + // lengths. + VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP, + // Attempted to deserialize a tagged union with an unknown tag. + VALIDATION_ERROR_UNKNOWN_UNION_TAG +}; + +const char* ValidationErrorToString(ValidationError error); + +void ReportValidationError(ValidationError error, + const char* description = nullptr); + +// Only used by validation tests and when there is only one thread doing message +// validation. +class ValidationErrorObserverForTesting { + public: + ValidationErrorObserverForTesting(); + ~ValidationErrorObserverForTesting(); + + ValidationError last_error() const { return last_error_; } + void set_last_error(ValidationError error) { last_error_ = error; } + + private: + ValidationError last_error_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ValidationErrorObserverForTesting); +}; + +// Used only by MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING. Don't use it directly. +// +// The function returns true if the error is recorded (by a +// SerializationWarningObserverForTesting object), false otherwise. +bool ReportSerializationWarning(ValidationError error); + +// Only used by serialization tests and when there is only one thread doing +// message serialization. +class SerializationWarningObserverForTesting { + public: + SerializationWarningObserverForTesting(); + ~SerializationWarningObserverForTesting(); + + ValidationError last_warning() const { return last_warning_; } + void set_last_warning(ValidationError error) { last_warning_ = error; } + + private: + ValidationError last_warning_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(SerializationWarningObserverForTesting); +}; + +} // namespace internal +} // namespace mojo + +// In debug build, logs a serialization warning if |condition| evaluates to +// true: +// - if there is a SerializationWarningObserverForTesting object alive, +// records |error| in it; +// - otherwise, logs a fatal-level message. +// |error| is the validation error that will be triggered by the receiver +// of the serialzation result. +// +// In non-debug build, does nothing (not even compiling |condition|). +#define MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( \ + condition, error, description) \ + MOJO_DLOG_IF(FATAL, (condition) && !ReportSerializationWarning(error)) \ + << "The outgoing message will trigger " \ + << ValidationErrorToString(error) << " at the receiving side (" \ + << description << ")."; + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ diff --git a/mojo/public/cpp/bindings/lib/validation_util.cc b/mojo/public/cpp/bindings/lib/validation_util.cc new file mode 100644 index 0000000..2268a15 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_util.cc @@ -0,0 +1,103 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/lib/validation_util.h" + +#include <limits> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +bool ValidateEncodedPointer(const uint64_t* offset) { + // - Make sure |*offset| is no more than 32-bits. + // - Cast |offset| to uintptr_t so overflow behavior is well defined across + // 32-bit and 64-bit systems. + return *offset <= std::numeric_limits<uint32_t>::max() && + (reinterpret_cast<uintptr_t>(offset) + + static_cast<uint32_t>(*offset) >= + reinterpret_cast<uintptr_t>(offset)); +} + +bool ValidateStructHeaderAndClaimMemory(const void* data, + BoundsChecker* bounds_checker) { + if (!IsAligned(data)) { + ReportValidationError(VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!bounds_checker->IsValidRange(data, sizeof(StructHeader))) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const StructHeader* header = static_cast<const StructHeader*>(data); + + if (header->num_bytes < sizeof(StructHeader)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!bounds_checker->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + return true; +} + +bool ValidateMessageIsRequestWithoutResponse(const Message* message) { + if (message->has_flag(kMessageIsResponse) || + message->has_flag(kMessageExpectsResponse)) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool ValidateMessageIsRequestExpectingResponse(const Message* message) { + if (message->has_flag(kMessageIsResponse) || + !message->has_flag(kMessageExpectsResponse)) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool ValidateMessageIsResponse(const Message* message) { + if (message->has_flag(kMessageExpectsResponse) || + !message->has_flag(kMessageIsResponse)) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool ValidateControlRequest(const Message* message) { + switch (message->header()->name) { + case kRunMessageId: + return ValidateMessageIsRequestExpectingResponse(message) && + ValidateMessagePayload<RunMessageParams_Data>(message); + case kRunOrClosePipeMessageId: + return ValidateMessageIsRequestWithoutResponse(message) && + ValidateMessagePayload<RunOrClosePipeMessageParams_Data>(message); + } + return false; +} + +bool ValidateControlResponse(const Message* message) { + if (!ValidateMessageIsResponse(message)) + return false; + switch (message->header()->name) { + case kRunMessageId: + return ValidateMessagePayload<RunResponseMessageParams_Data>(message); + } + return false; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/validation_util.h b/mojo/public/cpp/bindings/lib/validation_util.h new file mode 100644 index 0000000..6853f45 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_util.h @@ -0,0 +1,53 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +// Checks whether decoding the pointer will overflow and produce a pointer +// smaller than |offset|. +bool ValidateEncodedPointer(const uint64_t* offset); + +// Validates that |data| contains a valid struct header, in terms of alignment +// and size (i.e., the |num_bytes| field of the header is sufficient for storing +// the header itself). Besides, it checks that the memory range +// [data, data + num_bytes) is not marked as occupied by other objects in +// |bounds_checker|. On success, the memory range is marked as occupied. +// Note: Does not verify |version| or that |num_bytes| is correct for the +// claimed version. +bool ValidateStructHeaderAndClaimMemory(const void* data, + BoundsChecker* bounds_checker); + +// Validates that the message is a request which doesn't expect a response. +bool ValidateMessageIsRequestWithoutResponse(const Message* message); +// Validates that the message is a request expecting a response. +bool ValidateMessageIsRequestExpectingResponse(const Message* message); +// Validates that the message is a response. +bool ValidateMessageIsResponse(const Message* message); + +// Validates that the message payload is a valid struct of type ParamsType. +template <typename ParamsType> +bool ValidateMessagePayload(const Message* message) { + BoundsChecker bounds_checker(message->payload(), message->payload_num_bytes(), + message->handles()->size()); + return ParamsType::Validate(message->payload(), &bounds_checker); +} + +// The following methods validate control messages defined in +// interface_control_messages.mojom. +bool ValidateControlRequest(const Message* message); +bool ValidateControlResponse(const Message* message); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/value_traits.h b/mojo/public/cpp/bindings/lib/value_traits.h new file mode 100644 index 0000000..d3b295b --- /dev/null +++ b/mojo/public/cpp/bindings/lib/value_traits.h @@ -0,0 +1,108 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALUE_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALUE_TRAITS_H_ + +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template <typename T> +class Array; + +template <typename T> +class AssociatedInterfacePtrInfo; + +template <typename T> +class AssociatedInterfaceRequest; + +template <typename T> +class InlinedStructPtr; + +template <typename T> +class InterfacePtr; + +template <typename T> +class InterfaceRequest; + +template <typename K, typename V> +class Map; + +template <typename T> +class ScopedHandleBase; + +template <typename T> +class StructPtr; + +namespace internal { + +template <typename T, typename Enable = void> +struct ValueTraits { + static bool Equals(const T& a, const T& b) { return a == b; } +}; + +template <typename T> +struct ValueTraits< + T, + typename EnableIf<IsSpecializationOf<Array, T>::value || + IsSpecializationOf<Map, T>::value || + IsSpecializationOf<StructPtr, T>::value || + IsSpecializationOf<InlinedStructPtr, T>::value>::type> { + static bool Equals(const T& a, const T& b) { return a.Equals(b); } +}; + +template <typename T> +struct ValueTraits<ScopedHandleBase<T>> { + static bool Equals(const ScopedHandleBase<T>& a, + const ScopedHandleBase<T>& b) { + return a.get().value() == b.get().value(); + } +}; + +template <typename T> +struct ValueTraits< + T, + typename EnableIf< + IsSpecializationOf<InterfaceRequest, T>::value || + IsSpecializationOf<AssociatedInterfaceRequest, T>::value>::type> { + static bool Equals(const T& a, const T& b) { + if (&a == &b) + return true; + + // Now that |a| and |b| refer to different objects, they are equivalent if + // and only if they are both invalid. + return !a.is_pending() && !b.is_pending(); + } +}; + +template <typename T> +struct ValueTraits<InterfacePtr<T>> { + static bool Equals(const InterfacePtr<T>& a, const InterfacePtr<T>& b) { + if (&a == &b) + return true; + + // Now that |a| and |b| refer to different objects, they are equivalent if + // and only if they are both null. + return !a && !b; + } +}; + +template <typename T> +struct ValueTraits<AssociatedInterfacePtrInfo<T>> { + static bool Equals(const AssociatedInterfacePtrInfo<T>& a, + const AssociatedInterfacePtrInfo<T>& b) { + if (&a == &b) + return true; + + // Now that |a| and |b| refer to different objects, they are equivalent if + // and only if they are both invalid. + return !a.is_valid() && !b.is_valid(); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALUE_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/map.h b/mojo/public/cpp/bindings/map.h new file mode 100644 index 0000000..0f08d83 --- /dev/null +++ b/mojo/public/cpp/bindings/map.h @@ -0,0 +1,296 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ + +#include <map> + +#include "mojo/public/cpp/bindings/lib/map_internal.h" +#include "mojo/public/cpp/bindings/lib/value_traits.h" + +namespace mojo { + +// A move-only map that can handle move-only values. Map has the following +// characteristics: +// - The map itself can be null, and this is distinct from empty. +// - Keys must not be move-only. +// - The Key-type's "<" operator is used to sort the entries, and also is +// used to determine equality of the key values. +// - There can only be one entry per unique key. +// - Values of move-only types will be moved into the Map when they are added +// using the insert() method. +template <typename Key, typename Value> +class Map { + MOJO_MOVE_ONLY_TYPE(Map) + + public: + // Map keys cannot be move only classes. + static_assert(!internal::IsMoveOnlyType<Key>::value, + "Map keys cannot be move only types."); + + typedef internal::MapTraits<Key, + Value, + internal::IsMoveOnlyType<Value>::value> Traits; + typedef typename Traits::KeyStorageType KeyStorageType; + typedef typename Traits::KeyRefType KeyRefType; + typedef typename Traits::KeyConstRefType KeyConstRefType; + typedef typename Traits::KeyForwardType KeyForwardType; + + typedef typename Traits::ValueStorageType ValueStorageType; + typedef typename Traits::ValueRefType ValueRefType; + typedef typename Traits::ValueConstRefType ValueConstRefType; + typedef typename Traits::ValueForwardType ValueForwardType; + + typedef internal::Map_Data<typename internal::WrapperTraits<Key>::DataType, + typename internal::WrapperTraits<Value>::DataType> + Data_; + + Map() : is_null_(true) {} + + // Constructs a non-null Map containing the specified |keys| mapped to the + // corresponding |values|. + Map(mojo::Array<Key> keys, mojo::Array<Value> values) : is_null_(false) { + MOJO_DCHECK(keys.size() == values.size()); + Traits::InitializeFrom(&map_, keys.Pass(), values.Pass()); + } + + ~Map() { Traits::Finalize(&map_); } + + Map(Map&& other) : is_null_(true) { Take(&other); } + Map& operator=(Map&& other) { + Take(&other); + return *this; + } + + // Copies the contents of some other type of map into a new Map using a + // TypeConverter. A TypeConverter for std::map to Map is defined below. + template <typename U> + static Map From(const U& other) { + return TypeConverter<Map, U>::Convert(other); + } + + // Copies the contents of the Map into some other type of map. A TypeConverter + // for Map to std::map is defined below. + template <typename U> + U To() const { + return TypeConverter<U, Map>::Convert(*this); + } + + // Destroys the contents of the Map and leaves it in the null state. + void reset() { + if (!map_.empty()) { + Traits::Finalize(&map_); + map_.clear(); + } + is_null_ = true; + } + + bool is_null() const { return is_null_; } + + // Indicates the number of keys in the map. + size_t size() const { return map_.size(); } + + void mark_non_null() { is_null_ = false; } + + // Inserts a key-value pair into the map, moving the value by calling its + // Pass() method if it is a move-only type. Like std::map, this does not + // insert |value| if |key| is already a member of the map. + void insert(KeyForwardType key, ValueForwardType value) { + is_null_ = false; + Traits::Insert(&map_, key, value); + } + + // Returns a reference to the value associated with the specified key, + // crashing the process if the key is not present in the map. + ValueRefType at(KeyForwardType key) { return Traits::at(&map_, key); } + ValueConstRefType at(KeyForwardType key) const { + return Traits::at(&map_, key); + } + + // Returns a reference to the value associated with the specified key, + // creating a new entry if the key is not already present in the map. A + // newly-created value will be value-initialized (meaning that it will be + // initialized by the default constructor of the value type, if any, or else + // will be zero-initialized). + ValueRefType operator[](KeyForwardType key) { + is_null_ = false; + return Traits::GetOrInsert(&map_, key); + } + + // Swaps the contents of this Map with another Map of the same type (including + // nullness). + void Swap(Map<Key, Value>* other) { + std::swap(is_null_, other->is_null_); + map_.swap(other->map_); + } + + // Swaps the contents of this Map with an std::map containing keys and values + // of the same type. Since std::map cannot represent the null state, the + // std::map will be empty if Map is null. The Map will always be left in a + // non-null state. + void Swap(std::map<Key, Value>* other) { + is_null_ = false; + map_.swap(*other); + } + + // Removes all contents from the Map and places them into parallel key/value + // arrays. Each key will be copied from the source to the destination, and + // values will be copied unless their type is designated move-only, in which + // case they will be passed by calling their Pass() method. Either way, the + // Map will be left in a null state. + void DecomposeMapTo(mojo::Array<Key>* keys, mojo::Array<Value>* values) { + Traits::Decompose(&map_, keys, values); + Traits::Finalize(&map_); + map_.clear(); + is_null_ = true; + } + + // Returns a new Map that contains a copy of the contents of this map. If the + // values are of a type that is designated move-only, they will be cloned + // using the Clone() method of the type. Please note that calling this method + // will fail compilation if the value type cannot be cloned (which usually + // means that it is a Mojo handle type or a type that contains Mojo handles). + Map Clone() const { + Map result; + result.is_null_ = is_null_; + Traits::Clone(map_, &result.map_); + return result.Pass(); + } + + // Indicates whether the contents of this map are equal to those of another + // Map (including nullness). Keys are compared by the != operator. Values are + // compared as follows: + // - Map, Array, Struct, or StructPtr values are compared by their Equals() + // method. + // - ScopedHandleBase-derived types are compared by their handles. + // - Values of other types are compared by their "==" operator. + bool Equals(const Map& other) const { + if (is_null() != other.is_null()) + return false; + if (size() != other.size()) + return false; + auto i = begin(); + auto j = other.begin(); + while (i != end()) { + if (i.GetKey() != j.GetKey()) + return false; + if (!internal::ValueTraits<Value>::Equals(i.GetValue(), j.GetValue())) + return false; + ++i; + ++j; + } + return true; + } + + // A read-only iterator for Map. + class ConstMapIterator { + public: + ConstMapIterator( + const typename std::map<KeyStorageType, + ValueStorageType>::const_iterator& it) + : it_(it) {} + + // Returns a const reference to the key and value. + KeyConstRefType GetKey() { return Traits::GetKey(it_); } + ValueConstRefType GetValue() { return Traits::GetValue(it_); } + + ConstMapIterator& operator*() { + return *this; + } + ConstMapIterator& operator++() { + it_++; + return *this; + } + bool operator!=(const ConstMapIterator& rhs) const { + return it_ != rhs.it_; + } + bool operator==(const ConstMapIterator& rhs) const { + return it_ == rhs.it_; + } + + private: + typename std::map<KeyStorageType, ValueStorageType>::const_iterator it_; + }; + + // Provide read-only iteration over map members in a way similar to STL + // collections. + ConstMapIterator begin() const { return ConstMapIterator(map_.begin()); } + ConstMapIterator end() const { return ConstMapIterator(map_.end()); } + + // Returns the iterator pointing to the entry for |key|, if present, or else + // returns end(). + ConstMapIterator find(KeyForwardType key) const { + return ConstMapIterator(map_.find(key)); + } + + private: + typedef std::map<KeyStorageType, ValueStorageType> Map::*Testable; + + public: + // The Map may be used in boolean expressions to determine if it is non-null, + // but is not implicitly convertible to an actual bool value (which would be + // dangerous). + operator Testable() const { return is_null_ ? 0 : &Map::map_; } + + private: + // Forbid the == and != operators explicitly, otherwise Map will be converted + // to Testable to do == or != comparison. + template <typename T, typename U> + bool operator==(const Map<T, U>& other) const = delete; + template <typename T, typename U> + bool operator!=(const Map<T, U>& other) const = delete; + + void Take(Map* other) { + reset(); + Swap(other); + } + + std::map<KeyStorageType, ValueStorageType> map_; + bool is_null_; +}; + +// Copies the contents of an std::map to a new Map, optionally changing the +// types of the keys and values along the way using TypeConverter. +template <typename MojoKey, + typename MojoValue, + typename STLKey, + typename STLValue> +struct TypeConverter<Map<MojoKey, MojoValue>, std::map<STLKey, STLValue>> { + static Map<MojoKey, MojoValue> Convert( + const std::map<STLKey, STLValue>& input) { + Map<MojoKey, MojoValue> result; + result.mark_non_null(); + for (auto& pair : input) { + result.insert(TypeConverter<MojoKey, STLKey>::Convert(pair.first), + TypeConverter<MojoValue, STLValue>::Convert(pair.second)); + } + return result.Pass(); + } +}; + +// Copies the contents of a Map to an std::map, optionally changing the types of +// the keys and values along the way using TypeConverter. +template <typename MojoKey, + typename MojoValue, + typename STLKey, + typename STLValue> +struct TypeConverter<std::map<STLKey, STLValue>, Map<MojoKey, MojoValue>> { + static std::map<STLKey, STLValue> Convert( + const Map<MojoKey, MojoValue>& input) { + std::map<STLKey, STLValue> result; + if (!input.is_null()) { + for (auto it = input.begin(); it != input.end(); ++it) { + result.insert(std::make_pair( + TypeConverter<STLKey, MojoKey>::Convert(it.GetKey()), + TypeConverter<STLValue, MojoValue>::Convert(it.GetValue()))); + } + } + return result; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h new file mode 100644 index 0000000..7e34606 --- /dev/null +++ b/mojo/public/cpp/bindings/message.h @@ -0,0 +1,159 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ + +#include <vector> + +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { + +// Message is a holder for the data and handles to be sent over a MessagePipe. +// Message owns its data and handles, but a consumer of Message is free to +// mutate the data and handles. The message's data is comprised of a header +// followed by payload. +class Message { + public: + Message(); + ~Message(); + + void Reset(); + + void AllocData(uint32_t num_bytes); + void AllocUninitializedData(uint32_t num_bytes); + + // Transfers data and handles to |destination|. + void MoveTo(Message* destination); + + uint32_t data_num_bytes() const { return data_num_bytes_; } + + // Access the raw bytes of the message. + const uint8_t* data() const { + return reinterpret_cast<const uint8_t*>(data_); + } + uint8_t* mutable_data() { return reinterpret_cast<uint8_t*>(data_); } + + // Access the header. + const internal::MessageHeader* header() const { return &data_->header; } + + uint32_t name() const { return data_->header.name; } + bool has_flag(uint32_t flag) const { return !!(data_->header.flags & flag); } + + // Access the request_id field (if present). + bool has_request_id() const { return data_->header.version >= 1; } + uint64_t request_id() const { + MOJO_DCHECK(has_request_id()); + return static_cast<const internal::MessageHeaderWithRequestID*>( + &data_->header)->request_id; + } + void set_request_id(uint64_t request_id) { + MOJO_DCHECK(has_request_id()); + static_cast<internal::MessageHeaderWithRequestID*>(&data_->header) + ->request_id = request_id; + } + + // Access the payload. + const uint8_t* payload() const { + return reinterpret_cast<const uint8_t*>(data_) + data_->header.num_bytes; + } + uint8_t* mutable_payload() { + return reinterpret_cast<uint8_t*>(data_) + data_->header.num_bytes; + } + uint32_t payload_num_bytes() const { + MOJO_DCHECK(data_num_bytes_ >= data_->header.num_bytes); + return data_num_bytes_ - data_->header.num_bytes; + } + + // Access the handles. + const std::vector<Handle>* handles() const { return &handles_; } + std::vector<Handle>* mutable_handles() { return &handles_; } + + private: + void Initialize(); + void FreeDataAndCloseHandles(); + + uint32_t data_num_bytes_; + internal::MessageData* data_; + std::vector<Handle> handles_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Message); +}; + +class MessageReceiver { + public: + virtual ~MessageReceiver() {} + + // The receiver may mutate the given message. Returns true if the message + // was accepted and false otherwise, indicating that the message was invalid + // or malformed. + virtual bool Accept(Message* message) MOJO_WARN_UNUSED_RESULT = 0; +}; + +class MessageReceiverWithResponder : public MessageReceiver { + public: + ~MessageReceiverWithResponder() override {} + + // A variant on Accept that registers a MessageReceiver (known as the + // responder) to handle the response message generated from the given + // message. The responder's Accept method may be called during + // AcceptWithResponder or some time after its return. + // + // NOTE: Upon returning true, AcceptWithResponder assumes ownership of + // |responder| and will delete it after calling |responder->Accept| or upon + // its own destruction. + // + virtual bool AcceptWithResponder(Message* message, MessageReceiver* responder) + MOJO_WARN_UNUSED_RESULT = 0; +}; + +// A MessageReceiver that is also able to provide status about the state +// of the underlying MessagePipe to which it will be forwarding messages +// received via the |Accept()| call. +class MessageReceiverWithStatus : public MessageReceiver { + public: + ~MessageReceiverWithStatus() override {} + + // Returns |true| if this MessageReceiver is currently bound to a MessagePipe, + // the pipe has not been closed, and the pipe has not encountered an error. + virtual bool IsValid() = 0; +}; + +// An alternative to MessageReceiverWithResponder for cases in which it +// is necessary for the implementor of this interface to know about the status +// of the MessagePipe which will carry the responses. +class MessageReceiverWithResponderStatus : public MessageReceiver { + public: + ~MessageReceiverWithResponderStatus() override {} + + // A variant on Accept that registers a MessageReceiverWithStatus (known as + // the responder) to handle the response message generated from the given + // message. Any of the responder's methods (Accept or IsValid) may be called + // during AcceptWithResponder or some time after its return. + // + // NOTE: Upon returning true, AcceptWithResponder assumes ownership of + // |responder| and will delete it after calling |responder->Accept| or upon + // its own destruction. + // + virtual bool AcceptWithResponder(Message* message, + MessageReceiverWithStatus* responder) + MOJO_WARN_UNUSED_RESULT = 0; +}; + +// Read a single message from the pipe and dispatch to the given receiver. The +// receiver may be null, in which case the message is simply discarded. +// Returns MOJO_RESULT_SHOULD_WAIT if the caller should wait on the handle to +// become readable. Returns MOJO_RESULT_OK if a message was dispatched and +// otherwise returns an error code if something went wrong. +// +// NOTE: The message hasn't been validated and may be malformed! +MojoResult ReadAndDispatchMessage(MessagePipeHandle handle, + MessageReceiver* receiver, + bool* receiver_result); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ diff --git a/mojo/public/cpp/bindings/message_filter.h b/mojo/public/cpp/bindings/message_filter.h new file mode 100644 index 0000000..8e10f6b --- /dev/null +++ b/mojo/public/cpp/bindings/message_filter.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_FILTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_FILTER_H_ + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// This class is the base class for message filters. Subclasses should +// implement the pure virtual method Accept() inherited from MessageReceiver to +// process messages and/or forward them to |sink_|. +class MessageFilter : public MessageReceiver { + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit MessageFilter(MessageReceiver* sink = nullptr); + ~MessageFilter() override; + + void set_sink(MessageReceiver* sink) { sink_ = sink; } + + protected: + MessageReceiver* sink_; +}; + +// A trivial filter that simply forwards every message it receives to |sink_|. +class PassThroughFilter : public MessageFilter { + public: + explicit PassThroughFilter(MessageReceiver* sink = nullptr); + + bool Accept(Message* message) override; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_FILTER_H_ diff --git a/mojo/public/cpp/bindings/no_interface.h b/mojo/public/cpp/bindings/no_interface.h new file mode 100644 index 0000000..d8915cd --- /dev/null +++ b/mojo/public/cpp/bindings/no_interface.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// NoInterface is for use in cases when a non-existent or empty interface is +// needed. + +class NoInterfaceProxy; +class NoInterfaceStub; + +class NoInterface { + public: + static const char* Name_; + typedef NoInterfaceProxy Proxy_; + typedef NoInterfaceStub Stub_; + typedef PassThroughFilter RequestValidator_; + typedef PassThroughFilter ResponseValidator_; + virtual ~NoInterface() {} +}; + +class NoInterfaceProxy : public NoInterface { + public: + explicit NoInterfaceProxy(MessageReceiver* receiver) {} +}; + +class NoInterfaceStub : public MessageReceiverWithResponder { + public: + NoInterfaceStub() {} + void set_sink(NoInterface* sink) {} + NoInterface* sink() { return nullptr; } + bool Accept(Message* message) override; + bool AcceptWithResponder(Message* message, + MessageReceiver* responder) override; +}; + +// AnyInterface is for use in cases where any interface would do (e.g., see the +// Shell::Connect method). + +typedef NoInterface AnyInterface; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ diff --git a/mojo/public/cpp/bindings/string.h b/mojo/public/cpp/bindings/string.h new file mode 100644 index 0000000..e0ed4ba --- /dev/null +++ b/mojo/public/cpp/bindings/string.h @@ -0,0 +1,175 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ + +#include <string> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/type_converter.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { + +// A UTF-8 encoded character string that can be null. Provides functions that +// are similar to std::string, along with access to the underlying std::string +// object. +class String { + public: + typedef internal::String_Data Data_; + + String() : is_null_(true) {} + String(const std::string& str) : value_(str), is_null_(false) {} + String(const char* chars) : is_null_(!chars) { + if (chars) + value_ = chars; + } + String(const char* chars, size_t num_chars) + : value_(chars, num_chars), is_null_(false) {} + String(const mojo::String& str) + : value_(str.value_), is_null_(str.is_null_) {} + + template <size_t N> + String(const char chars[N]) + : value_(chars, N - 1), is_null_(false) {} + + template <typename U> + static String From(const U& other) { + return TypeConverter<String, U>::Convert(other); + } + + template <typename U> + U To() const { + return TypeConverter<U, String>::Convert(*this); + } + + String& operator=(const mojo::String& str) { + value_ = str.value_; + is_null_ = str.is_null_; + return *this; + } + String& operator=(const std::string& str) { + value_ = str; + is_null_ = false; + return *this; + } + String& operator=(const char* chars) { + is_null_ = !chars; + if (chars) { + value_ = chars; + } else { + value_.clear(); + } + return *this; + } + + void reset() { + value_.clear(); + is_null_ = true; + } + + bool is_null() const { return is_null_; } + + size_t size() const { return value_.size(); } + + const char* data() const { return value_.data(); } + + const char& at(size_t offset) const { return value_.at(offset); } + const char& operator[](size_t offset) const { return value_[offset]; } + + const std::string& get() const { return value_; } + operator const std::string&() const { return value_; } + + void Swap(String* other) { + std::swap(is_null_, other->is_null_); + value_.swap(other->value_); + } + + void Swap(std::string* other) { + is_null_ = false; + value_.swap(*other); + } + + private: + typedef std::string String::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &String::value_; } + + private: + std::string value_; + bool is_null_; +}; + +inline bool operator==(const String& a, const String& b) { + return a.is_null() == b.is_null() && a.get() == b.get(); +} +inline bool operator==(const char* a, const String& b) { + return !b.is_null() && a == b.get(); +} +inline bool operator==(const String& a, const char* b) { + return !a.is_null() && a.get() == b; +} +inline bool operator!=(const String& a, const String& b) { + return !(a == b); +} +inline bool operator!=(const char* a, const String& b) { + return !(a == b); +} +inline bool operator!=(const String& a, const char* b) { + return !(a == b); +} + +inline std::ostream& operator<<(std::ostream& out, const String& s) { + return out << s.get(); +} + +inline bool operator<(const String& a, const String& b) { + if (a.is_null()) + return !b.is_null(); + if (b.is_null()) + return false; + + return a.get() < b.get(); +} + +// TODO(darin): Add similar variants of operator<,<=,>,>= + +template <> +struct TypeConverter<String, std::string> { + static String Convert(const std::string& input) { return String(input); } +}; + +template <> +struct TypeConverter<std::string, String> { + static std::string Convert(const String& input) { return input; } +}; + +template <size_t N> +struct TypeConverter<String, char[N]> { + static String Convert(const char input[N]) { + MOJO_DCHECK(input); + return String(input, N - 1); + } +}; + +// Appease MSVC. +template <size_t N> +struct TypeConverter<String, const char[N]> { + static String Convert(const char input[N]) { + MOJO_DCHECK(input); + return String(input, N - 1); + } +}; + +template <> +struct TypeConverter<String, const char*> { + // |input| may be null, in which case a null String will be returned. + static String Convert(const char* input) { return String(input); } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ diff --git a/mojo/public/cpp/bindings/strong_binding.h b/mojo/public/cpp/bindings/strong_binding.h new file mode 100644 index 0000000..860260e --- /dev/null +++ b/mojo/public/cpp/bindings/strong_binding.h @@ -0,0 +1,127 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ + +#include <assert.h> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// This connects an interface implementation strongly to a pipe. When a +// connection error is detected the implementation is deleted. Deleting the +// connector also closes the pipe. +// +// Example of an implementation that is always bound strongly to a pipe +// +// class StronglyBound : public Foo { +// public: +// explicit StronglyBound(InterfaceRequest<Foo> request) +// : binding_(this, request.Pass()) {} +// +// // Foo implementation here +// +// private: +// StrongBinding<Foo> binding_; +// }; +// +// class MyFooFactory : public InterfaceFactory<Foo> { +// public: +// void Create(..., InterfaceRequest<Foo> request) override { +// new StronglyBound(request.Pass()); // The binding now owns the +// // instance of StronglyBound. +// } +// }; +template <typename Interface> +class StrongBinding { + MOJO_MOVE_ONLY_TYPE(StrongBinding) + + public: + explicit StrongBinding(Interface* impl) : binding_(impl) { + binding_.set_connection_error_handler([this]() { OnConnectionError(); }); + } + + StrongBinding( + Interface* impl, + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : StrongBinding(impl) { + binding_.Bind(handle.Pass(), waiter); + } + + StrongBinding( + Interface* impl, + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : StrongBinding(impl) { + binding_.Bind(ptr, waiter); + } + + StrongBinding( + Interface* impl, + InterfaceRequest<Interface> request, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) + : StrongBinding(impl) { + binding_.Bind(request.Pass(), waiter); + } + + ~StrongBinding() {} + + void Bind( + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + assert(!binding_.is_bound()); + binding_.Bind(handle.Pass(), waiter); + } + + void Bind( + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + assert(!binding_.is_bound()); + binding_.Bind(ptr, waiter); + } + + void Bind( + InterfaceRequest<Interface> request, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + assert(!binding_.is_bound()); + binding_.Bind(request.Pass(), waiter); + } + + bool WaitForIncomingMethodCall() { + return binding_.WaitForIncomingMethodCall(); + } + + // Note: The error handler must not delete the interface implementation. + void set_connection_error_handler(const Closure& error_handler) { + connection_error_handler_ = error_handler; + } + + Interface* impl() { return binding_.impl(); } + // Exposed for testing, should not generally be used. + internal::Router* internal_router() { return binding_.internal_router(); } + + void OnConnectionError() { + connection_error_handler_.Run(); + delete binding_.impl(); + } + + private: + Closure connection_error_handler_; + Binding<Interface> binding_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h new file mode 100644 index 0000000..b324e93 --- /dev/null +++ b/mojo/public/cpp/bindings/struct_ptr.h @@ -0,0 +1,206 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ + +#include <new> + +#include "mojo/public/cpp/bindings/type_converter.h" +#include "mojo/public/cpp/environment/logging.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +template <typename Struct> +class StructHelper { + public: + template <typename Ptr> + static void Initialize(Ptr* ptr) { + ptr->Initialize(); + } +}; + +} // namespace internal + +// Smart pointer wrapping a mojom structure with move-only semantics. +template <typename Struct> +class StructPtr { + MOJO_MOVE_ONLY_TYPE(StructPtr) + + public: + + StructPtr() : ptr_(nullptr) {} + StructPtr(decltype(nullptr)) : ptr_(nullptr) {} + + ~StructPtr() { delete ptr_; } + + StructPtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + StructPtr(StructPtr&& other) : ptr_(nullptr) { Take(&other); } + StructPtr& operator=(StructPtr&& other) { + Take(&other); + return *this; + } + + template <typename U> + U To() const { + return TypeConverter<U, StructPtr>::Convert(*this); + } + + void reset() { + if (ptr_) { + delete ptr_; + ptr_ = nullptr; + } + } + + bool is_null() const { return ptr_ == nullptr; } + + Struct& operator*() const { + MOJO_DCHECK(ptr_); + return *ptr_; + } + Struct* operator->() const { + MOJO_DCHECK(ptr_); + return ptr_; + } + Struct* get() const { return ptr_; } + + void Swap(StructPtr* other) { std::swap(ptr_, other->ptr_); } + + // Please note that calling this method will fail compilation if the value + // type |Struct| doesn't have a Clone() method defined (which usually means + // that it contains Mojo handles). + StructPtr Clone() const { return is_null() ? StructPtr() : ptr_->Clone(); } + + bool Equals(const StructPtr& other) const { + if (is_null() || other.is_null()) + return is_null() && other.is_null(); + return ptr_->Equals(*other.ptr_); + } + + private: + typedef Struct* StructPtr::*Testable; + + public: + operator Testable() const { return ptr_ ? &StructPtr::ptr_ : 0; } + + private: + friend class internal::StructHelper<Struct>; + + // Forbid the == and != operators explicitly, otherwise StructPtr will be + // converted to Testable to do == or != comparison. + template <typename T> + bool operator==(const StructPtr<T>& other) const = delete; + template <typename T> + bool operator!=(const StructPtr<T>& other) const = delete; + + void Initialize() { + MOJO_DCHECK(!ptr_); + ptr_ = new Struct(); + } + + void Take(StructPtr* other) { + reset(); + Swap(other); + } + + Struct* ptr_; +}; + +// Designed to be used when Struct is small and copyable. +template <typename Struct> +class InlinedStructPtr { + MOJO_MOVE_ONLY_TYPE(InlinedStructPtr); + + public: + + InlinedStructPtr() : is_null_(true) {} + InlinedStructPtr(decltype(nullptr)) : is_null_(true) {} + + ~InlinedStructPtr() {} + + InlinedStructPtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + InlinedStructPtr(InlinedStructPtr&& other) : is_null_(true) { Take(&other); } + InlinedStructPtr& operator=(InlinedStructPtr&& other) { + Take(&other); + return *this; + } + + template <typename U> + U To() const { + return TypeConverter<U, InlinedStructPtr>::Convert(*this); + } + + void reset() { + is_null_ = true; + value_. ~Struct(); + new (&value_) Struct(); + } + + bool is_null() const { return is_null_; } + + Struct& operator*() const { + MOJO_DCHECK(!is_null_); + return value_; + } + Struct* operator->() const { + MOJO_DCHECK(!is_null_); + return &value_; + } + Struct* get() const { return &value_; } + + void Swap(InlinedStructPtr* other) { + std::swap(value_, other->value_); + std::swap(is_null_, other->is_null_); + } + + InlinedStructPtr Clone() const { + return is_null() ? InlinedStructPtr() : value_.Clone(); + } + bool Equals(const InlinedStructPtr& other) const { + if (is_null() || other.is_null()) + return is_null() && other.is_null(); + return value_.Equals(other.value_); + } + + private: + typedef Struct InlinedStructPtr::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &InlinedStructPtr::value_; } + + private: + friend class internal::StructHelper<Struct>; + + // Forbid the == and != operators explicitly, otherwise InlinedStructPtr will + // be converted to Testable to do == or != comparison. + template <typename T> + bool operator==(const InlinedStructPtr<T>& other) const = delete; + template <typename T> + bool operator!=(const InlinedStructPtr<T>& other) const = delete; + + void Initialize() { is_null_ = false; } + + void Take(InlinedStructPtr* other) { + reset(); + Swap(other); + } + + mutable Struct value_; + bool is_null_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn new file mode 100644 index 0000000..1bc018c --- /dev/null +++ b/mojo/public/cpp/bindings/tests/BUILD.gn @@ -0,0 +1,86 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../../mojo_application.gni") +import("../../../mojo_sdk.gni") + +mojo_sdk_source_set("tests") { + testonly = true + + sources = [ + "array_unittest.cc", + "binding_callback_unittest.cc", + "binding_unittest.cc", + "bounds_checker_unittest.cc", + "buffer_unittest.cc", + "callback_unittest.cc", + "connector_unittest.cc", + "constant_unittest.cc", + "container_test_util.cc", + "equals_unittest.cc", + "handle_passing_unittest.cc", + "interface_ptr_unittest.cc", + "map_unittest.cc", + "message_queue.cc", + "message_queue.h", + "request_response_unittest.cc", + "router_unittest.cc", + "sample_service_unittest.cc", + "serialization_warning_unittest.cc", + "string_unittest.cc", + "struct_unittest.cc", + "type_conversion_unittest.cc", + "union_unittest.cc", + "validation_unittest.cc", + ] + + deps = [ + ":mojo_public_bindings_test_utils", + "//mojo/environment:chromium", + "//mojo/message_pump", + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/cpp/bindings", + "mojo/public/cpp/bindings:callback", + "mojo/public/cpp/system", + "mojo/public/cpp/test_support:test_utils", + "mojo/public/cpp/utility", + "mojo/public/interfaces/bindings/tests:test_associated_interfaces", + "mojo/public/interfaces/bindings/tests:test_interfaces", + "mojo/public/interfaces/bindings/tests:test_interfaces_experimental", + ] +} + +mojo_sdk_source_set("perftests") { + testonly = true + + sources = [ + "bindings_perftest.cc", + ] + + deps = [ + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/cpp/bindings", + "mojo/public/cpp/bindings:callback", + "mojo/public/cpp/environment:standalone", + "mojo/public/cpp/system", + "mojo/public/cpp/test_support:test_utils", + "mojo/public/cpp/utility", + "mojo/public/interfaces/bindings/tests:test_interfaces", + ] +} + +mojo_sdk_source_set("mojo_public_bindings_test_utils") { + sources = [ + "validation_test_input_parser.cc", + "validation_test_input_parser.h", + ] + + mojo_sdk_deps = [ "mojo/public/c/system" ] +} diff --git a/mojo/public/cpp/bindings/tests/array_unittest.cc b/mojo/public/cpp/bindings/tests/array_unittest.cc new file mode 100644 index 0000000..8befe81 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/array_unittest.cc @@ -0,0 +1,438 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/tests/container_test_util.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::Array_Data; +using mojo::internal::ArrayValidateParams; +using mojo::internal::FixedBufferForTesting; +using mojo::internal::String_Data; + +using ArrayTest = testing::Test; + +// Tests that basic Array operations work. +TEST_F(ArrayTest, Basic) { + Array<char> array(8); + for (size_t i = 0; i < array.size(); ++i) { + char val = static_cast<char>(i * 2); + array[i] = val; + EXPECT_EQ(val, array.at(i)); + } +} + +// Tests that basic Array<bool> operations work. +TEST_F(ArrayTest, Bool) { + Array<bool> array(64); + for (size_t i = 0; i < array.size(); ++i) { + bool val = i % 3 == 0; + array[i] = val; + EXPECT_EQ(val, array.at(i)); + } +} + +// Tests that Array<ScopedMessagePipeHandle> supports transferring handles. +TEST_F(ArrayTest, Handle) { + MessagePipe pipe; + Array<ScopedMessagePipeHandle> handles(2); + handles[0] = pipe.handle0.Pass(); + handles[1].reset(pipe.handle1.release()); + + EXPECT_FALSE(pipe.handle0.is_valid()); + EXPECT_FALSE(pipe.handle1.is_valid()); + + Array<ScopedMessagePipeHandle> handles2 = handles.Pass(); + EXPECT_TRUE(handles2[0].is_valid()); + EXPECT_TRUE(handles2[1].is_valid()); + + ScopedMessagePipeHandle pipe_handle = handles2[0].Pass(); + EXPECT_TRUE(pipe_handle.is_valid()); + EXPECT_FALSE(handles2[0].is_valid()); +} + +// Tests that Array<ScopedMessagePipeHandle> supports closing handles. +TEST_F(ArrayTest, HandlesAreClosed) { + MessagePipe pipe; + MojoHandle pipe0_value = pipe.handle0.get().value(); + MojoHandle pipe1_value = pipe.handle0.get().value(); + + { + Array<ScopedMessagePipeHandle> handles(2); + handles[0] = pipe.handle0.Pass(); + handles[1].reset(pipe.handle0.release()); + } + + // We expect the pipes to have been closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(pipe0_value)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(pipe1_value)); +} + +TEST_F(ArrayTest, Clone) { + { + // Test POD. + Array<int32_t> array(3); + for (size_t i = 0; i < array.size(); ++i) + array[i] = static_cast<int32_t>(i); + + Array<int32_t> clone_array = array.Clone(); + EXPECT_EQ(array.size(), clone_array.size()); + for (size_t i = 0; i < array.size(); ++i) + EXPECT_EQ(array[i], clone_array[i]); + } + + { + // Test copyable object. + Array<String> array(2); + array[0] = "hello"; + array[1] = "world"; + + Array<String> clone_array = array.Clone(); + EXPECT_EQ(array.size(), clone_array.size()); + for (size_t i = 0; i < array.size(); ++i) + EXPECT_EQ(array[i], clone_array[i]); + } + + { + // Test struct. + Array<RectPtr> array(2); + array[1] = Rect::New(); + array[1]->x = 1; + array[1]->y = 2; + array[1]->width = 3; + array[1]->height = 4; + + Array<RectPtr> clone_array = array.Clone(); + EXPECT_EQ(array.size(), clone_array.size()); + EXPECT_TRUE(clone_array[0].is_null()); + EXPECT_EQ(array[1]->x, clone_array[1]->x); + EXPECT_EQ(array[1]->y, clone_array[1]->y); + EXPECT_EQ(array[1]->width, clone_array[1]->width); + EXPECT_EQ(array[1]->height, clone_array[1]->height); + } + + { + // Test array of array. + Array<Array<int8_t>> array(2); + array[1] = Array<int8_t>(2); + array[1][0] = 0; + array[1][1] = 1; + + Array<Array<int8_t>> clone_array = array.Clone(); + EXPECT_EQ(array.size(), clone_array.size()); + EXPECT_TRUE(clone_array[0].is_null()); + EXPECT_EQ(array[1].size(), clone_array[1].size()); + EXPECT_EQ(array[1][0], clone_array[1][0]); + EXPECT_EQ(array[1][1], clone_array[1][1]); + } + + { + // Test that array of handles still works although Clone() is not available. + Array<ScopedMessagePipeHandle> array(10); + EXPECT_FALSE(array[0].is_valid()); + } +} + +TEST_F(ArrayTest, Serialization_ArrayOfPOD) { + Array<int32_t> array(4); + for (size_t i = 0; i < array.size(); ++i) + array[i] = static_cast<int32_t>(i); + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(8U + 4 * 4U, size); + + FixedBufferForTesting buf(size); + Array_Data<int32_t>* data; + ArrayValidateParams validate_params(0, false, nullptr); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<int32_t> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(4U, array2.size()); + for (size_t i = 0; i < array2.size(); ++i) + EXPECT_EQ(static_cast<int32_t>(i), array2[i]); +} + +TEST_F(ArrayTest, Serialization_EmptyArrayOfPOD) { + Array<int32_t> array(0); + size_t size = GetSerializedSize_(array); + EXPECT_EQ(8U, size); + + FixedBufferForTesting buf(size); + Array_Data<int32_t>* data; + ArrayValidateParams validate_params(0, false, nullptr); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<int32_t> array2; + Deserialize_(data, &array2); + EXPECT_EQ(0U, array2.size()); +} + +TEST_F(ArrayTest, Serialization_ArrayOfArrayOfPOD) { + Array<Array<int32_t>> array(2); + for (size_t j = 0; j < array.size(); ++j) { + Array<int32_t> inner(4); + for (size_t i = 0; i < inner.size(); ++i) + inner[i] = static_cast<int32_t>(i + (j * 10)); + array[j] = inner.Pass(); + } + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(8U + 2 * 8U + 2 * (8U + 4 * 4U), size); + + FixedBufferForTesting buf(size); + Array_Data<Array_Data<int32_t>*>* data; + ArrayValidateParams validate_params( + 0, false, new ArrayValidateParams(0, false, nullptr)); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<Array<int32_t>> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(2U, array2.size()); + for (size_t j = 0; j < array2.size(); ++j) { + const Array<int32_t>& inner = array2[j]; + EXPECT_EQ(4U, inner.size()); + for (size_t i = 0; i < inner.size(); ++i) + EXPECT_EQ(static_cast<int32_t>(i + (j * 10)), inner[i]); + } +} + +TEST_F(ArrayTest, Serialization_ArrayOfBool) { + Array<bool> array(10); + for (size_t i = 0; i < array.size(); ++i) + array[i] = i % 2 ? true : false; + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(8U + 8U, size); + + FixedBufferForTesting buf(size); + Array_Data<bool>* data; + ArrayValidateParams validate_params(0, false, nullptr); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<bool> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(10U, array2.size()); + for (size_t i = 0; i < array2.size(); ++i) + EXPECT_EQ(i % 2 ? true : false, array2[i]); +} + +TEST_F(ArrayTest, Serialization_ArrayOfString) { + Array<String> array(10); + for (size_t i = 0; i < array.size(); ++i) { + char c = 'A' + static_cast<char>(i); + array[i] = String(&c, 1); + } + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(8U + // array header + 10 * 8U + // array payload (10 pointers) + 10 * (8U + // string header + 8U), // string length of 1 padded to 8 + size); + + FixedBufferForTesting buf(size); + Array_Data<String_Data*>* data; + ArrayValidateParams validate_params( + 0, false, new ArrayValidateParams(0, false, nullptr)); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<String> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(10U, array2.size()); + for (size_t i = 0; i < array2.size(); ++i) { + char c = 'A' + static_cast<char>(i); + EXPECT_EQ(String(&c, 1), array2[i]); + } +} + +TEST_F(ArrayTest, Resize_Copyable) { + ASSERT_EQ(0u, CopyableType::num_instances()); + mojo::Array<CopyableType> array(3); + std::vector<CopyableType*> value_ptrs; + value_ptrs.push_back(array[0].ptr()); + value_ptrs.push_back(array[1].ptr()); + + for (size_t i = 0; i < array.size(); i++) + array[i].ResetCopied(); + + array.resize(2); + ASSERT_EQ(2u, array.size()); + EXPECT_EQ(array.size(), CopyableType::num_instances()); + for (size_t i = 0; i < array.size(); i++) { + EXPECT_FALSE(array[i].copied()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + + array.resize(3); + array[2].ResetCopied(); + ASSERT_EQ(3u, array.size()); + EXPECT_EQ(array.size(), CopyableType::num_instances()); + for (size_t i = 0; i < array.size(); i++) + EXPECT_FALSE(array[i].copied()); + value_ptrs.push_back(array[2].ptr()); + + size_t capacity = array.storage().capacity(); + array.resize(capacity); + ASSERT_EQ(capacity, array.size()); + EXPECT_EQ(array.size(), CopyableType::num_instances()); + for (size_t i = 0; i < 3; i++) + EXPECT_FALSE(array[i].copied()); + for (size_t i = 3; i < array.size(); i++) { + array[i].ResetCopied(); + value_ptrs.push_back(array[i].ptr()); + } + + array.resize(capacity + 2); + ASSERT_EQ(capacity + 2, array.size()); + EXPECT_EQ(array.size(), CopyableType::num_instances()); + for (size_t i = 0; i < capacity; i++) { + EXPECT_TRUE(array[i].copied()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + array.reset(); + EXPECT_EQ(0u, CopyableType::num_instances()); + EXPECT_FALSE(array); + array.resize(0); + EXPECT_EQ(0u, CopyableType::num_instances()); + EXPECT_TRUE(array); +} + +TEST_F(ArrayTest, Resize_MoveOnly) { + ASSERT_EQ(0u, MoveOnlyType::num_instances()); + mojo::Array<MoveOnlyType> array(3); + std::vector<MoveOnlyType*> value_ptrs; + value_ptrs.push_back(array[0].ptr()); + value_ptrs.push_back(array[1].ptr()); + + for (size_t i = 0; i < array.size(); i++) + EXPECT_FALSE(array[i].moved()); + + array.resize(2); + ASSERT_EQ(2u, array.size()); + EXPECT_EQ(array.size(), MoveOnlyType::num_instances()); + for (size_t i = 0; i < array.size(); i++) { + EXPECT_FALSE(array[i].moved()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + + array.resize(3); + ASSERT_EQ(3u, array.size()); + EXPECT_EQ(array.size(), MoveOnlyType::num_instances()); + for (size_t i = 0; i < array.size(); i++) + EXPECT_FALSE(array[i].moved()); + value_ptrs.push_back(array[2].ptr()); + + size_t capacity = array.storage().capacity(); + array.resize(capacity); + ASSERT_EQ(capacity, array.size()); + EXPECT_EQ(array.size(), MoveOnlyType::num_instances()); + for (size_t i = 0; i < array.size(); i++) + EXPECT_FALSE(array[i].moved()); + for (size_t i = 3; i < array.size(); i++) + value_ptrs.push_back(array[i].ptr()); + + array.resize(capacity + 2); + ASSERT_EQ(capacity + 2, array.size()); + EXPECT_EQ(array.size(), MoveOnlyType::num_instances()); + for (size_t i = 0; i < capacity; i++) { + EXPECT_TRUE(array[i].moved()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + for (size_t i = capacity; i < array.size(); i++) + EXPECT_FALSE(array[i].moved()); + + array.reset(); + EXPECT_EQ(0u, MoveOnlyType::num_instances()); + EXPECT_FALSE(array); + array.resize(0); + EXPECT_EQ(0u, MoveOnlyType::num_instances()); + EXPECT_TRUE(array); +} + +TEST_F(ArrayTest, PushBack_Copyable) { + ASSERT_EQ(0u, CopyableType::num_instances()); + mojo::Array<CopyableType> array(2); + array.reset(); + std::vector<CopyableType*> value_ptrs; + size_t capacity = array.storage().capacity(); + for (size_t i = 0; i < capacity; i++) { + CopyableType value; + value_ptrs.push_back(value.ptr()); + array.push_back(value); + ASSERT_EQ(i + 1, array.size()); + ASSERT_EQ(i + 1, value_ptrs.size()); + EXPECT_EQ(array.size() + 1, CopyableType::num_instances()); + EXPECT_TRUE(array[i].copied()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + array[i].ResetCopied(); + EXPECT_TRUE(array); + } + { + CopyableType value; + value_ptrs.push_back(value.ptr()); + array.push_back(value); + EXPECT_EQ(array.size() + 1, CopyableType::num_instances()); + } + ASSERT_EQ(capacity + 1, array.size()); + EXPECT_EQ(array.size(), CopyableType::num_instances()); + + for (size_t i = 0; i < array.size(); i++) { + EXPECT_TRUE(array[i].copied()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + array.reset(); + EXPECT_EQ(0u, CopyableType::num_instances()); +} + +TEST_F(ArrayTest, PushBack_MoveOnly) { + ASSERT_EQ(0u, MoveOnlyType::num_instances()); + mojo::Array<MoveOnlyType> array(2); + array.reset(); + std::vector<MoveOnlyType*> value_ptrs; + size_t capacity = array.storage().capacity(); + for (size_t i = 0; i < capacity; i++) { + MoveOnlyType value; + value_ptrs.push_back(value.ptr()); + array.push_back(value.Pass()); + ASSERT_EQ(i + 1, array.size()); + ASSERT_EQ(i + 1, value_ptrs.size()); + EXPECT_EQ(array.size() + 1, MoveOnlyType::num_instances()); + EXPECT_TRUE(array[i].moved()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + array[i].ResetMoved(); + EXPECT_TRUE(array); + } + { + MoveOnlyType value; + value_ptrs.push_back(value.ptr()); + array.push_back(value.Pass()); + EXPECT_EQ(array.size() + 1, MoveOnlyType::num_instances()); + } + ASSERT_EQ(capacity + 1, array.size()); + EXPECT_EQ(array.size(), MoveOnlyType::num_instances()); + + for (size_t i = 0; i < array.size(); i++) { + EXPECT_TRUE(array[i].moved()); + EXPECT_EQ(value_ptrs[i], array[i].ptr()); + } + array.reset(); + EXPECT_EQ(0u, MoveOnlyType::num_instances()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc new file mode 100644 index 0000000..83d74f9 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc @@ -0,0 +1,305 @@ +// Copyright 2015 The Chromium 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 "base/message_loop/message_loop.h" +#include "build/build_config.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// The tests in this file are designed to test the interaction between a +// Callback and its associated Binding. If a Callback is deleted before +// being used we DCHECK fail--unless the associated Binding has already +// been closed or deleted. This contract must be explained to the Mojo +// application developer. For example it is the developer's responsibility to +// ensure that the Binding is destroyed before an unused Callback is destroyed. +// +/////////////////////////////////////////////////////////////////////////////// + +namespace mojo { +namespace test { +namespace { + +// A Runnable object that saves the last value it sees via the +// provided int32_t*. Used on the client side. +class ValueSaver { + public: + explicit ValueSaver(int32_t* last_value_seen) + : last_value_seen_(last_value_seen) {} + void Run(int32_t x) const { *last_value_seen_ = x; } + + private: + int32_t* const last_value_seen_; +}; + +// An implementation of sample::Provider used on the server side. +// It only implements one of the methods: EchoInt(). +// All it does is save the values and Callbacks it sees. +class InterfaceImpl : public sample::Provider { + public: + InterfaceImpl() + : last_server_value_seen_(0), + callback_saved_(new Callback<void(int32_t)>()) {} + + ~InterfaceImpl() override { + if (callback_saved_) { + delete callback_saved_; + } + } + + // Run's the callback previously saved from the last invocation + // of |EchoInt()|. + bool RunCallback() { + if (callback_saved_) { + callback_saved_->Run(last_server_value_seen_); + return true; + } + return false; + } + + // Delete's the previously saved callback. + void DeleteCallback() { + delete callback_saved_; + callback_saved_ = nullptr; + } + + // sample::Provider implementation + + // Saves its two input values in member variables and does nothing else. + void EchoInt(int32_t x, const Callback<void(int32_t)>& callback) override { + last_server_value_seen_ = x; + *callback_saved_ = callback; + } + + void EchoString(const String& a, + const Callback<void(String)>& callback) override { + MOJO_CHECK(false) << "Not implemented."; + } + + void EchoStrings(const String& a, + const String& b, + const Callback<void(String, String)>& callback) override { + MOJO_CHECK(false) << "Not implemented."; + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const Callback<void(ScopedMessagePipeHandle)>& callback) override { + MOJO_CHECK(false) << "Not implemented."; + } + + void EchoEnum(sample::Enum a, + const Callback<void(sample::Enum)>& callback) override { + MOJO_CHECK(false) << "Not implemented."; + } + + void resetLastServerValueSeen() { last_server_value_seen_ = 0; } + + int32_t last_server_value_seen() const { return last_server_value_seen_; } + + private: + int32_t last_server_value_seen_; + Callback<void(int32_t)>* callback_saved_; +}; + +class BindingCallbackTest : public testing::Test { + public: + BindingCallbackTest() : loop_(common::MessagePumpMojo::Create()) {} + ~BindingCallbackTest() override {} + + protected: + int32_t last_client_callback_value_seen_; + sample::ProviderPtr interface_ptr_; + + void PumpMessages() { loop_.RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +// Tests that the InterfacePtr and the Binding can communicate with each +// other normally. +TEST_F(BindingCallbackTest, Basic) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, GetProxy(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + interface_ptr_->EchoInt(7, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback. + server_impl.RunCallback(); + PumpMessages(); + + // Check that the client has now seen the correct value. + EXPECT_EQ(7, last_client_callback_value_seen_); + + // Initialize the test values again. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method again. + interface_ptr_->EchoInt(13, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(13, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback again. + server_impl.RunCallback(); + PumpMessages(); + + // Check that the client has now seen the correct value again. + EXPECT_EQ(13, last_client_callback_value_seen_); +} + +// Tests that running the Callback after the Binding has been deleted +// results in a clean failure. +TEST_F(BindingCallbackTest, DeleteBindingThenRunCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + { + // Create the binding in an inner scope so it can be deleted first. + Binding<sample::Provider> binding(&server_impl, GetProxy(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + interface_ptr_->EchoInt(7, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now try to run the Callback. This should do nothing since the pipe + // is closed. + EXPECT_TRUE(server_impl.RunCallback()); + PumpMessages(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Attempt to invoke the method again and confirm that an error was + // encountered. + interface_ptr_->EchoInt(13, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + EXPECT_TRUE(interface_ptr_.encountered_error()); +} + +// Tests that deleting a Callback without running it after the corresponding +// binding has already been deleted does not result in a crash. +TEST_F(BindingCallbackTest, DeleteBindingThenDeleteCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + { + // Create the binding in an inner scope so it can be deleted first. + Binding<sample::Provider> binding(&server_impl, GetProxy(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + interface_ptr_->EchoInt(7, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Delete the callback without running it. This should not + // cause a problem because the insfrastructure can detect that the + // binding has already been destroyed and the pipe is closed. + server_impl.DeleteCallback(); +} + +// Tests that closing a Binding allows us to delete a callback +// without running it without encountering a crash. +TEST_F(BindingCallbackTest, CloseBindingBeforeDeletingCallback) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, GetProxy(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + interface_ptr_->EchoInt(7, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now close the Binding. + binding.Close(); + + // Delete the callback without running it. This should not + // cause a crash because the insfrastructure can detect that the + // binding has already been closed. + server_impl.DeleteCallback(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); +} + +// Tests that deleting a Callback without using it before the +// Binding has been destroyed or closed results in a DCHECK. +TEST_F(BindingCallbackTest, DeleteCallbackBeforeBindingDeathTest) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, GetProxy(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + interface_ptr_->EchoInt(7, ValueSaver(&last_client_callback_value_seen_)); + PumpMessages(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) + // Delete the callback without running it. This should cause a crash in debug + // builds due to a DCHECK. + std::string regex("Check failed: !callback_was_dropped."); +#if defined(OS_WIN) + // TODO(msw): Fix MOJO_DCHECK logs and EXPECT_DEATH* on Win: crbug.com/535014 + regex.clear(); +#endif // OS_WIN + EXPECT_DEATH_IF_SUPPORTED(server_impl.DeleteCallback(), regex.c_str()); +#endif // !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_unittest.cc b/mojo/public/cpp/bindings/tests/binding_unittest.cc new file mode 100644 index 0000000..1462b33 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc @@ -0,0 +1,364 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: This file tests both binding.h (mojo::Binding) and strong_binding.h +// (mojo::StrongBinding). + +#include "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class BindingTestBase : public testing::Test { + public: + BindingTestBase() : loop_(common::MessagePumpMojo::Create()) {} + ~BindingTestBase() override {} + + base::MessageLoop& loop() { return loop_; } + + private: + base::MessageLoop loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(BindingTestBase); +}; + +class ServiceImpl : public sample::Service { + public: + explicit ServiceImpl(bool* was_deleted = nullptr) + : was_deleted_(was_deleted) {} + ~ServiceImpl() override { + if (was_deleted_) + *was_deleted_ = true; + } + + private: + // sample::Service implementation + void Frobinate(sample::FooPtr foo, + BazOptions options, + sample::PortPtr port, + const FrobinateCallback& callback) override { + callback.Run(1); + } + void GetPort(InterfaceRequest<sample::Port> port) override {} + + bool* const was_deleted_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceImpl); +}; + +// BindingTest ----------------------------------------------------------------- + +using BindingTest = BindingTestBase; + +TEST_F(BindingTest, Close) { + bool called = false; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ptr.set_connection_error_handler([&called]() { called = true; }); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, request.Pass()); + + binding.Close(); + EXPECT_FALSE(called); + loop().RunUntilIdle(); + EXPECT_TRUE(called); +} + +// Tests that destroying a mojo::Binding closes the bound message pipe handle. +TEST_F(BindingTest, DestroyClosesMessagePipe) { + bool encountered_error = false; + ServiceImpl impl; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ptr.set_connection_error_handler( + [&encountered_error]() { encountered_error = true; }); + bool called = false; + auto called_cb = [&called](int32_t result) { called = true; }; + { + Binding<sample::Service> binding(&impl, request.Pass()); + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + } + // Now that the Binding is out of scope we should detect an error on the other + // end of the pipe. + loop().RunUntilIdle(); + EXPECT_TRUE(encountered_error); + + // And calls should fail. + called = false; + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +// Tests that the binding's connection error handler gets called when the other +// end is closed. +TEST_F(BindingTest, ConnectionError) { + bool called = false; + { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, GetProxy(&ptr)); + binding.set_connection_error_handler([&called]() { called = true; }); + ptr.reset(); + EXPECT_FALSE(called); + loop().RunUntilIdle(); + EXPECT_TRUE(called); + // We want to make sure that it isn't called again during destruction. + called = false; + } + EXPECT_FALSE(called); +} + +// Tests that calling Close doesn't result in the connection error handler being +// called. +TEST_F(BindingTest, CloseDoesntCallConnectionErrorHandler) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, GetProxy(&ptr)); + bool called = false; + binding.set_connection_error_handler([&called]() { called = true; }); + binding.Close(); + loop().RunUntilIdle(); + EXPECT_FALSE(called); + + // We can also close the other end, and the error handler still won't be + // called. + ptr.reset(); + loop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +class ServiceImplWithBinding : public ServiceImpl { + public: + ServiceImplWithBinding(bool* was_deleted, + InterfaceRequest<sample::Service> request) + : ServiceImpl(was_deleted), binding_(this, request.Pass()) { + binding_.set_connection_error_handler([this]() { delete this; }); + } + + private: + Binding<sample::Service> binding_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceImplWithBinding); +}; + +// Tests that the binding may be deleted in the connection error handler. +TEST_F(BindingTest, SelfDeleteOnConnectionError) { + bool was_deleted = false; + sample::ServicePtr ptr; + // This should delete itself on connection error. + new ServiceImplWithBinding(&was_deleted, GetProxy(&ptr)); + ptr.reset(); + EXPECT_FALSE(was_deleted); + loop().RunUntilIdle(); + EXPECT_TRUE(was_deleted); +} + +// Tests that explicitly calling Unbind followed by rebinding works. +TEST_F(BindingTest, Unbind) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, GetProxy(&ptr)); + + bool called = false; + auto called_cb = [&called](int32_t result) { called = true; }; + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_TRUE(called); + + called = false; + auto request = binding.Unbind(); + EXPECT_FALSE(binding.is_bound()); + // All calls should fail when not bound... + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_FALSE(called); + + called = false; + binding.Bind(request.Pass()); + EXPECT_TRUE(binding.is_bound()); + // ...and should succeed again when the rebound. + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_TRUE(called); +} + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() {} + ~IntegerAccessorImpl() override {} + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(1, sample::ENUM_VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override {} + + MOJO_DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl); +}; + +TEST_F(BindingTest, SetInterfacePtrVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, &ptr); + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(BindingTest, PauseResume) { + bool called = false; + auto called_cb = [&called](int32_t result) { called = true; }; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, request.Pass()); + binding.PauseIncomingMethodCallProcessing(); + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + EXPECT_FALSE(called); + loop().RunUntilIdle(); + // Frobinate() should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger processing. + binding.ResumeIncomingMethodCallProcessing(); + loop().RunUntilIdle(); + EXPECT_TRUE(called); +} + +// Verifies the connection error handler is not run while a binding is paused. +TEST_F(BindingTest, ErrorHandleNotRunWhilePaused) { + bool called = false; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, request.Pass()); + binding.set_connection_error_handler([&called]() { called = true; }); + binding.PauseIncomingMethodCallProcessing(); + + ptr.reset(); + loop().RunUntilIdle(); + // The connection error handle should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger the error handler. + binding.ResumeIncomingMethodCallProcessing(); + loop().RunUntilIdle(); + EXPECT_TRUE(called); +} + +// StrongBindingTest ----------------------------------------------------------- + +using StrongBindingTest = BindingTestBase; + +// Tests that destroying a mojo::StrongBinding closes the bound message pipe +// handle but does *not* destroy the implementation object. +TEST_F(StrongBindingTest, DestroyClosesMessagePipe) { + bool encountered_error = false; + bool was_deleted = false; + ServiceImpl impl(&was_deleted); + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ptr.set_connection_error_handler( + [&encountered_error]() { encountered_error = true; }); + bool called = false; + auto called_cb = [&called](int32_t result) { called = true; }; + { + StrongBinding<sample::Service> binding(&impl, request.Pass()); + ptr->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + called_cb); + loop().RunUntilIdle(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + } + // Now that the StrongBinding is out of scope we should detect an error on the + // other end of the pipe. + loop().RunUntilIdle(); + EXPECT_TRUE(encountered_error); + // But destroying the StrongBinding doesn't destroy the object. + ASSERT_FALSE(was_deleted); +} + +class ServiceImplWithStrongBinding : public ServiceImpl { + public: + ServiceImplWithStrongBinding(bool* was_deleted, + InterfaceRequest<sample::Service> request) + : ServiceImpl(was_deleted), binding_(this, request.Pass()) {} + + StrongBinding<sample::Service>& binding() { return binding_; } + + private: + StrongBinding<sample::Service> binding_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceImplWithStrongBinding); +}; + +// Tests the typical case, where the implementation object owns the +// StrongBinding (and should be destroyed on connection error). +TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) { + sample::ServicePtr ptr; + bool was_deleted = false; + // Will delete itself. + new ServiceImplWithBinding(&was_deleted, GetProxy(&ptr)); + + loop().RunUntilIdle(); + EXPECT_FALSE(was_deleted); + + ptr.reset(); + EXPECT_FALSE(was_deleted); + loop().RunUntilIdle(); + EXPECT_TRUE(was_deleted); +} + +// Tests that even when the implementation object owns the StrongBinding, that +// the implementation can still be deleted (which should result in the message +// pipe being closed). Also checks that the connection error handler doesn't get +// called. +TEST_F(StrongBindingTest, ExplicitDeleteImpl) { + bool ptr_error_handler_called = false; + sample::ServicePtr ptr; + auto request = GetProxy(&ptr); + ptr.set_connection_error_handler( + [&ptr_error_handler_called]() { ptr_error_handler_called = true; }); + bool was_deleted = false; + ServiceImplWithStrongBinding* impl = + new ServiceImplWithStrongBinding(&was_deleted, request.Pass()); + bool binding_error_handler_called = false; + impl->binding().set_connection_error_handler( + [&binding_error_handler_called]() { + binding_error_handler_called = true; + }); + + loop().RunUntilIdle(); + EXPECT_FALSE(ptr_error_handler_called); + EXPECT_FALSE(was_deleted); + + delete impl; + EXPECT_FALSE(ptr_error_handler_called); + EXPECT_TRUE(was_deleted); + was_deleted = false; // It shouldn't be double-deleted! + loop().RunUntilIdle(); + EXPECT_TRUE(ptr_error_handler_called); + EXPECT_FALSE(was_deleted); + + EXPECT_FALSE(binding_error_handler_called); +} + +} // namespace +} // mojo diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc new file mode 100644 index 0000000..7440acc --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc @@ -0,0 +1,127 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const double kMojoTicksPerSecond = 1000000.0; + +double MojoTicksToSeconds(MojoTimeTicks ticks) { + return ticks / kMojoTicksPerSecond; +} + +class PingServiceImpl : public test::PingService { + public: + explicit PingServiceImpl() {} + ~PingServiceImpl() override {} + + // |PingService| methods: + void Ping(const Callback<void()>& callback) override; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); +}; + +void PingServiceImpl::Ping(const Callback<void()>& callback) { + callback.Run(); +} + +class PingPongTest { + public: + explicit PingPongTest(test::PingServicePtr service); + + void Run(unsigned int iterations); + + private: + void OnPingDone(); + + test::PingServicePtr service_; + unsigned int iterations_to_run_; + unsigned int current_iterations_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(PingPongTest); +}; + +PingPongTest::PingPongTest(test::PingServicePtr service) + : service_(service.Pass()) { +} + +void PingPongTest::Run(unsigned int iterations) { + iterations_to_run_ = iterations; + current_iterations_ = 0; + + service_->Ping([this]() { OnPingDone(); }); + RunLoop::current()->Run(); +} + +void PingPongTest::OnPingDone() { + current_iterations_++; + if (current_iterations_ >= iterations_to_run_) { + RunLoop::current()->Quit(); + return; + } + + service_->Ping([this]() { OnPingDone(); }); +} + +struct BoundPingService { + BoundPingService() : binding(&impl) { + binding.Bind(GetProxy(&service)); + } + + PingServiceImpl impl; + test::PingServicePtr service; + Binding<test::PingService> binding; +}; + +class MojoBindingsPerftest : public testing::Test { + protected: + Environment env_; + RunLoop run_loop_; +}; + +TEST_F(MojoBindingsPerftest, InProcessPingPong) { + test::PingServicePtr service; + PingServiceImpl impl; + Binding<test::PingService> binding(&impl, GetProxy(&service)); + PingPongTest test(service.Pass()); + + { + const unsigned int kIterations = 100000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "0_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + } + + { + const size_t kNumInactiveServices = 1000; + BoundPingService* inactive_services = + new BoundPingService[kNumInactiveServices]; + + const unsigned int kIterations = 10000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "1000_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + + delete[] inactive_services; + } +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc b/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc new file mode 100644 index 0000000..c1ec7b3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc @@ -0,0 +1,209 @@ +// Copyright 2014 The Chromium 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 <limits> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +const void* ToPtr(uintptr_t ptr) { + return reinterpret_cast<const void*>(ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(BoundsCheckerTest, ConstructorRangeOverflow) { + { + // Test memory range overflow. + internal::BoundsChecker checker( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 5000, 0); + + EXPECT_FALSE(checker.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1)); + EXPECT_FALSE(checker.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1)); + } + + if (sizeof(size_t) > sizeof(uint32_t)) { + // Test handle index range overflow. + size_t num_handles = + static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5; + internal::BoundsChecker checker(ToPtr(0), 0, num_handles); + + EXPECT_FALSE(checker.ClaimHandle(Handle(0))); + EXPECT_FALSE( + checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1))); + + EXPECT_TRUE( + checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue))); + } +} +#endif + +TEST(BoundsCheckerTest, IsValidRange) { + { + internal::BoundsChecker checker(ToPtr(1234), 100, 0); + + // Basics. + EXPECT_FALSE(checker.IsValidRange(ToPtr(100), 5)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1230), 50)); + EXPECT_TRUE(checker.IsValidRange(ToPtr(1234), 5)); + EXPECT_TRUE(checker.IsValidRange(ToPtr(1240), 50)); + EXPECT_TRUE(checker.IsValidRange(ToPtr(1234), 100)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 101)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1240), 100)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1333), 5)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(2234), 5)); + + // ClaimMemory() updates the valid range. + EXPECT_TRUE(checker.ClaimMemory(ToPtr(1254), 10)); + + EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1254), 10)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1263), 1)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1263), 10)); + EXPECT_TRUE(checker.IsValidRange(ToPtr(1264), 10)); + EXPECT_TRUE(checker.IsValidRange(ToPtr(1264), 70)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1264), 71)); + } + + { + internal::BoundsChecker checker(ToPtr(1234), 100, 0); + // Should return false for empty ranges. + EXPECT_FALSE(checker.IsValidRange(ToPtr(0), 0)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1200), 0)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 0)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1240), 0)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(2234), 0)); + } + + { + // The valid memory range is empty. + internal::BoundsChecker checker(ToPtr(1234), 0, 0); + + EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 0)); + } + + { + internal::BoundsChecker checker( + ToPtr(std::numeric_limits<uintptr_t>::max() - 2000), 1000, 0); + + // Test overflow. + EXPECT_FALSE(checker.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 4000)); + EXPECT_FALSE(checker.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), + std::numeric_limits<uint32_t>::max())); + + // This should be fine. + EXPECT_TRUE(checker.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 200)); + } +} + +TEST(BoundsCheckerTest, ClaimHandle) { + { + internal::BoundsChecker checker(ToPtr(0), 0, 10); + + // Basics. + EXPECT_TRUE(checker.ClaimHandle(Handle(0))); + EXPECT_FALSE(checker.ClaimHandle(Handle(0))); + + EXPECT_TRUE(checker.ClaimHandle(Handle(9))); + EXPECT_FALSE(checker.ClaimHandle(Handle(10))); + + // Should fail because it is smaller than the max index that has been + // claimed. + EXPECT_FALSE(checker.ClaimHandle(Handle(8))); + + // Should return true for invalid handle. + EXPECT_TRUE( + checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue))); + EXPECT_TRUE( + checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue))); + } + + { + // No handle to claim. + internal::BoundsChecker checker(ToPtr(0), 0, 0); + + EXPECT_FALSE(checker.ClaimHandle(Handle(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE( + checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue))); + } + + { + // Test the case that |num_handles| is the same value as + // |internal::kEncodedInvalidHandleValue|. + EXPECT_EQ(internal::kEncodedInvalidHandleValue, + std::numeric_limits<uint32_t>::max()); + internal::BoundsChecker checker( + ToPtr(0), 0, std::numeric_limits<uint32_t>::max()); + + EXPECT_TRUE( + checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE( + checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE(checker.ClaimHandle(Handle(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE( + checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue))); + } +} + +TEST(BoundsCheckerTest, ClaimMemory) { + { + internal::BoundsChecker checker(ToPtr(1000), 2000, 0); + + // Basics. + EXPECT_FALSE(checker.ClaimMemory(ToPtr(500), 100)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(800), 300)); + EXPECT_TRUE(checker.ClaimMemory(ToPtr(1000), 100)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(1099), 100)); + EXPECT_TRUE(checker.ClaimMemory(ToPtr(1100), 200)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(2000), 1001)); + EXPECT_TRUE(checker.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(1400), 100)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(3000), 1)); + EXPECT_TRUE(checker.ClaimMemory(ToPtr(2500), 500)); + } + + { + // No memory to claim. + internal::BoundsChecker checker(ToPtr(10000), 0, 0); + + EXPECT_FALSE(checker.ClaimMemory(ToPtr(10000), 1)); + EXPECT_FALSE(checker.ClaimMemory(ToPtr(10000), 0)); + } + + { + internal::BoundsChecker checker( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1000), 500, 0); + + // Test overflow. + EXPECT_FALSE(checker.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 4000)); + EXPECT_FALSE( + checker.ClaimMemory(ToPtr(std::numeric_limits<uintptr_t>::max() - 750), + std::numeric_limits<uint32_t>::max())); + + // This should be fine. + EXPECT_TRUE(checker.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 200)); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc new file mode 100644 index 0000000..317a2a5 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/buffer_unittest.cc @@ -0,0 +1,91 @@ +// Copyright 2014 The Chromium 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 <limits> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +bool IsZero(void* p_buf, size_t size) { + char* buf = reinterpret_cast<char*>(p_buf); + for (size_t i = 0; i < size; ++i) { + if (buf[i] != 0) + return false; + } + return true; +} + +// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries. +TEST(FixedBufferTest, Alignment) { + internal::FixedBufferForTesting buf(internal::Align(10) * 2); + ASSERT_EQ(buf.size(), 16u * 2); + + void* a = buf.Allocate(10); + ASSERT_TRUE(a); + EXPECT_TRUE(IsZero(a, 10)); + EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(a) % 8); + + void* b = buf.Allocate(10); + ASSERT_TRUE(b); + EXPECT_TRUE(IsZero(b, 10)); + EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(b) % 8); + + // Any more allocations would result in an assert, but we can't test that. +} + +// Tests that FixedBufferForTesting::Leak passes ownership to the caller. +TEST(FixedBufferTest, Leak) { + void* ptr = nullptr; + void* buf_ptr = nullptr; + { + internal::FixedBufferForTesting buf(8); + ASSERT_EQ(8u, buf.size()); + + ptr = buf.Allocate(8); + ASSERT_TRUE(ptr); + buf_ptr = buf.Leak(); + + // The buffer should point to the first element allocated. + // TODO(mpcomplete): Is this a reasonable expectation? + EXPECT_EQ(ptr, buf_ptr); + + // The FixedBufferForTesting should be empty now. + EXPECT_EQ(0u, buf.size()); + EXPECT_FALSE(buf.Leak()); + } + + // Since we called Leak, ptr is still writable after FixedBufferForTesting + // went out of scope. + memset(ptr, 1, 8); + free(buf_ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(FixedBufferTest, TooBig) { + internal::FixedBufferForTesting buf(24); + + // A little bit too large. + EXPECT_EQ(reinterpret_cast<void*>(0), buf.Allocate(32)); + + // Move the cursor forward. + EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16)); + + // A lot too large. + EXPECT_EQ(reinterpret_cast<void*>(0), + buf.Allocate(std::numeric_limits<size_t>::max() - 1024u)); + + // A lot too large, leading to possible integer overflow. + EXPECT_EQ(reinterpret_cast<void*>(0), + buf.Allocate(std::numeric_limits<size_t>::max() - 8u)); +} +#endif + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/callback_unittest.cc b/mojo/public/cpp/bindings/tests/callback_unittest.cc new file mode 100644 index 0000000..158b21e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/callback_unittest.cc @@ -0,0 +1,196 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/map.h" +#include "mojo/public/cpp/bindings/string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +struct RunnableNoArgs { + explicit RunnableNoArgs(int* calls) : calls(calls) {} + + void Run() const { (*calls)++; } + int* calls; +}; + +struct RunnableOneArg { + explicit RunnableOneArg(int* calls) : calls(calls) {} + + void Run(int increment) const { (*calls) += increment; } + + int* calls; +}; + +struct RunnableStringArgByConstRef { + explicit RunnableStringArgByConstRef(int* calls) : calls(calls) {} + + void Run(const String& s) const { (*calls)++; } + + int* calls; +}; + +using ExampleMoveOnlyType = Map<int, int>; + +struct RunnableMoveOnlyParam { + explicit RunnableMoveOnlyParam(int* calls) : calls(calls) {} + + void Run(ExampleMoveOnlyType m) const { (*calls)++; } + + int* calls; +}; + +int* g_calls = nullptr; + +void FunctionNoArgs() { + (*g_calls)++; +} + +void FunctionOneArg(int increment) { + (*g_calls) += increment; +} + +void FunctionStringArgByConstRef(const String& s) { + (*g_calls)++; +} + +void FunctionMoveOnlyType(ExampleMoveOnlyType m) { + (*g_calls)++; +} + +static_assert(!internal::HasCompatibleCallOperator<RunnableNoArgs>::value, + "HasCompatibleCallOperator<Runnable>"); +static_assert(!internal::HasCompatibleCallOperator<RunnableOneArg, int>::value, + "HasCompatibleCallOperator<RunnableOneArg, int>"); +static_assert(!internal::HasCompatibleCallOperator<RunnableStringArgByConstRef, + String>::value, + "HasCompatibleCallOperator<RunnableStringArgByConstRef, String>"); +static_assert(!internal::HasCompatibleCallOperator<RunnableMoveOnlyParam, + ExampleMoveOnlyType>::value, + "HasCompatibleCallOperator<RunnableMoveOnlyParam, String>"); + +auto lambda_one = []() {}; +static_assert(internal::HasCompatibleCallOperator<decltype(lambda_one)>::value, + "HasCompatibleCallOperator<lambda []() {}>"); + +auto lambda_two = [](int x) {}; +static_assert( + internal::HasCompatibleCallOperator<decltype(lambda_two), int>::value, + "HasCompatibleCallOperator<lambda [](int x) {}, int>"); + +auto lambda_three = [](const String& s) {}; +static_assert( + internal::HasCompatibleCallOperator<decltype(lambda_three), String>::value, + "HasCompatibleCallOperator<lambda [](const String& s) {}, String>"); + +auto lambda_four = [](ExampleMoveOnlyType m) {}; +static_assert(internal::HasCompatibleCallOperator<decltype(lambda_four), + ExampleMoveOnlyType>::value, + "HasCompatibleCallOperator<lambda [](ExampleMoveOnlyType) {}, " + "ExampleMoveOnlyType>"); + +// Tests constructing and invoking a mojo::Callback from objects with a +// compatible Run() method (called 'runnables'), from lambdas, and from function +// pointers. +TEST(Callback, Create) { + int calls = 0; + + RunnableNoArgs f(&calls); + // Construct from a runnable object. + mojo::Callback<void()> cb = f; + cb.Run(); + EXPECT_EQ(1, calls); + + // Construct from a parameterless lambda that captures one variable. + cb = [&calls]() { calls++; }; + cb.Run(); + EXPECT_EQ(2, calls); + + // Construct from a runnable object with one primitive parameter. + mojo::Callback<void(int)> cb_with_param = RunnableOneArg(&calls); + cb_with_param.Run(1); + EXPECT_EQ(3, calls); + + // Construct from a lambda that takes one parameter and captures one variable. + cb_with_param = [&calls](int increment) { calls += increment; }; + cb_with_param.Run(1); + EXPECT_EQ(4, calls); + + // Construct from a runnable object with one string parameter. + mojo::Callback<void(String)> cb_with_string_param = + RunnableStringArgByConstRef(&calls); + cb_with_string_param.Run(String("hello world")); + EXPECT_EQ(5, calls); + + // Construct from a lambda that takes one string parameter. + cb_with_string_param = [&calls](const String& s) { calls++; }; + cb_with_string_param.Run(String("world")); + EXPECT_EQ(6, calls); + + ExampleMoveOnlyType m; + mojo::Callback<void(ExampleMoveOnlyType)> cb_with_move_only_param = + RunnableMoveOnlyParam(&calls); + cb_with_move_only_param.Run(m.Clone()); + EXPECT_EQ(7, calls); + + cb_with_move_only_param = [&calls](ExampleMoveOnlyType m) { calls++; }; + cb_with_move_only_param.Run(m.Clone()); + EXPECT_EQ(8, calls); + + // Construct from a function pointer. + g_calls = &calls; + + cb = &FunctionNoArgs; + cb.Run(); + EXPECT_EQ(9, calls); + + cb_with_param = &FunctionOneArg; + cb_with_param.Run(1); + EXPECT_EQ(10, calls); + + cb_with_string_param = &FunctionStringArgByConstRef; + cb_with_string_param.Run(String("hello")); + EXPECT_EQ(11, calls); + + cb_with_move_only_param = &FunctionMoveOnlyType; + cb_with_move_only_param.Run(m.Clone()); + EXPECT_EQ(12, calls); + + g_calls = nullptr; +} + +bool g_overloaded_function_with_int_param_called = false; + +void OverloadedFunction(int param) { + g_overloaded_function_with_int_param_called = true; +} + +bool g_overloaded_function_with_double_param_called = false; + +void OverloadedFunction(double param) { + g_overloaded_function_with_double_param_called = true; +} + +// Tests constructing and invoking a mojo::Callback from pointers to overloaded +// functions. +TEST(Callback, CreateFromOverloadedFunctionPtr) { + g_overloaded_function_with_int_param_called = false; + mojo::Callback<void(int)> cb_with_int_param = &OverloadedFunction; + cb_with_int_param.Run(123); + EXPECT_TRUE(g_overloaded_function_with_int_param_called); + g_overloaded_function_with_int_param_called = false; + + g_overloaded_function_with_double_param_called = false; + mojo::Callback<void(double)> cb_with_double_param = &OverloadedFunction; + cb_with_double_param.Run(123); + EXPECT_TRUE(g_overloaded_function_with_double_param_called); + g_overloaded_function_with_double_param_called = false; +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc new file mode 100644 index 0000000..febc72e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc @@ -0,0 +1,443 @@ +// Copyright 2013 The Chromium 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 <stdlib.h> +#include <string.h> + +#include "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/lib/connector.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "mojo/public/cpp/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class MessageAccumulator : public MessageReceiver { + public: + MessageAccumulator() {} + + bool Accept(Message* message) override { + queue_.Push(message); + return true; + } + + bool IsEmpty() const { return queue_.IsEmpty(); } + + void Pop(Message* message) { queue_.Pop(message); } + + private: + MessageQueue queue_; +}; + +class ConnectorDeletingMessageAccumulator : public MessageAccumulator { + public: + ConnectorDeletingMessageAccumulator(internal::Connector** connector) + : connector_(connector) {} + + bool Accept(Message* message) override { + delete *connector_; + *connector_ = nullptr; + return MessageAccumulator::Accept(message); + } + + private: + internal::Connector** connector_; +}; + +class ReentrantMessageAccumulator : public MessageAccumulator { + public: + ReentrantMessageAccumulator(internal::Connector* connector) + : connector_(connector), number_of_calls_(0) {} + + bool Accept(Message* message) override { + if (!MessageAccumulator::Accept(message)) + return false; + number_of_calls_++; + if (number_of_calls_ == 1) { + return connector_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + } + return true; + } + + int number_of_calls() { return number_of_calls_; } + + private: + internal::Connector* connector_; + int number_of_calls_; +}; + +class ConnectorTest : public testing::Test { + public: + ConnectorTest() : loop_(common::MessagePumpMojo::Create()) {} + + void SetUp() override { + CreateMessagePipe(nullptr, &handle0_, &handle1_); + } + + void TearDown() override {} + + void AllocMessage(const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(1, payload_size); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + + builder.message()->MoveTo(message); + } + + void PumpMessages() { loop_.RunUntilIdle(); } + + protected: + ScopedMessagePipeHandle handle0_; + ScopedMessagePipeHandle handle1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(ConnectorTest, Basic) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + PumpMessages(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_Synchronous) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + PumpMessages(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_TwoMessages) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + PumpMessages(); + + for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) { + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + } +} + +TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[0]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + + ASSERT_TRUE(accumulator.IsEmpty()); +} + +TEST_F(ConnectorTest, WriteToClosedPipe) { + internal::Connector connector0(handle0_.Pass()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + // Close the other end of the pipe. + handle1_.reset(); + + // Not observed yet because we haven't spun the message loop yet. + EXPECT_FALSE(connector0.encountered_error()); + + // Write failures are not reported. + bool ok = connector0.Accept(&message); + EXPECT_TRUE(ok); + + // Still not observed. + EXPECT_FALSE(connector0.encountered_error()); + + // Spin the message loop, and then we should start observing the closed pipe. + PumpMessages(); + + EXPECT_TRUE(connector0.encountered_error()); +} + +TEST_F(ConnectorTest, MessageWithHandles) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char kText[] = "hello world"; + + Message message1; + AllocMessage(kText, &message1); + + MessagePipe pipe; + message1.mutable_handles()->push_back(pipe.handle0.release()); + + connector0.Accept(&message1); + + // The message should have been transferred, releasing the handles. + EXPECT_TRUE(message1.handles()->empty()); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + PumpMessages(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + ASSERT_EQ(1U, message_received.handles()->size()); + + // Now send a message to the transferred handle and confirm it's sent through + // to the orginal pipe. + // TODO(vtl): Do we need a better way of "downcasting" the handle types? + ScopedMessagePipeHandle smph; + smph.reset(MessagePipeHandle(message_received.handles()->front().value())); + message_received.mutable_handles()->front() = Handle(); + // |smph| now owns this handle. + + internal::Connector connector_received(smph.Pass()); + internal::Connector connector_original(pipe.handle1.Pass()); + + Message message2; + AllocMessage(kText, &message2); + + connector_received.Accept(&message2); + connector_original.set_incoming_receiver(&accumulator); + PumpMessages(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithError) { + internal::Connector connector0(handle0_.Pass()); + // Close the other end of the pipe. + handle1_.reset(); + ASSERT_FALSE(connector0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE)); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector* connector1 = new internal::Connector(handle1_.Pass()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + ConnectorDeletingMessageAccumulator accumulator(&connector1); + connector1->set_incoming_receiver(&accumulator); + + connector1->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(connector1); + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) { + internal::Connector connector0(handle0_.Pass()); + internal::Connector connector1(handle1_.Pass()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + ReentrantMessageAccumulator accumulator(&connector1); + connector1.set_incoming_receiver(&accumulator); + + PumpMessages(); + + for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) { + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + } + + ASSERT_EQ(2, accumulator.number_of_calls()); +} + +TEST_F(ConnectorTest, RaiseError) { + internal::Connector connector0(handle0_.Pass()); + bool error_handler_called0 = false; + connector0.set_connection_error_handler( + [&error_handler_called0]() { error_handler_called0 = true; }); + + internal::Connector connector1(handle1_.Pass()); + bool error_handler_called1 = false; + connector1.set_connection_error_handler( + [&error_handler_called1]() { error_handler_called1 = true; }); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + connector0.RaiseError(); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + PumpMessages(); + + // Messages sent prior to RaiseError() still arrive at the other end. + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + + PumpMessages(); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(connector0.encountered_error()); + EXPECT_TRUE(connector1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(connector0.is_valid()); + EXPECT_TRUE(connector1.is_valid()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/constant_unittest.cc b/mojo/public/cpp/bindings/tests/constant_unittest.cc new file mode 100644 index 0000000..f6394f3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/constant_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium 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 "mojo/public/interfaces/bindings/tests/test_constants.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(ConstantTest, GlobalConstants) { + // Compile-time constants. + static_assert(kBoolValue == true, ""); + static_assert(kInt8Value == -2, ""); + static_assert(kUint8Value == 128U, ""); + static_assert(kInt16Value == -233, ""); + static_assert(kUint16Value == 44204U, ""); + static_assert(kInt32Value == -44204, ""); + static_assert(kUint32Value == 4294967295U, ""); + static_assert(kInt64Value == -9223372036854775807, ""); + static_assert(kUint64Value == 9999999999999999999ULL, ""); + + EXPECT_DOUBLE_EQ(kDoubleValue, 3.14159); + EXPECT_FLOAT_EQ(kFloatValue, 2.71828f); +} + +TEST(ConstantTest, StructConstants) { + // Compile-time constants. + static_assert(StructWithConstants::kInt8Value == 5U, ""); + + EXPECT_FLOAT_EQ(StructWithConstants::kFloatValue, 765.432f); +} + +TEST(ConstantTest, InterfaceConstants) { + // Compile-time constants. + static_assert(InterfaceWithConstants::kUint32Value == 20100722, ""); + + EXPECT_DOUBLE_EQ(InterfaceWithConstants::kDoubleValue, 12.34567); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.cc b/mojo/public/cpp/bindings/tests/container_test_util.cc new file mode 100644 index 0000000..e8377c4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.cc @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/tests/container_test_util.h" + +namespace mojo { + +size_t CopyableType::num_instances_ = 0; +size_t MoveOnlyType::num_instances_ = 0; + +CopyableType::CopyableType() : copied_(false), ptr_(this) { + num_instances_++; +} + +CopyableType::CopyableType(const CopyableType& other) + : copied_(true), ptr_(other.ptr()) { + num_instances_++; +} + +CopyableType& CopyableType::operator=(const CopyableType& other) { + copied_ = true; + ptr_ = other.ptr(); + return *this; +} + +CopyableType::~CopyableType() { + num_instances_--; +} + +MoveOnlyType::MoveOnlyType() : moved_(false), ptr_(this) { + num_instances_++; +} + +MoveOnlyType::MoveOnlyType(MoveOnlyType&& other) + : moved_(true), ptr_(other.ptr()) { + num_instances_++; +} + +MoveOnlyType& MoveOnlyType::operator=(MoveOnlyType&& other) { + moved_ = true; + ptr_ = other.ptr(); + return *this; +} + +MoveOnlyType::~MoveOnlyType() { + num_instances_--; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.h b/mojo/public/cpp/bindings/tests/container_test_util.h new file mode 100644 index 0000000..1e29d22 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +class CopyableType { + public: + CopyableType(); + CopyableType(const CopyableType& other); + CopyableType& operator=(const CopyableType& other); + ~CopyableType(); + + bool copied() const { return copied_; } + static size_t num_instances() { return num_instances_; } + CopyableType* ptr() const { return ptr_; } + void ResetCopied() { copied_ = false; } + + private: + bool copied_; + static size_t num_instances_; + CopyableType* ptr_; +}; + +class MoveOnlyType { + MOJO_MOVE_ONLY_TYPE(MoveOnlyType) + public: + typedef MoveOnlyType Data_; + MoveOnlyType(); + MoveOnlyType(MoveOnlyType&& other); + MoveOnlyType& operator=(MoveOnlyType&& other); + ~MoveOnlyType(); + + bool moved() const { return moved_; } + static size_t num_instances() { return num_instances_; } + MoveOnlyType* ptr() const { return ptr_; } + void ResetMoved() { moved_ = false; } + + private: + bool moved_; + static size_t num_instances_; + MoveOnlyType* ptr_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/equals_unittest.cc b/mojo/public/cpp/bindings/tests/equals_unittest.cc new file mode 100644 index 0000000..5306f1f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/equals_unittest.cc @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/lib/value_traits.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +namespace { + +RectPtr CreateRect() { + RectPtr r = Rect::New(); + r->x = 1; + r->y = 2; + r->width = 3; + r->height = 4; + return r.Pass(); +} + +using EqualsTest = testing::Test; + +} // namespace + +TEST_F(EqualsTest, NullStruct) { + RectPtr r1; + RectPtr r2; + EXPECT_TRUE(r1.Equals(r2)); + EXPECT_TRUE(r2.Equals(r1)); + + r1 = CreateRect(); + EXPECT_FALSE(r1.Equals(r2)); + EXPECT_FALSE(r2.Equals(r1)); +} + +TEST_F(EqualsTest, Struct) { + RectPtr r1(CreateRect()); + RectPtr r2(r1.Clone()); + EXPECT_TRUE(r1.Equals(r2)); + r2->y = 1; + EXPECT_FALSE(r1.Equals(r2)); + r2.reset(); + EXPECT_FALSE(r1.Equals(r2)); +} + +TEST_F(EqualsTest, StructNested) { + RectPairPtr p1(RectPair::New()); + p1->first = CreateRect(); + p1->second = CreateRect(); + RectPairPtr p2(p1.Clone()); + EXPECT_TRUE(p1.Equals(p2)); + p2->second->width = 0; + EXPECT_FALSE(p1.Equals(p2)); + p2->second.reset(); + EXPECT_FALSE(p1.Equals(p2)); +} + +TEST_F(EqualsTest, Array) { + NamedRegionPtr n1(NamedRegion::New()); + n1->name = "n1"; + n1->rects.push_back(CreateRect()); + NamedRegionPtr n2(n1.Clone()); + EXPECT_TRUE(n1.Equals(n2)); + + n2->rects.reset(); + EXPECT_FALSE(n1.Equals(n2)); + n2->rects.resize(0); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects.push_back(CreateRect()); + n2->rects.push_back(CreateRect()); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects.resize(1); + n2->rects[0]->width = 0; + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects[0] = CreateRect(); + EXPECT_TRUE(n1.Equals(n2)); +} + +TEST_F(EqualsTest, Map) { + auto n1(NamedRegion::New()); + n1->name = "foo"; + n1->rects.push_back(CreateRect()); + + Map<std::string, NamedRegionPtr> m1; + m1.insert("foo", n1.Pass()); + + decltype(m1) m2; + EXPECT_FALSE(m1.Equals(m2)); + + m2.insert("bar", m1.at("foo").Clone()); + EXPECT_FALSE(m1.Equals(m2)); + + m2 = m1.Clone(); + m2.at("foo")->name = "monkey"; + EXPECT_FALSE(m1.Equals(m2)); + + m2 = m1.Clone(); + m2.at("foo")->rects.push_back(Rect::New()); + EXPECT_FALSE(m1.Equals(m2)); + + m2.at("foo")->rects.resize(1); + m2.at("foo")->rects[0]->width = 1; + EXPECT_FALSE(m1.Equals(m2)); + + m2 = m1.Clone(); + EXPECT_TRUE(m1.Equals(m2)); +} + +TEST_F(EqualsTest, InterfacePtr) { + using InterfaceValueTraits = mojo::internal::ValueTraits<SomeInterfacePtr>; + + SomeInterfacePtr inf1; + SomeInterfacePtr inf2; + + EXPECT_TRUE(InterfaceValueTraits::Equals(inf1, inf1)); + EXPECT_TRUE(InterfaceValueTraits::Equals(inf1, inf2)); + + auto inf1_request = GetProxy(&inf1); + MOJO_ALLOW_UNUSED_LOCAL(inf1_request); + + EXPECT_TRUE(InterfaceValueTraits::Equals(inf1, inf1)); + EXPECT_FALSE(InterfaceValueTraits::Equals(inf1, inf2)); + + auto inf2_request = GetProxy(&inf2); + MOJO_ALLOW_UNUSED_LOCAL(inf2_request); + + EXPECT_FALSE(InterfaceValueTraits::Equals(inf1, inf2)); +} + +TEST_F(EqualsTest, InterfaceRequest) { + using RequestValueTraits = + mojo::internal::ValueTraits<InterfaceRequest<SomeInterface>>; + + InterfaceRequest<SomeInterface> req1; + InterfaceRequest<SomeInterface> req2; + + EXPECT_TRUE(RequestValueTraits::Equals(req1, req1)); + EXPECT_TRUE(RequestValueTraits::Equals(req1, req2)); + + SomeInterfacePtr inf1; + req1 = GetProxy(&inf1); + + EXPECT_TRUE(RequestValueTraits::Equals(req1, req1)); + EXPECT_FALSE(RequestValueTraits::Equals(req1, req2)); + + SomeInterfacePtr inf2; + req2 = GetProxy(&inf2); + + EXPECT_FALSE(RequestValueTraits::Equals(req1, req2)); +} + +} // test +} // mojo diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc new file mode 100644 index 0000000..8487d0a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc @@ -0,0 +1,353 @@ +// Copyright 2013 The Chromium 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 "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +const char kText1[] = "hello"; +const char kText2[] = "world"; + +class StringRecorder { + public: + explicit StringRecorder(std::string* buf) : buf_(buf) {} + void Run(const String& a) const { *buf_ = a.To<std::string>(); } + + private: + std::string* buf_; +}; + +class ImportedInterfaceImpl : public imported::ImportedInterface { + public: + explicit ImportedInterfaceImpl( + InterfaceRequest<imported::ImportedInterface> request) + : binding_(this, request.Pass()) {} + + void DoSomething() override { do_something_count_++; } + + static int do_something_count() { return do_something_count_; } + + private: + static int do_something_count_; + Binding<ImportedInterface> binding_; +}; +int ImportedInterfaceImpl::do_something_count_ = 0; + +class SampleNamedObjectImpl : public sample::NamedObject { + public: + explicit SampleNamedObjectImpl(InterfaceRequest<sample::NamedObject> request) + : binding_(this, request.Pass()) {} + void SetName(const mojo::String& name) override { name_ = name; } + + void GetName(const mojo::Callback<void(mojo::String)>& callback) override { + callback.Run(name_); + } + + private: + std::string name_; + StrongBinding<sample::NamedObject> binding_; +}; + +class SampleFactoryImpl : public sample::Factory { + public: + explicit SampleFactoryImpl(InterfaceRequest<sample::Factory> request) + : binding_(this, request.Pass()) {} + + void DoStuff(sample::RequestPtr request, + ScopedMessagePipeHandle pipe, + const DoStuffCallback& callback) override { + std::string text1; + if (pipe.is_valid()) + EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1)); + + std::string text2; + if (request->pipe.is_valid()) { + EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2)); + + // Ensure that simply accessing request->pipe does not close it. + EXPECT_TRUE(request->pipe.is_valid()); + } + + ScopedMessagePipeHandle pipe0; + if (!text2.empty()) { + CreateMessagePipe(nullptr, &pipe0, &pipe1_); + EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2)); + } + + sample::ResponsePtr response(sample::Response::New()); + response->x = 2; + response->pipe = pipe0.Pass(); + callback.Run(response.Pass(), text1); + + if (request->obj) + request->obj->DoSomething(); + } + + void DoStuff2(ScopedDataPipeConsumerHandle pipe, + const DoStuff2Callback& callback) override { + // Read the data from the pipe, writing the response (as a string) to + // DidStuff2(). + ASSERT_TRUE(pipe.is_valid()); + uint32_t data_size = 0; + ASSERT_EQ(MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY)); + ASSERT_NE(0, static_cast<int>(data_size)); + char data[64]; + ASSERT_LT(static_cast<int>(data_size), 64); + ASSERT_EQ( + MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), data, &data_size, MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + callback.Run(data); + } + + void CreateNamedObject( + InterfaceRequest<sample::NamedObject> object_request) override { + EXPECT_TRUE(object_request.is_pending()); + new SampleNamedObjectImpl(object_request.Pass()); + } + + // These aren't called or implemented, but exist here to test that the + // methods are generated with the correct argument types for imported + // interfaces. + void RequestImportedInterface( + InterfaceRequest<imported::ImportedInterface> imported, + const mojo::Callback<void(InterfaceRequest<imported::ImportedInterface>)>& + callback) override {} + void TakeImportedInterface( + imported::ImportedInterfacePtr imported, + const mojo::Callback<void(imported::ImportedInterfacePtr)>& callback) + override {} + + private: + ScopedMessagePipeHandle pipe1_; + Binding<sample::Factory> binding_; +}; + +class HandlePassingTest : public testing::Test { + public: + HandlePassingTest() : loop_(common::MessagePumpMojo::Create()) {} + + void TearDown() override { PumpMessages(); } + + void PumpMessages() { loop_.RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +struct DoStuffCallback { + DoStuffCallback(bool* got_response, std::string* got_text_reply) + : got_response(got_response), got_text_reply(got_text_reply) {} + + void Run(sample::ResponsePtr response, const String& text_reply) const { + *got_text_reply = text_reply; + + if (response->pipe.is_valid()) { + std::string text2; + EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2)); + + // Ensure that simply accessing response.pipe does not close it. + EXPECT_TRUE(response->pipe.is_valid()); + + EXPECT_EQ(std::string(kText2), text2); + + // Do some more tests of handle passing: + ScopedMessagePipeHandle p = response->pipe.Pass(); + EXPECT_TRUE(p.is_valid()); + EXPECT_FALSE(response->pipe.is_valid()); + } + + *got_response = true; + } + + bool* got_response; + std::string* got_text_reply; +}; + +TEST_F(HandlePassingTest, Basic) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(GetProxy(&factory)); + + MessagePipe pipe0; + EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1)); + + MessagePipe pipe1; + EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2)); + + imported::ImportedInterfacePtr imported; + ImportedInterfaceImpl imported_impl(GetProxy(&imported)); + + sample::RequestPtr request(sample::Request::New()); + request->x = 1; + request->pipe = pipe1.handle0.Pass(); + request->obj = imported.Pass(); + bool got_response = false; + std::string got_text_reply; + DoStuffCallback cb(&got_response, &got_text_reply); + factory->DoStuff(request.Pass(), pipe0.handle0.Pass(), cb); + + EXPECT_FALSE(*cb.got_response); + int count_before = ImportedInterfaceImpl::do_something_count(); + + PumpMessages(); + + EXPECT_TRUE(*cb.got_response); + EXPECT_EQ(kText1, *cb.got_text_reply); + EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before); +} + +TEST_F(HandlePassingTest, PassInvalid) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(GetProxy(&factory)); + + sample::RequestPtr request(sample::Request::New()); + request->x = 1; + bool got_response = false; + std::string got_text_reply; + DoStuffCallback cb(&got_response, &got_text_reply); + factory->DoStuff(request.Pass(), ScopedMessagePipeHandle().Pass(), cb); + + EXPECT_FALSE(*cb.got_response); + + PumpMessages(); + + EXPECT_TRUE(*cb.got_response); +} + +struct DoStuff2Callback { + DoStuff2Callback(bool* got_response, std::string* got_text_reply) + : got_response(got_response), got_text_reply(got_text_reply) {} + + void Run(const String& text_reply) const { + *got_response = true; + *got_text_reply = text_reply; + } + + bool* got_response; + std::string* got_text_reply; +}; + +// Verifies DataPipeConsumer can be passed and read from. +TEST_F(HandlePassingTest, DataPipe) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(GetProxy(&factory)); + + // Writes a string to a data pipe and passes the data pipe (consumer) to the + // factory. + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; + MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions), + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + 1, + 1024}; + ASSERT_EQ(MOJO_RESULT_OK, + CreateDataPipe(&options, &producer_handle, &consumer_handle)); + std::string expected_text_reply = "got it"; + // +1 for \0. + uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1); + ASSERT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer_handle.get(), + expected_text_reply.c_str(), + &data_size, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + + bool got_response = false; + std::string got_text_reply; + DoStuff2Callback cb(&got_response, &got_text_reply); + factory->DoStuff2(consumer_handle.Pass(), cb); + + EXPECT_FALSE(*cb.got_response); + + PumpMessages(); + + EXPECT_TRUE(*cb.got_response); + EXPECT_EQ(expected_text_reply, *cb.got_text_reply); +} + +TEST_F(HandlePassingTest, PipesAreClosed) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(GetProxy(&factory)); + + MessagePipe extra_pipe; + + MojoHandle handle0_value = extra_pipe.handle0.get().value(); + MojoHandle handle1_value = extra_pipe.handle1.get().value(); + + { + Array<ScopedMessagePipeHandle> pipes(2); + pipes[0] = extra_pipe.handle0.Pass(); + pipes[1] = extra_pipe.handle1.Pass(); + + sample::RequestPtr request(sample::Request::New()); + request->more_pipes = pipes.Pass(); + + factory->DoStuff(request.Pass(), ScopedMessagePipeHandle(), + sample::Factory::DoStuffCallback()); + } + + // We expect the pipes to have been closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value)); +} + +TEST_F(HandlePassingTest, IsHandle) { + // Validate that mojo::internal::IsHandle<> works as expected since this. + // template is key to ensuring that we don't leak handles. + EXPECT_TRUE(internal::IsHandle<Handle>::value); + EXPECT_TRUE(internal::IsHandle<MessagePipeHandle>::value); + EXPECT_TRUE(internal::IsHandle<DataPipeConsumerHandle>::value); + EXPECT_TRUE(internal::IsHandle<DataPipeProducerHandle>::value); + EXPECT_TRUE(internal::IsHandle<SharedBufferHandle>::value); + + // Basic sanity checks... + EXPECT_FALSE(internal::IsHandle<int>::value); + EXPECT_FALSE(internal::IsHandle<sample::FactoryPtr>::value); + EXPECT_FALSE(internal::IsHandle<String>::value); +} + +TEST_F(HandlePassingTest, CreateNamedObject) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(GetProxy(&factory)); + + sample::NamedObjectPtr object1; + EXPECT_FALSE(object1); + + InterfaceRequest<sample::NamedObject> object1_request = GetProxy(&object1); + EXPECT_TRUE(object1_request.is_pending()); + factory->CreateNamedObject(object1_request.Pass()); + EXPECT_FALSE(object1_request.is_pending()); // We've passed the request. + + ASSERT_TRUE(object1); + object1->SetName("object1"); + + sample::NamedObjectPtr object2; + factory->CreateNamedObject(GetProxy(&object2)); + object2->SetName("object2"); + + std::string name1; + object1->GetName(StringRecorder(&name1)); + + std::string name2; + object2->GetName(StringRecorder(&name2)); + + PumpMessages(); // Yield for results. + + EXPECT_EQ(std::string("object1"), name1); + EXPECT_EQ(std::string("object2"), name2); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc new file mode 100644 index 0000000..508787a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc @@ -0,0 +1,647 @@ +// Copyright 2013 The Chromium 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 "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/scoping.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename Method, typename Class> +class RunnableImpl { + public: + RunnableImpl(Method method, Class instance) + : method_(method), instance_(instance) {} + template <typename... Args> + void Run(Args... args) const { + (instance_->*method_)(args...); + } + + private: + Method method_; + Class instance_; +}; + +template <typename Method, typename Class> +RunnableImpl<Method, Class> MakeRunnable(Method method, Class object) { + return RunnableImpl<Method, Class>(method, object); +} + +typedef mojo::Callback<void(double)> CalcCallback; + +class MathCalculatorImpl : public math::Calculator { + public: + explicit MathCalculatorImpl(InterfaceRequest<math::Calculator> request) + : total_(0.0), binding_(this, request.Pass()) {} + ~MathCalculatorImpl() override {} + + void CloseMessagePipe() { binding_.Close(); } + + void WaitForIncomingMethodCall() { binding_.WaitForIncomingMethodCall(); } + + void Clear(const CalcCallback& callback) override { + total_ = 0.0; + callback.Run(total_); + } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_; + Binding<math::Calculator> binding_; +}; + +class MathCalculatorUI { + public: + explicit MathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(calculator.Pass()), + output_(0.0), + callback_(MakeRunnable(&MathCalculatorUI::Output, this)) {} + + bool WaitForIncomingResponse() { + return calculator_.WaitForIncomingResponse(); + } + + bool encountered_error() const { return calculator_.encountered_error(); } + + void Add(double value) { calculator_->Add(value, callback_); } + + void Subtract(double value) { calculator_->Add(-value, callback_); } + + void Multiply(double value) { calculator_->Multiply(value, callback_); } + + void Divide(double value) { calculator_->Multiply(1.0 / value, callback_); } + + double GetOutput() const { return output_; } + + private: + void Output(double output) { output_ = output; } + + math::CalculatorPtr calculator_; + double output_; + Callback<void(double)> callback_; +}; + +class SelfDestructingMathCalculatorUI { + public: + explicit SelfDestructingMathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(calculator.Pass()), nesting_level_(0) { + ++num_instances_; + } + + void BeginTest(bool nested) { + nesting_level_ = nested ? 2 : 1; + calculator_->Add( + 1.0, MakeRunnable(&SelfDestructingMathCalculatorUI::Output, this)); + } + + static int num_instances() { return num_instances_; } + + void Output(double value) { + if (--nesting_level_ > 0) { + // Add some more and wait for re-entrant call to Output! + calculator_->Add( + 1.0, MakeRunnable(&SelfDestructingMathCalculatorUI::Output, this)); + base::MessageLoop::current()->RunUntilIdle(); + } else { + delete this; + } + } + + private: + ~SelfDestructingMathCalculatorUI() { --num_instances_; } + + math::CalculatorPtr calculator_; + int nesting_level_; + static int num_instances_; +}; + +// static +int SelfDestructingMathCalculatorUI::num_instances_ = 0; + +class ReentrantServiceImpl : public sample::Service { + public: + ~ReentrantServiceImpl() override {} + + explicit ReentrantServiceImpl(InterfaceRequest<sample::Service> request) + : call_depth_(0), max_call_depth_(0), binding_(this, request.Pass()) {} + + int max_call_depth() { return max_call_depth_; } + + void Frobinate(sample::FooPtr foo, + sample::Service::BazOptions baz, + sample::PortPtr port, + const sample::Service::FrobinateCallback& callback) override { + max_call_depth_ = std::max(++call_depth_, max_call_depth_); + if (call_depth_ == 1) { + EXPECT_TRUE(binding_.WaitForIncomingMethodCall()); + } + call_depth_--; + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest<sample::Port> port) override {} + + private: + int call_depth_; + int max_call_depth_; + Binding<sample::Service> binding_; +}; + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() : integer_(0) {} + ~IntegerAccessorImpl() override {} + + int64_t integer() const { return integer_; } + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(integer_, sample::ENUM_VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override { integer_ = data; } + + int64_t integer_; +}; + +class InterfacePtrTest : public testing::Test { + public: + InterfacePtrTest() : loop_(common::MessagePumpMojo::Create()) {} + ~InterfacePtrTest() override { loop_.RunUntilIdle(); } + + void PumpMessages() { loop_.RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +TEST_F(InterfacePtrTest, IsBound) { + math::CalculatorPtr calc; + EXPECT_FALSE(calc.is_bound()); + MathCalculatorImpl calc_impl(GetProxy(&calc)); + EXPECT_TRUE(calc.is_bound()); +} + +TEST_F(InterfacePtrTest, EndToEnd) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(GetProxy(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(calc.Pass()); + + calculator_ui.Add(2.0); + calculator_ui.Multiply(5.0); + + PumpMessages(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, EndToEnd_Synchronous) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(GetProxy(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(calc.Pass()); + + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + + calculator_ui.Add(2.0); + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + calc_impl.WaitForIncomingMethodCall(); + calculator_ui.WaitForIncomingResponse(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + + calculator_ui.Multiply(5.0); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + calc_impl.WaitForIncomingMethodCall(); + calculator_ui.WaitForIncomingResponse(); + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, Movable) { + math::CalculatorPtr a; + math::CalculatorPtr b; + MathCalculatorImpl calc_impl(GetProxy(&b)); + + EXPECT_TRUE(!a); + EXPECT_FALSE(!b); + + a = b.Pass(); + + EXPECT_FALSE(!a); + EXPECT_TRUE(!b); +} + +TEST_F(InterfacePtrTest, Resettable) { + math::CalculatorPtr a; + + EXPECT_TRUE(!a); + + MessagePipe pipe; + + // Save this so we can test it later. + Handle handle = pipe.handle0.get(); + + a = MakeProxy(InterfacePtrInfo<math::Calculator>(pipe.handle0.Pass(), 0u)); + + EXPECT_FALSE(!a); + + a.reset(); + + EXPECT_TRUE(!a); + EXPECT_FALSE(a.internal_state()->router_for_testing()); + + // Test that handle was closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle)); +} + +TEST_F(InterfacePtrTest, BindInvalidHandle) { + math::CalculatorPtr ptr; + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); + + ptr.Bind(InterfacePtrInfo<math::Calculator>()); + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); +} + +TEST_F(InterfacePtrTest, EncounteredError) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(GetProxy(&proxy)); + + MathCalculatorUI calculator_ui(proxy.Pass()); + + calculator_ui.Add(2.0); + PumpMessages(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.CloseMessagePipe(); + + // The state change isn't picked up locally yet. + EXPECT_FALSE(calculator_ui.encountered_error()); + + PumpMessages(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); +} + +TEST_F(InterfacePtrTest, EncounteredErrorCallback) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(GetProxy(&proxy)); + + bool encountered_error = false; + proxy.set_connection_error_handler( + [&encountered_error]() { encountered_error = true; }); + + MathCalculatorUI calculator_ui(proxy.Pass()); + + calculator_ui.Add(2.0); + PumpMessages(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.CloseMessagePipe(); + + // The state change isn't picked up locally yet. + EXPECT_FALSE(calculator_ui.encountered_error()); + + PumpMessages(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); + + // We should have also been able to observe the error through the error + // handler. + EXPECT_TRUE(encountered_error); +} + +TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(GetProxy(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(proxy.Pass()); + impl->BeginTest(false); + + PumpMessages(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(GetProxy(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(proxy.Pass()); + impl->BeginTest(true); + + PumpMessages(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) { + sample::ServicePtr proxy; + ReentrantServiceImpl impl(GetProxy(&proxy)); + + proxy->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + sample::Service::FrobinateCallback()); + proxy->Frobinate(nullptr, sample::Service::BAZ_OPTIONS_REGULAR, nullptr, + sample::Service::FrobinateCallback()); + + PumpMessages(); + + EXPECT_EQ(2, impl.max_call_depth()); +} + +TEST_F(InterfacePtrTest, QueryVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, GetProxy(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + auto callback = [](uint32_t version) { EXPECT_EQ(3u, version); }; + ptr.QueryVersion(callback); + + PumpMessages(); + + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(InterfacePtrTest, RequireVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, GetProxy(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + ptr.RequireVersion(1u); + EXPECT_EQ(1u, ptr.version()); + ptr->SetInteger(123, sample::ENUM_VALUE); + PumpMessages(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(123, impl.integer()); + + ptr.RequireVersion(3u); + EXPECT_EQ(3u, ptr.version()); + ptr->SetInteger(456, sample::ENUM_VALUE); + PumpMessages(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(456, impl.integer()); + + // Require a version that is not supported by the impl side. + ptr.RequireVersion(4u); + // This value is set to the input of RequireVersion() synchronously. + EXPECT_EQ(4u, ptr.version()); + ptr->SetInteger(789, sample::ENUM_VALUE); + PumpMessages(); + EXPECT_TRUE(ptr.encountered_error()); + // The call to SetInteger() after RequireVersion(4u) is ignored. + EXPECT_EQ(456, impl.integer()); +} + +class StrongMathCalculatorImpl : public math::Calculator { + public: + StrongMathCalculatorImpl(ScopedMessagePipeHandle handle, + bool* error_received, + bool* destroyed) + : error_received_(error_received), + destroyed_(destroyed), + binding_(this, handle.Pass()) { + binding_.set_connection_error_handler( + [this]() { *error_received_ = true; }); + } + ~StrongMathCalculatorImpl() override { *destroyed_ = true; } + + // math::Calculator implementation. + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* error_received_; + bool* destroyed_; + + StrongBinding<math::Calculator> binding_; +}; + +TEST(StrongConnectorTest, Math) { + base::MessageLoop loop(common::MessagePumpMojo::Create()); + + bool error_received = false; + bool destroyed = false; + MessagePipe pipe; + new StrongMathCalculatorImpl(pipe.handle0.Pass(), &error_received, + &destroyed); + + math::CalculatorPtr calc; + calc.Bind(InterfacePtrInfo<math::Calculator>(pipe.handle1.Pass(), 0u)); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(calc.Pass()); + + calculator_ui.Add(2.0); + calculator_ui.Multiply(5.0); + + loop.RunUntilIdle(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + } + // Destroying calculator_ui should close the pipe and generate an error on the + // other + // end which will destroy the instance since it is strongly bound. + + loop.RunUntilIdle(); + EXPECT_TRUE(error_received); + EXPECT_TRUE(destroyed); +} + +class WeakMathCalculatorImpl : public math::Calculator { + public: + WeakMathCalculatorImpl(ScopedMessagePipeHandle handle, + bool* error_received, + bool* destroyed) + : error_received_(error_received), + destroyed_(destroyed), + binding_(this, handle.Pass()) { + binding_.set_connection_error_handler( + [this]() { *error_received_ = true; }); + } + ~WeakMathCalculatorImpl() override { *destroyed_ = true; } + + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* error_received_; + bool* destroyed_; + + Binding<math::Calculator> binding_; +}; + +TEST(WeakConnectorTest, Math) { + base::MessageLoop loop(common::MessagePumpMojo::Create()); + + bool error_received = false; + bool destroyed = false; + MessagePipe pipe; + WeakMathCalculatorImpl impl(pipe.handle0.Pass(), &error_received, &destroyed); + + math::CalculatorPtr calc; + calc.Bind(InterfacePtrInfo<math::Calculator>(pipe.handle1.Pass(), 0u)); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(calc.Pass()); + + calculator_ui.Add(2.0); + calculator_ui.Multiply(5.0); + + loop.RunUntilIdle(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + // Destroying calculator_ui should close the pipe and generate an error on + // the other + // end which will destroy the instance since it is strongly bound. + } + + loop.RunUntilIdle(); + EXPECT_TRUE(error_received); + EXPECT_FALSE(destroyed); +} + +class CImpl : public C { + public: + CImpl(bool* d_called, InterfaceRequest<C> request) + : d_called_(d_called), + binding_(this, request.Pass()) {} + ~CImpl() override {} + + private: + void D() override { + *d_called_ = true; + } + + bool* d_called_; + StrongBinding<C> binding_; +}; + +class BImpl : public B { + public: + BImpl(bool* d_called, InterfaceRequest<B> request) + : d_called_(d_called), + binding_(this, request.Pass()) {} + ~BImpl() override {} + + private: + void GetC(InterfaceRequest<C> c) override { + new CImpl(d_called_, c.Pass()); + } + + bool* d_called_; + StrongBinding<B> binding_; +}; + +class AImpl : public A { + public: + explicit AImpl(InterfaceRequest<A> request) + : d_called_(false), + binding_(this, request.Pass()) {} + ~AImpl() override {} + + bool d_called() const { return d_called_; } + + private: + void GetB(InterfaceRequest<B> b) override { + new BImpl(&d_called_, b.Pass()); + } + + bool d_called_; + Binding<A> binding_; +}; + +TEST_F(InterfacePtrTest, Scoping) { + APtr a; + AImpl a_impl(GetProxy(&a)); + + EXPECT_FALSE(a_impl.d_called()); + + { + BPtr b; + a->GetB(GetProxy(&b)); + CPtr c; + b->GetC(GetProxy(&c)); + c->D(); + } + + // While B & C have fallen out of scope, the pipes will remain until they are + // flushed. + EXPECT_FALSE(a_impl.d_called()); + PumpMessages(); + EXPECT_TRUE(a_impl.d_called()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/map_unittest.cc b/mojo/public/cpp/bindings/tests/map_unittest.cc new file mode 100644 index 0000000..5b049a8 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/map_unittest.cc @@ -0,0 +1,316 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/map.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/bindings/tests/container_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +namespace { + +using mojo::internal::Array_Data; +using mojo::internal::ArrayValidateParams; +using mojo::internal::FixedBufferForTesting; +using mojo::internal::Map_Data; +using mojo::internal::String_Data; + +struct StringIntData { + const char* string_data; + int int_data; +} kStringIntData[] = { + {"one", 1}, + {"two", 2}, + {"three", 3}, + {"four", 4}, +}; + +const size_t kStringIntDataSize = 4; + +using MapTest = testing::Test; + +// Tests that basic Map operations work. +TEST_F(MapTest, InsertWorks) { + Map<String, int> map; + for (size_t i = 0; i < kStringIntDataSize; ++i) + map.insert(kStringIntData[i].string_data, kStringIntData[i].int_data); + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + EXPECT_EQ(kStringIntData[i].int_data, + map.at(kStringIntData[i].string_data)); + } +} + +TEST_F(MapTest, TestIndexOperator) { + Map<String, int> map; + for (size_t i = 0; i < kStringIntDataSize; ++i) + map[kStringIntData[i].string_data] = kStringIntData[i].int_data; + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + EXPECT_EQ(kStringIntData[i].int_data, + map.at(kStringIntData[i].string_data)); + } +} + +TEST_F(MapTest, TestIndexOperatorAsRValue) { + Map<String, int> map; + for (size_t i = 0; i < kStringIntDataSize; ++i) + map.insert(kStringIntData[i].string_data, kStringIntData[i].int_data); + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + EXPECT_EQ(kStringIntData[i].int_data, map[kStringIntData[i].string_data]); + } +} + +TEST_F(MapTest, TestIndexOperatorMoveOnly) { + ASSERT_EQ(0u, MoveOnlyType::num_instances()); + mojo::Map<mojo::String, mojo::Array<int32_t>> map; + std::vector<MoveOnlyType*> value_ptrs; + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + const char* key = kStringIntData[i].string_data; + Array<int32_t> array(1); + array[0] = kStringIntData[i].int_data; + map[key] = array.Pass(); + EXPECT_TRUE(map); + } + + // We now read back that data, to test the behavior of operator[]. + for (size_t i = 0; i < kStringIntDataSize; ++i) { + auto it = map.find(kStringIntData[i].string_data); + ASSERT_TRUE(it != map.end()); + ASSERT_EQ(1u, it.GetValue().size()); + EXPECT_EQ(kStringIntData[i].int_data, it.GetValue()[0]); + } +} + +TEST_F(MapTest, ConstructedFromArray) { + Array<String> keys(kStringIntDataSize); + Array<int> values(kStringIntDataSize); + for (size_t i = 0; i < kStringIntDataSize; ++i) { + keys[i] = kStringIntData[i].string_data; + values[i] = kStringIntData[i].int_data; + } + + Map<String, int> map(keys.Pass(), values.Pass()); + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + EXPECT_EQ(kStringIntData[i].int_data, + map.at(mojo::String(kStringIntData[i].string_data))); + } +} + +TEST_F(MapTest, DecomposeMapTo) { + Array<String> keys(kStringIntDataSize); + Array<int> values(kStringIntDataSize); + for (size_t i = 0; i < kStringIntDataSize; ++i) { + keys[i] = kStringIntData[i].string_data; + values[i] = kStringIntData[i].int_data; + } + + Map<String, int> map(keys.Pass(), values.Pass()); + EXPECT_EQ(kStringIntDataSize, map.size()); + + Array<String> keys2; + Array<int> values2; + map.DecomposeMapTo(&keys2, &values2); + EXPECT_EQ(0u, map.size()); + + EXPECT_EQ(kStringIntDataSize, keys2.size()); + EXPECT_EQ(kStringIntDataSize, values2.size()); + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + // We are not guaranteed that the copies have the same sorting as the + // originals. + String key = kStringIntData[i].string_data; + int value = kStringIntData[i].int_data; + + bool found = false; + for (size_t j = 0; j < keys2.size(); ++j) { + if (keys2[j] == key) { + EXPECT_EQ(value, values2[j]); + found = true; + break; + } + } + + EXPECT_TRUE(found); + } +} + +TEST_F(MapTest, Insert_Copyable) { + ASSERT_EQ(0u, CopyableType::num_instances()); + mojo::Map<mojo::String, CopyableType> map; + std::vector<CopyableType*> value_ptrs; + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + const char* key = kStringIntData[i].string_data; + CopyableType value; + value_ptrs.push_back(value.ptr()); + map.insert(key, value); + ASSERT_EQ(i + 1, map.size()); + ASSERT_EQ(i + 1, value_ptrs.size()); + EXPECT_EQ(map.size() + 1, CopyableType::num_instances()); + EXPECT_TRUE(map.at(key).copied()); + EXPECT_EQ(value_ptrs[i], map.at(key).ptr()); + map.at(key).ResetCopied(); + EXPECT_TRUE(map); + } + + // std::map doesn't have a capacity() method like std::vector so this test is + // a lot more boring. + + map.reset(); + EXPECT_EQ(0u, CopyableType::num_instances()); +} + +TEST_F(MapTest, Insert_MoveOnly) { + ASSERT_EQ(0u, MoveOnlyType::num_instances()); + mojo::Map<mojo::String, MoveOnlyType> map; + std::vector<MoveOnlyType*> value_ptrs; + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + const char* key = kStringIntData[i].string_data; + MoveOnlyType value; + value_ptrs.push_back(value.ptr()); + map.insert(key, value.Pass()); + ASSERT_EQ(i + 1, map.size()); + ASSERT_EQ(i + 1, value_ptrs.size()); + EXPECT_EQ(map.size() + 1, MoveOnlyType::num_instances()); + EXPECT_TRUE(map.at(key).moved()); + EXPECT_EQ(value_ptrs[i], map.at(key).ptr()); + map.at(key).ResetMoved(); + EXPECT_TRUE(map); + } + + // std::map doesn't have a capacity() method like std::vector so this test is + // a lot more boring. + + map.reset(); + EXPECT_EQ(0u, MoveOnlyType::num_instances()); +} + +TEST_F(MapTest, IndexOperator_MoveOnly) { + ASSERT_EQ(0u, MoveOnlyType::num_instances()); + mojo::Map<mojo::String, MoveOnlyType> map; + std::vector<MoveOnlyType*> value_ptrs; + + for (size_t i = 0; i < kStringIntDataSize; ++i) { + const char* key = kStringIntData[i].string_data; + MoveOnlyType value; + value_ptrs.push_back(value.ptr()); + map[key] = value.Pass(); + ASSERT_EQ(i + 1, map.size()); + ASSERT_EQ(i + 1, value_ptrs.size()); + EXPECT_EQ(map.size() + 1, MoveOnlyType::num_instances()); + EXPECT_TRUE(map.at(key).moved()); + EXPECT_EQ(value_ptrs[i], map.at(key).ptr()); + map.at(key).ResetMoved(); + EXPECT_TRUE(map); + } + + // std::map doesn't have a capacity() method like std::vector so this test is + // a lot more boring. + + map.reset(); + EXPECT_EQ(0u, MoveOnlyType::num_instances()); +} + +TEST_F(MapTest, STLToMojo) { + std::map<std::string, int> stl_data; + for (size_t i = 0; i < kStringIntDataSize; ++i) + stl_data[kStringIntData[i].string_data] = kStringIntData[i].int_data; + + Map<String, int32_t> mojo_data = Map<String, int32_t>::From(stl_data); + for (size_t i = 0; i < kStringIntDataSize; ++i) { + EXPECT_EQ(kStringIntData[i].int_data, + mojo_data.at(kStringIntData[i].string_data)); + } +} + +TEST_F(MapTest, MojoToSTL) { + Map<String, int32_t> mojo_map; + for (size_t i = 0; i < kStringIntDataSize; ++i) + mojo_map.insert(kStringIntData[i].string_data, kStringIntData[i].int_data); + + std::map<std::string, int> stl_map = + mojo_map.To<std::map<std::string, int>>(); + for (size_t i = 0; i < kStringIntDataSize; ++i) { + auto it = stl_map.find(kStringIntData[i].string_data); + ASSERT_TRUE(it != stl_map.end()); + EXPECT_EQ(kStringIntData[i].int_data, it->second); + } +} + +TEST_F(MapTest, MapArrayClone) { + Map<String, Array<String>> m; + for (size_t i = 0; i < kStringIntDataSize; ++i) { + Array<String> s; + s.push_back(kStringIntData[i].string_data); + m.insert(kStringIntData[i].string_data, s.Pass()); + } + + Map<String, Array<String>> m2 = m.Clone(); + + for (auto it = m2.begin(); it != m2.end(); ++it) { + ASSERT_EQ(1u, it.GetValue().size()); + EXPECT_EQ(it.GetKey(), it.GetValue().at(0)); + } +} + +TEST_F(MapTest, ArrayOfMap) { + { + Array<Map<int32_t, int8_t>> array(1); + array[0].insert(1, 42); + + size_t size = GetSerializedSize_(array); + FixedBufferForTesting buf(size); + Array_Data<Map_Data<int32_t, int8_t>*>* data; + ArrayValidateParams validate_params( + 0, false, new ArrayValidateParams(0, false, nullptr)); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<Map<int32_t, int8_t>> deserialized_array; + Deserialize_(data, &deserialized_array); + + ASSERT_EQ(1u, deserialized_array.size()); + ASSERT_EQ(1u, deserialized_array[0].size()); + ASSERT_EQ(42, deserialized_array[0].at(1)); + } + + { + Array<Map<String, Array<bool>>> array(1); + Array<bool> map_value(2); + map_value[0] = false; + map_value[1] = true; + array[0].insert("hello world", map_value.Pass()); + + size_t size = GetSerializedSize_(array); + FixedBufferForTesting buf(size); + Array_Data<Map_Data<String_Data*, Array_Data<bool>*>*>* data; + ArrayValidateParams validate_params( + 0, false, new ArrayValidateParams( + 0, false, new ArrayValidateParams(0, false, nullptr))); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<Map<String, Array<bool>>> deserialized_array; + Deserialize_(data, &deserialized_array); + + ASSERT_EQ(1u, deserialized_array.size()); + ASSERT_EQ(1u, deserialized_array[0].size()); + ASSERT_FALSE(deserialized_array[0].at("hello world")[0]); + ASSERT_TRUE(deserialized_array[0].at("hello world")[1]); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.cc b/mojo/public/cpp/bindings/tests/message_queue.cc new file mode 100644 index 0000000..71cb4905 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.cc @@ -0,0 +1,43 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/bindings/tests/message_queue.h" + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/environment/logging.h" + +namespace mojo { +namespace test { + +MessageQueue::MessageQueue() { +} + +MessageQueue::~MessageQueue() { + while (!queue_.empty()) + Pop(); +} + +bool MessageQueue::IsEmpty() const { + return queue_.empty(); +} + +void MessageQueue::Push(Message* message) { + queue_.push(new Message()); + message->MoveTo(queue_.back()); +} + +void MessageQueue::Pop(Message* message) { + MOJO_DCHECK(!queue_.empty()); + queue_.front()->MoveTo(message); + Pop(); +} + +void MessageQueue::Pop() { + MOJO_DCHECK(!queue_.empty()); + delete queue_.front(); + queue_.pop(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.h b/mojo/public/cpp/bindings/tests/message_queue.h new file mode 100644 index 0000000..c3091db --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.h @@ -0,0 +1,43 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ + +#include <queue> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +class Message; + +namespace test { + +// A queue for Message objects. +class MessageQueue { + public: + MessageQueue(); + ~MessageQueue(); + + bool IsEmpty() const; + + // This method copies the message data and steals ownership of its handles. + void Push(Message* message); + + // Removes the next message from the queue, copying its data and transferring + // ownership of its handles to the given |message|. + void Pop(Message* message); + + private: + void Pop(); + + std::queue<Message*> queue_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc new file mode 100644 index 0000000..1e85d4a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/request_response_unittest.cc @@ -0,0 +1,152 @@ +// Copyright 2014 The Chromium 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 "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class ProviderImpl : public sample::Provider { + public: + explicit ProviderImpl(InterfaceRequest<sample::Provider> request) + : binding_(this, request.Pass()) {} + + void EchoString(const String& a, + const Callback<void(String)>& callback) override { + Callback<void(String)> callback_copy; + // Make sure operator= is used. + callback_copy = callback; + callback_copy.Run(a); + } + + void EchoStrings(const String& a, + const String& b, + const Callback<void(String, String)>& callback) override { + callback.Run(a, b); + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const Callback<void(ScopedMessagePipeHandle)>& callback) override { + callback.Run(a.Pass()); + } + + void EchoEnum(sample::Enum a, + const Callback<void(sample::Enum)>& callback) override { + callback.Run(a); + } + + void EchoInt(int32_t a, const EchoIntCallback& callback) override { + callback.Run(a); + } + + Binding<sample::Provider> binding_; +}; + +class StringRecorder { + public: + explicit StringRecorder(std::string* buf) : buf_(buf) {} + void Run(const String& a) const { *buf_ = a; } + void Run(const String& a, const String& b) const { + *buf_ = a.get() + b.get(); + } + + private: + std::string* buf_; +}; + +class EnumRecorder { + public: + explicit EnumRecorder(sample::Enum* value) : value_(value) {} + void Run(sample::Enum a) const { *value_ = a; } + + private: + sample::Enum* value_; +}; + +class MessagePipeWriter { + public: + explicit MessagePipeWriter(const char* text) : text_(text) {} + void Run(ScopedMessagePipeHandle handle) const { + WriteTextMessage(handle.get(), text_); + } + + private: + std::string text_; +}; + +class RequestResponseTest : public testing::Test { + public: + RequestResponseTest() : loop_(common::MessagePumpMojo::Create()) {} + ~RequestResponseTest() override { loop_.RunUntilIdle(); } + + void PumpMessages() { loop_.RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +TEST_F(RequestResponseTest, EchoString) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(GetProxy(&provider)); + + std::string buf; + provider->EchoString(String::From("hello"), StringRecorder(&buf)); + + PumpMessages(); + + EXPECT_EQ(std::string("hello"), buf); +} + +TEST_F(RequestResponseTest, EchoStrings) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(GetProxy(&provider)); + + std::string buf; + provider->EchoStrings( + String::From("hello"), String::From(" world"), StringRecorder(&buf)); + + PumpMessages(); + + EXPECT_EQ(std::string("hello world"), buf); +} + +TEST_F(RequestResponseTest, EchoMessagePipeHandle) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(GetProxy(&provider)); + + MessagePipe pipe2; + provider->EchoMessagePipeHandle(pipe2.handle1.Pass(), + MessagePipeWriter("hello")); + + PumpMessages(); + + std::string value; + ReadTextMessage(pipe2.handle0.get(), &value); + + EXPECT_EQ(std::string("hello"), value); +} + +TEST_F(RequestResponseTest, EchoEnum) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(GetProxy(&provider)); + + sample::Enum value; + provider->EchoEnum(sample::ENUM_VALUE, EnumRecorder(&value)); + + PumpMessages(); + + EXPECT_EQ(sample::ENUM_VALUE, value); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/router_unittest.cc b/mojo/public/cpp/bindings/tests/router_unittest.cc new file mode 100644 index 0000000..c9c9f01 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/router_unittest.cc @@ -0,0 +1,379 @@ +// Copyright 2014 The Chromium 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 <stdlib.h> +#include <string.h> + +#include "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "mojo/public/cpp/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +void AllocRequestMessage(uint32_t name, const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::RequestMessageBuilder builder(name, payload_size); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + + builder.message()->MoveTo(message); +} + +void AllocResponseMessage(uint32_t name, + const char* text, + uint64_t request_id, + Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::ResponseMessageBuilder builder(name, payload_size, request_id); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + + builder.message()->MoveTo(message); +} + +class MessageAccumulator : public MessageReceiver { + public: + explicit MessageAccumulator(MessageQueue* queue) : queue_(queue) {} + + bool Accept(Message* message) override { + queue_->Push(message); + return true; + } + + private: + MessageQueue* queue_; +}; + +class ResponseGenerator : public MessageReceiverWithResponderStatus { + public: + ResponseGenerator() {} + + bool Accept(Message* message) override { return false; } + + bool AcceptWithResponder(Message* message, + MessageReceiverWithStatus* responder) override { + EXPECT_TRUE(message->has_flag(internal::kMessageExpectsResponse)); + + bool result = SendResponse( + message->name(), message->request_id(), + reinterpret_cast<const char*>(message->payload()), responder); + EXPECT_TRUE(responder->IsValid()); + delete responder; + return result; + } + + bool SendResponse(uint32_t name, + uint64_t request_id, + const char* request_string, + MessageReceiver* responder) { + Message response; + std::string response_string(request_string); + response_string += " world!"; + AllocResponseMessage(name, response_string.c_str(), request_id, &response); + + return responder->Accept(&response); + } +}; + +class LazyResponseGenerator : public ResponseGenerator { + public: + LazyResponseGenerator() : responder_(nullptr), name_(0), request_id_(0) {} + + ~LazyResponseGenerator() override { delete responder_; } + + bool AcceptWithResponder(Message* message, + MessageReceiverWithStatus* responder) override { + name_ = message->name(); + request_id_ = message->request_id(); + request_string_ = + std::string(reinterpret_cast<const char*>(message->payload())); + responder_ = responder; + return true; + } + + bool has_responder() const { return !!responder_; } + + bool responder_is_valid() const { return responder_->IsValid(); } + + // Send the response and delete the responder. + void CompleteWithResponse() { Complete(true); } + + // Delete the responder without sending a response. + void CompleteWithoutResponse() { Complete(false); } + + private: + // Completes the request handling by deleting responder_. Optionally + // also sends a response. + void Complete(bool send_response) { + if (send_response) { + SendResponse(name_, request_id_, request_string_.c_str(), responder_); + } + delete responder_; + responder_ = nullptr; + } + + MessageReceiverWithStatus* responder_; + uint32_t name_; + uint64_t request_id_; + std::string request_string_; +}; + +class RouterTest : public testing::Test { + public: + RouterTest() : loop_(common::MessagePumpMojo::Create()) {} + + void SetUp() override { + CreateMessagePipe(nullptr, &handle0_, &handle1_); + } + + void TearDown() override {} + + void PumpMessages() { loop_.RunUntilIdle(); } + + protected: + ScopedMessagePipeHandle handle0_; + ScopedMessagePipeHandle handle1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(RouterTest, BasicRequestResponse) { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + + ResponseGenerator generator; + router1.set_incoming_receiver(&generator); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + + PumpMessages(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + router0.AcceptWithResponder(&request2, + new MessageAccumulator(&message_queue)); + + PumpMessages(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +TEST_F(RouterTest, BasicRequestResponse_Synchronous) { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + + ResponseGenerator generator; + router1.set_incoming_receiver(&generator); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + + router1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + router0.AcceptWithResponder(&request2, + new MessageAccumulator(&message_queue)); + + router1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +TEST_F(RouterTest, RequestWithNoReceiver) { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + + // Without an incoming receiver set on router1, we expect router0 to observe + // an error as a result of sending a message. + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + + PumpMessages(); + + EXPECT_TRUE(router0.encountered_error()); + EXPECT_TRUE(router1.encountered_error()); + EXPECT_TRUE(message_queue.IsEmpty()); +} + +// Tests Router using the LazyResponseGenerator. The responses will not be +// sent until after the requests have been accepted. +TEST_F(RouterTest, LazyResponses) { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + + LazyResponseGenerator generator; + router1.set_incoming_receiver(&generator); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + PumpMessages(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + PumpMessages(); + + // Check the response. + EXPECT_FALSE(message_queue.IsEmpty()); + Message response; + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + router0.AcceptWithResponder(&request2, + new MessageAccumulator(&message_queue)); + PumpMessages(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the second response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + PumpMessages(); + + // Check the second response. + EXPECT_FALSE(message_queue.IsEmpty()); + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +// Tests that if the receiving application destroys the responder_ without +// sending a response, then we trigger connection error at both sides. Moreover, +// both sides still appear to have a valid message pipe handle bound. +TEST_F(RouterTest, MissingResponses) { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + bool error_handler_called0 = false; + router0.set_connection_error_handler( + [&error_handler_called0]() { error_handler_called0 = true; }); + + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + bool error_handler_called1 = false; + router1.set_connection_error_handler( + [&error_handler_called1]() { error_handler_called1 = true; }); + + LazyResponseGenerator generator; + router1.set_incoming_receiver(&generator); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue)); + PumpMessages(); + + // The request has been received but no response has been sent. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Destroy the responder MessagerReceiver but don't send any response. + generator.CompleteWithoutResponse(); + PumpMessages(); + + // Check that no response was received. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(router0.encountered_error()); + EXPECT_TRUE(router1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(router0.is_valid()); + EXPECT_TRUE(router1.is_valid()); +} + +TEST_F(RouterTest, LateResponse) { + // Test that things won't blow up if we try to send a message to a + // MessageReceiver, which was given to us via AcceptWithResponder, + // after the router has gone away. + + LazyResponseGenerator generator; + { + internal::Router router0(handle0_.Pass(), internal::FilterChain()); + internal::Router router1(handle1_.Pass(), internal::FilterChain()); + + router1.set_incoming_receiver(&generator); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + router0.AcceptWithResponder(&request, + new MessageAccumulator(&message_queue)); + + PumpMessages(); + + EXPECT_TRUE(generator.has_responder()); + } + + EXPECT_FALSE(generator.responder_is_valid()); + generator.CompleteWithResponse(); // This should end up doing nothing. +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc new file mode 100644 index 0000000..58878a7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc @@ -0,0 +1,371 @@ +// Copyright 2014 The Chromium 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 <algorithm> +#include <ostream> +#include <string> + +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { + +template <> +struct TypeConverter<int32_t, sample::BarPtr> { + static int32_t Convert(const sample::BarPtr& bar) { + return static_cast<int32_t>(bar->alpha) << 16 | + static_cast<int32_t>(bar->beta) << 8 | + static_cast<int32_t>(bar->gamma); + } +}; + +} // namespace mojo + +namespace sample { +namespace { + +// Set this variable to true to print the message in hex. +bool g_dump_message_as_hex = false; + +// Set this variable to true to print the message in human readable form. +bool g_dump_message_as_text = false; + +// Make a sample |Foo|. +FooPtr MakeFoo() { + mojo::String name("foopy"); + + BarPtr bar(Bar::New()); + bar->alpha = 20; + bar->beta = 40; + bar->gamma = 60; + bar->type = Bar::TYPE_VERTICAL; + + mojo::Array<BarPtr> extra_bars(3); + for (size_t i = 0; i < extra_bars.size(); ++i) { + Bar::Type type = i % 2 == 0 ? Bar::TYPE_VERTICAL : Bar::TYPE_HORIZONTAL; + BarPtr bar(Bar::New()); + uint8_t base = static_cast<uint8_t>(i * 100); + bar->alpha = base; + bar->beta = base + 20; + bar->gamma = base + 40; + bar->type = type; + extra_bars[i] = bar.Pass(); + } + + mojo::Array<uint8_t> data(10); + for (size_t i = 0; i < data.size(); ++i) + data[i] = static_cast<uint8_t>(data.size() - i); + + mojo::Array<mojo::ScopedDataPipeConsumerHandle> input_streams(2); + mojo::Array<mojo::ScopedDataPipeProducerHandle> output_streams(2); + for (size_t i = 0; i < input_streams.size(); ++i) { + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = 1024; + mojo::ScopedDataPipeProducerHandle producer; + mojo::ScopedDataPipeConsumerHandle consumer; + mojo::CreateDataPipe(&options, &producer, &consumer); + input_streams[i] = consumer.Pass(); + output_streams[i] = producer.Pass(); + } + + mojo::Array<mojo::Array<bool>> array_of_array_of_bools(2); + for (size_t i = 0; i < 2; ++i) { + mojo::Array<bool> array_of_bools(2); + for (size_t j = 0; j < 2; ++j) + array_of_bools[j] = j; + array_of_array_of_bools[i] = array_of_bools.Pass(); + } + + mojo::MessagePipe pipe; + FooPtr foo(Foo::New()); + foo->name = name; + foo->x = 1; + foo->y = 2; + foo->a = false; + foo->b = true; + foo->c = false; + foo->bar = bar.Pass(); + foo->extra_bars = extra_bars.Pass(); + foo->data = data.Pass(); + foo->source = pipe.handle1.Pass(); + foo->input_streams = input_streams.Pass(); + foo->output_streams = output_streams.Pass(); + foo->array_of_array_of_bools = array_of_array_of_bools.Pass(); + + return foo.Pass(); +} + +// Check that the given |Foo| is identical to the one made by |MakeFoo()|. +void CheckFoo(const Foo& foo) { + const std::string kName("foopy"); + ASSERT_FALSE(foo.name.is_null()); + EXPECT_EQ(kName.size(), foo.name.size()); + for (size_t i = 0; i < std::min(kName.size(), foo.name.size()); i++) { + // Test both |operator[]| and |at|. + EXPECT_EQ(kName[i], foo.name.at(i)) << i; + EXPECT_EQ(kName[i], foo.name[i]) << i; + } + EXPECT_EQ(kName, foo.name.get()); + + EXPECT_EQ(1, foo.x); + EXPECT_EQ(2, foo.y); + EXPECT_FALSE(foo.a); + EXPECT_TRUE(foo.b); + EXPECT_FALSE(foo.c); + + EXPECT_EQ(20, foo.bar->alpha); + EXPECT_EQ(40, foo.bar->beta); + EXPECT_EQ(60, foo.bar->gamma); + EXPECT_EQ(Bar::TYPE_VERTICAL, foo.bar->type); + + EXPECT_EQ(3u, foo.extra_bars.size()); + for (size_t i = 0; i < foo.extra_bars.size(); i++) { + uint8_t base = static_cast<uint8_t>(i * 100); + Bar::Type type = i % 2 == 0 ? Bar::TYPE_VERTICAL : Bar::TYPE_HORIZONTAL; + EXPECT_EQ(base, foo.extra_bars[i]->alpha) << i; + EXPECT_EQ(base + 20, foo.extra_bars[i]->beta) << i; + EXPECT_EQ(base + 40, foo.extra_bars[i]->gamma) << i; + EXPECT_EQ(type, foo.extra_bars[i]->type) << i; + } + + EXPECT_EQ(10u, foo.data.size()); + for (size_t i = 0; i < foo.data.size(); ++i) { + EXPECT_EQ(static_cast<uint8_t>(foo.data.size() - i), foo.data[i]) << i; + } + + EXPECT_FALSE(foo.input_streams.is_null()); + EXPECT_EQ(2u, foo.input_streams.size()); + + EXPECT_FALSE(foo.output_streams.is_null()); + EXPECT_EQ(2u, foo.output_streams.size()); + + EXPECT_EQ(2u, foo.array_of_array_of_bools.size()); + for (size_t i = 0; i < foo.array_of_array_of_bools.size(); ++i) { + EXPECT_EQ(2u, foo.array_of_array_of_bools[i].size()); + for (size_t j = 0; j < foo.array_of_array_of_bools[i].size(); ++j) { + EXPECT_EQ(bool(j), foo.array_of_array_of_bools[i][j]); + } + } +} + +void PrintSpacer(int depth) { + for (int i = 0; i < depth; ++i) + std::cout << " "; +} + +void Print(int depth, const char* name, bool value) { + PrintSpacer(depth); + std::cout << name << ": " << (value ? "true" : "false") << std::endl; +} + +void Print(int depth, const char* name, int32_t value) { + PrintSpacer(depth); + std::cout << name << ": " << value << std::endl; +} + +void Print(int depth, const char* name, uint8_t value) { + PrintSpacer(depth); + std::cout << name << ": " << uint32_t(value) << std::endl; +} + +template <typename H> +void Print(int depth, + const char* name, + const mojo::ScopedHandleBase<H>& value) { + PrintSpacer(depth); + std::cout << name << ": 0x" << std::hex << value.get().value() << std::endl; +} + +void Print(int depth, const char* name, const mojo::String& str) { + PrintSpacer(depth); + std::cout << name << ": \"" << str.get() << "\"" << std::endl; +} + +void Print(int depth, const char* name, const BarPtr& bar) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!bar.is_null()) { + ++depth; + Print(depth, "alpha", bar->alpha); + Print(depth, "beta", bar->beta); + Print(depth, "gamma", bar->gamma); + Print(depth, "packed", bar.To<int32_t>()); + --depth; + } +} + +template <typename T> +void Print(int depth, const char* name, const mojo::Array<T>& array) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!array.is_null()) { + ++depth; + for (size_t i = 0; i < array.size(); ++i) { + std::stringstream buf; + buf << i; + Print(depth, buf.str().data(), array.at(i)); + } + --depth; + } +} + +void Print(int depth, const char* name, const FooPtr& foo) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!foo.is_null()) { + ++depth; + Print(depth, "name", foo->name); + Print(depth, "x", foo->x); + Print(depth, "y", foo->y); + Print(depth, "a", foo->a); + Print(depth, "b", foo->b); + Print(depth, "c", foo->c); + Print(depth, "bar", foo->bar); + Print(depth, "extra_bars", foo->extra_bars); + Print(depth, "data", foo->data); + Print(depth, "source", foo->source); + Print(depth, "input_streams", foo->input_streams); + Print(depth, "output_streams", foo->output_streams); + Print(depth, "array_of_array_of_bools", foo->array_of_array_of_bools); + --depth; + } +} + +void DumpHex(const uint8_t* bytes, uint32_t num_bytes) { + for (uint32_t i = 0; i < num_bytes; ++i) { + std::cout << std::setw(2) << std::setfill('0') << std::hex + << uint32_t(bytes[i]); + + if (i % 16 == 15) { + std::cout << std::endl; + continue; + } + + if (i % 2 == 1) + std::cout << " "; + if (i % 8 == 7) + std::cout << " "; + } +} + +class ServiceImpl : public Service { + public: + void Frobinate(FooPtr foo, + BazOptions baz, + PortPtr port, + const Service::FrobinateCallback& callback) override { + // Users code goes here to handle the incoming Frobinate message. + + // We mainly check that we're given the expected arguments. + EXPECT_FALSE(foo.is_null()); + if (!foo.is_null()) + CheckFoo(*foo); + EXPECT_EQ(BAZ_OPTIONS_EXTRA, baz); + + if (g_dump_message_as_text) { + // Also dump the Foo structure and all of its members. + std::cout << "Frobinate:" << std::endl; + int depth = 1; + Print(depth, "foo", foo); + Print(depth, "baz", baz); + Print(depth, "port", port.get()); + } + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest<Port> port_request) override {} +}; + +class ServiceProxyImpl : public ServiceProxy { + public: + explicit ServiceProxyImpl(mojo::MessageReceiverWithResponder* receiver) + : ServiceProxy(receiver) {} +}; + +class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder { + public: + bool Accept(mojo::Message* message) override { + // Imagine some IPC happened here. + + if (g_dump_message_as_hex) { + DumpHex(reinterpret_cast<const uint8_t*>(message->data()), + message->data_num_bytes()); + } + + // In the receiving process, an implementation of ServiceStub is known to + // the system. It receives the incoming message. + ServiceImpl impl; + + ServiceStub stub; + stub.set_sink(&impl); + return stub.Accept(message); + } + + bool AcceptWithResponder(mojo::Message* message, + mojo::MessageReceiver* responder) override { + return false; + } +}; + +using BindingsSampleTest = testing::Test; + +TEST_F(BindingsSampleTest, Basic) { + SimpleMessageReceiver receiver; + + // User has a proxy to a Service somehow. + Service* service = new ServiceProxyImpl(&receiver); + + // User constructs a message to send. + + // Notice that it doesn't matter in what order the structs / arrays are + // allocated. Here, the various members of Foo are allocated before Foo is + // allocated. + + FooPtr foo = MakeFoo(); + CheckFoo(*foo); + + PortPtr port; + service->Frobinate(foo.Pass(), Service::BAZ_OPTIONS_EXTRA, port.Pass(), + Service::FrobinateCallback()); + + delete service; +} + +TEST_F(BindingsSampleTest, DefaultValues) { + DefaultsTestPtr defaults(DefaultsTest::New()); + EXPECT_EQ(-12, defaults->a0); + EXPECT_EQ(kTwelve, defaults->a1); + EXPECT_EQ(1234, defaults->a2); + EXPECT_EQ(34567U, defaults->a3); + EXPECT_EQ(123456, defaults->a4); + EXPECT_EQ(3456789012U, defaults->a5); + EXPECT_EQ(-111111111111LL, defaults->a6); + EXPECT_EQ(9999999999999999999ULL, defaults->a7); + EXPECT_EQ(0x12345, defaults->a8); + EXPECT_EQ(-0x12345, defaults->a9); + EXPECT_EQ(1234, defaults->a10); + EXPECT_TRUE(defaults->a11); + EXPECT_FALSE(defaults->a12); + EXPECT_FLOAT_EQ(123.25f, defaults->a13); + EXPECT_DOUBLE_EQ(1234567890.123, defaults->a14); + EXPECT_DOUBLE_EQ(1E10, defaults->a15); + EXPECT_DOUBLE_EQ(-1.2E+20, defaults->a16); + EXPECT_DOUBLE_EQ(1.23E-20, defaults->a17); + EXPECT_TRUE(defaults->a18.is_null()); + EXPECT_TRUE(defaults->a19.is_null()); + EXPECT_EQ(Bar::TYPE_BOTH, defaults->a20); + EXPECT_TRUE(defaults->a21.is_null()); + ASSERT_FALSE(defaults->a22.is_null()); + EXPECT_EQ(imported::SHAPE_RECTANGLE, defaults->a22->shape); + EXPECT_EQ(imported::COLOR_BLACK, defaults->a22->color); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, defaults->a23); + EXPECT_EQ(0x123456789, defaults->a24); + EXPECT_EQ(-0x123456789, defaults->a25); +} + +} // namespace +} // namespace sample diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc new file mode 100644 index 0000000..15da051 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc @@ -0,0 +1,227 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Serialization warnings are only recorded in debug build. +#ifndef NDEBUG + +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::ArrayValidateParams; + +// Creates an array of arrays of handles (2 X 3) for testing. +Array<Array<ScopedHandle>> CreateTestNestedHandleArray() { + Array<Array<ScopedHandle>> array(2); + for (size_t i = 0; i < array.size(); ++i) { + Array<ScopedHandle> nested_array(3); + for (size_t j = 0; j < nested_array.size(); ++j) { + MessagePipe pipe; + nested_array[j] = ScopedHandle::From(pipe.handle1.Pass()); + } + array[i] = nested_array.Pass(); + } + + return array.Pass(); +} + +class SerializationWarningTest : public testing::Test { + public: + ~SerializationWarningTest() override {} + + protected: + template <typename T> + void TestWarning(StructPtr<T> obj, + mojo::internal::ValidationError expected_warning) { + TestStructWarningImpl<T>(obj.Pass(), expected_warning); + } + + template <typename T> + void TestWarning(InlinedStructPtr<T> obj, + mojo::internal::ValidationError expected_warning) { + TestStructWarningImpl<T>(obj.Pass(), expected_warning); + } + + template <typename T, typename TPtr> + void TestStructWarningImpl(TPtr obj, + mojo::internal::ValidationError expected_warning) { + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::FixedBufferForTesting buf(GetSerializedSize_(obj)); + typename T::Data_* data; + Serialize_(obj.Pass(), &buf, &data); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + template <typename T> + void TestArrayWarning(T obj, + mojo::internal::ValidationError expected_warning, + const ArrayValidateParams* validate_params) { + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::FixedBufferForTesting buf(GetSerializedSize_(obj)); + typename T::Data_* data; + SerializeArray_(obj.Pass(), &buf, &data, validate_params); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + mojo::internal::SerializationWarningObserverForTesting warning_observer_; +}; + +TEST_F(SerializationWarningTest, HandleInStruct) { + Struct2Ptr test_struct(Struct2::New()); + EXPECT_FALSE(test_struct->hdl.is_valid()); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE); + + test_struct = Struct2::New(); + MessagePipe pipe; + test_struct->hdl = ScopedHandle::From(pipe.handle1.Pass()); + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, StructInStruct) { + Struct3Ptr test_struct(Struct3::New()); + EXPECT_TRUE(!test_struct->struct_1); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct3::New(); + test_struct->struct_1 = Struct1::New(); + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfStructsInStruct) { + Struct4Ptr test_struct(Struct4::New()); + EXPECT_TRUE(!test_struct->data); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct4::New(); + test_struct->data.resize(1); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct4::New(); + test_struct->data.resize(0); + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); + + test_struct = Struct4::New(); + test_struct->data.resize(1); + test_struct->data[0] = Struct1::New(); + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, FixedArrayOfStructsInStruct) { + Struct5Ptr test_struct(Struct5::New()); + EXPECT_TRUE(!test_struct->pair); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct5::New(); + test_struct->pair.resize(1); + test_struct->pair[0] = Struct1::New(); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + + test_struct = Struct5::New(); + test_struct->pair.resize(2); + test_struct->pair[0] = Struct1::New(); + test_struct->pair[1] = Struct1::New(); + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, StringInStruct) { + Struct6Ptr test_struct(Struct6::New()); + EXPECT_TRUE(!test_struct->str); + + TestWarning(test_struct.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct6::New(); + test_struct->str = "hello world"; + + TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfArraysOfHandles) { + Array<Array<ScopedHandle>> test_array = CreateTestNestedHandleArray(); + test_array[0] = Array<ScopedHandle>(); + test_array[1][0] = ScopedHandle(); + + ArrayValidateParams validate_params_0( + 0, true, new ArrayValidateParams(0, true, nullptr)); + TestArrayWarning(test_array.Pass(), mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + test_array = CreateTestNestedHandleArray(); + test_array[0] = Array<ScopedHandle>(); + ArrayValidateParams validate_params_1( + 0, false, new ArrayValidateParams(0, true, nullptr)); + TestArrayWarning(test_array.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = CreateTestNestedHandleArray(); + test_array[1][0] = ScopedHandle(); + ArrayValidateParams validate_params_2( + 0, true, new ArrayValidateParams(0, false, nullptr)); + TestArrayWarning(test_array.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + &validate_params_2); +} + +TEST_F(SerializationWarningTest, ArrayOfStrings) { + Array<String> test_array(3); + for (size_t i = 0; i < test_array.size(); ++i) + test_array[i] = "hello"; + + ArrayValidateParams validate_params_0( + 0, true, new ArrayValidateParams(0, false, nullptr)); + TestArrayWarning(test_array.Pass(), mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + test_array = Array<String>(3); + ArrayValidateParams validate_params_1( + 0, false, new ArrayValidateParams(0, false, nullptr)); + TestArrayWarning(test_array.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = Array<String>(2); + ArrayValidateParams validate_params_2( + 3, true, new ArrayValidateParams(0, false, nullptr)); + TestArrayWarning(test_array.Pass(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + &validate_params_2); +} + +} // namespace +} // namespace test +} // namespace mojo + +#endif diff --git a/mojo/public/cpp/bindings/tests/string_unittest.cc b/mojo/public/cpp/bindings/tests/string_unittest.cc new file mode 100644 index 0000000..f6bc424 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/string_unittest.cc @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(StringTest, DefaultIsNull) { + String s; + EXPECT_TRUE(s.is_null()); +} + +TEST(StringTest, ConstructedWithNULL) { + String s(nullptr); + EXPECT_TRUE(s.is_null()); +} + +TEST(StringTest, ConstructedWithNullCharPointer) { + const char* null = nullptr; + String s(null); + EXPECT_TRUE(s.is_null()); +} + +TEST(StringTest, AssignedNULL) { + String s(""); + EXPECT_FALSE(s.is_null()); + s = nullptr; + EXPECT_TRUE(s.is_null()); +} + +TEST(StringTest, Empty) { + String s(""); + EXPECT_FALSE(s.is_null()); + EXPECT_TRUE(s.get().empty()); +} + +TEST(StringTest, Basic) { + String s("hello world"); + EXPECT_EQ(std::string("hello world"), s.get()); +} + +TEST(StringTest, Assignment) { + String s("hello world"); + String t = s; // Makes a copy. + EXPECT_FALSE(t.is_null()); + EXPECT_EQ(std::string("hello world"), t.get()); + EXPECT_FALSE(s.is_null()); +} + +TEST(StringTest, Equality) { + String s("hello world"); + String t("hello world"); + EXPECT_EQ(s, t); + EXPECT_TRUE(s == s); + EXPECT_FALSE(s != s); + EXPECT_TRUE(s == t); + EXPECT_FALSE(s != t); + EXPECT_TRUE("hello world" == s); + EXPECT_TRUE(s == "hello world"); + EXPECT_TRUE("not" != s); + EXPECT_FALSE("not" == s); + EXPECT_TRUE(s != "not"); + EXPECT_FALSE(s == "not"); + + // Test null strings. + String n1; + String n2; + EXPECT_TRUE(n1 == n1); + EXPECT_FALSE(n1 != n2); + EXPECT_TRUE(n1 == n2); + EXPECT_FALSE(n1 != n2); + EXPECT_TRUE(n1 != s); + EXPECT_FALSE(n1 == s); + EXPECT_TRUE(s != n1); + EXPECT_FALSE(s == n1); +} + +TEST(StringTest, LessThanNullness) { + String null; + String null2; + EXPECT_FALSE(null < null2); + EXPECT_FALSE(null2 < null); + + String real("real"); + EXPECT_TRUE(null < real); + EXPECT_FALSE(real < null); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc new file mode 100644 index 0000000..24173e5 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc @@ -0,0 +1,415 @@ +// Copyright 2014 The Chromium 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 <string.h> + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +RectPtr MakeRect(int32_t factor = 1) { + RectPtr rect(Rect::New()); + rect->x = 1 * factor; + rect->y = 2 * factor; + rect->width = 10 * factor; + rect->height = 20 * factor; + return rect.Pass(); +} + +void CheckRect(const Rect& rect, int32_t factor = 1) { + EXPECT_EQ(1 * factor, rect.x); + EXPECT_EQ(2 * factor, rect.y); + EXPECT_EQ(10 * factor, rect.width); + EXPECT_EQ(20 * factor, rect.height); +} + +MultiVersionStructPtr MakeMultiVersionStruct() { + MultiVersionStructPtr output(MultiVersionStruct::New()); + output->f_int32 = 123; + output->f_rect = MakeRect(5); + output->f_string = "hello"; + output->f_array = Array<int8_t>(3); + output->f_array[0] = 10; + output->f_array[1] = 9; + output->f_array[2] = 8; + MessagePipe pipe; + output->f_message_pipe = pipe.handle0.Pass(); + output->f_int16 = 42; + + return output.Pass(); +} + +template <typename U, typename T> +U SerializeAndDeserialize(T input) { + typedef typename mojo::internal::WrapperTraits<T>::DataType InputDataType; + typedef typename mojo::internal::WrapperTraits<U>::DataType OutputDataType; + + size_t size = GetSerializedSize_(input); + mojo::internal::FixedBufferForTesting buf(size + 32); + InputDataType data; + Serialize_(input.Pass(), &buf, &data); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + + // Set the subsequent area to a special value, so that we can find out if we + // mistakenly access the area. + void* subsequent_area = buf.Allocate(32); + memset(subsequent_area, 0xAA, 32); + + OutputDataType output_data = reinterpret_cast<OutputDataType>(data); + output_data->DecodePointersAndHandles(&handles); + + U output; + Deserialize_(output_data, &output); + return output.Pass(); +} + +using StructTest = testing::Test; + +} // namespace + +TEST_F(StructTest, Rect) { + RectPtr rect; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = nullptr; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = MakeRect(); + EXPECT_FALSE(rect.is_null()); + EXPECT_FALSE(!rect); + EXPECT_TRUE(rect); + + RectPtr null_rect = nullptr; + EXPECT_TRUE(null_rect.is_null()); + EXPECT_TRUE(!null_rect); + EXPECT_FALSE(null_rect); + + CheckRect(*rect); +} + +TEST_F(StructTest, Clone) { + NamedRegionPtr region; + + NamedRegionPtr clone_region = region.Clone(); + EXPECT_TRUE(clone_region.is_null()); + + region = NamedRegion::New(); + clone_region = region.Clone(); + EXPECT_TRUE(clone_region->name.is_null()); + EXPECT_TRUE(clone_region->rects.is_null()); + + region->name = "hello world"; + clone_region = region.Clone(); + EXPECT_EQ(region->name, clone_region->name); + + region->rects = Array<RectPtr>(2); + region->rects[1] = MakeRect(); + clone_region = region.Clone(); + EXPECT_EQ(2u, clone_region->rects.size()); + EXPECT_TRUE(clone_region->rects[0].is_null()); + CheckRect(*clone_region->rects[1]); + + // NoDefaultFieldValues contains handles, so Clone() is not available, but + // NoDefaultFieldValuesPtr should still compile. + NoDefaultFieldValuesPtr no_default_field_values(NoDefaultFieldValues::New()); + EXPECT_FALSE(no_default_field_values->f13.is_valid()); +} + +// Serialization test of a struct with no pointer or handle members. +TEST_F(StructTest, Serialization_Basic) { + RectPtr rect(MakeRect()); + + size_t size = GetSerializedSize_(rect); + EXPECT_EQ(8U + 16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::Rect_Data* data; + Serialize_(rect.Pass(), &buf, &data); + + RectPtr rect2; + Deserialize_(data, &rect2); + + CheckRect(*rect2); +} + +// Construction of a struct with struct pointers from null. +TEST_F(StructTest, Construction_StructPointers) { + RectPairPtr pair; + EXPECT_TRUE(pair.is_null()); + + pair = RectPair::New(); + EXPECT_FALSE(pair.is_null()); + EXPECT_TRUE(pair->first.is_null()); + EXPECT_TRUE(pair->first.is_null()); + + pair = nullptr; + EXPECT_TRUE(pair.is_null()); +} + +// Serialization test of a struct with struct pointers. +TEST_F(StructTest, Serialization_StructPointers) { + RectPairPtr pair(RectPair::New()); + pair->first = MakeRect(); + pair->second = MakeRect(); + + size_t size = GetSerializedSize_(pair); + EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::RectPair_Data* data; + Serialize_(pair.Pass(), &buf, &data); + + RectPairPtr pair2; + Deserialize_(data, &pair2); + + CheckRect(*pair2->first); + CheckRect(*pair2->second); +} + +// Serialization test of a struct with an array member. +TEST_F(StructTest, Serialization_ArrayPointers) { + NamedRegionPtr region(NamedRegion::New()); + region->name = "region"; + region->rects = Array<RectPtr>::New(4); + for (size_t i = 0; i < region->rects.size(); ++i) + region->rects[i] = MakeRect(static_cast<int32_t>(i) + 1); + + size_t size = GetSerializedSize_(region); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U + // rects pointer + 8U + // name header + 8U + // name payload (rounded up) + 8U + // rects header + 4 * 8U + // rects payload (four pointers) + 4 * (8U + // rect header + 16U), // rect payload (four ints) + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + Serialize_(region.Pass(), &buf, &data); + + NamedRegionPtr region2; + Deserialize_(data, ®ion2); + + EXPECT_EQ(String("region"), region2->name); + + EXPECT_EQ(4U, region2->rects.size()); + for (size_t i = 0; i < region2->rects.size(); ++i) + CheckRect(*region2->rects[i], static_cast<int32_t>(i) + 1); +} + +// Serialization test of a struct with null array pointers. +TEST_F(StructTest, Serialization_NullArrayPointers) { + NamedRegionPtr region(NamedRegion::New()); + EXPECT_TRUE(region->name.is_null()); + EXPECT_TRUE(region->rects.is_null()); + + size_t size = GetSerializedSize_(region); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U, // rects pointer + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + Serialize_(region.Pass(), &buf, &data); + + NamedRegionPtr region2; + Deserialize_(data, ®ion2); + + EXPECT_TRUE(region2->name.is_null()); + EXPECT_TRUE(region2->rects.is_null()); +} + +// Tests deserializing structs as a newer version. +TEST_F(StructTest, Versioning_OldToNew) { + { + MultiVersionStructV0Ptr input(MultiVersionStructV0::New()); + input->f_int32 = 123; + MultiVersionStructPtr expected_output(MultiVersionStruct::New()); + expected_output->f_int32 = 123; + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV1Ptr input(MultiVersionStructV1::New()); + input->f_int32 = 123; + input->f_rect = MakeRect(5); + MultiVersionStructPtr expected_output(MultiVersionStruct::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV3Ptr input(MultiVersionStructV3::New()); + input->f_int32 = 123; + input->f_rect = MakeRect(5); + input->f_string = "hello"; + MultiVersionStructPtr expected_output(MultiVersionStruct::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV5Ptr input(MultiVersionStructV5::New()); + input->f_int32 = 123; + input->f_rect = MakeRect(5); + input->f_string = "hello"; + input->f_array = Array<int8_t>(3); + input->f_array[0] = 10; + input->f_array[1] = 9; + input->f_array[2] = 8; + MultiVersionStructPtr expected_output(MultiVersionStruct::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + expected_output->f_array = Array<int8_t>(3); + expected_output->f_array[0] = 10; + expected_output->f_array[1] = 9; + expected_output->f_array[2] = 8; + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV7Ptr input(MultiVersionStructV7::New()); + input->f_int32 = 123; + input->f_rect = MakeRect(5); + input->f_string = "hello"; + input->f_array = Array<int8_t>(3); + input->f_array[0] = 10; + input->f_array[1] = 9; + input->f_array[2] = 8; + MessagePipe pipe; + input->f_message_pipe = pipe.handle0.Pass(); + + MultiVersionStructPtr expected_output(MultiVersionStruct::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + expected_output->f_array = Array<int8_t>(3); + expected_output->f_array[0] = 10; + expected_output->f_array[1] = 9; + expected_output->f_array[2] = 8; + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} + +// Tests deserializing structs as an older version. +TEST_F(StructTest, Versioning_NewToOld) { + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV7Ptr expected_output(MultiVersionStructV7::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + expected_output->f_array = Array<int8_t>(3); + expected_output->f_array[0] = 10; + expected_output->f_array[1] = 9; + expected_output->f_array[2] = 8; + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructV7Ptr output = + SerializeAndDeserialize<MultiVersionStructV7Ptr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV5Ptr expected_output(MultiVersionStructV5::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + expected_output->f_array = Array<int8_t>(3); + expected_output->f_array[0] = 10; + expected_output->f_array[1] = 9; + expected_output->f_array[2] = 8; + + MultiVersionStructV5Ptr output = + SerializeAndDeserialize<MultiVersionStructV5Ptr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV3Ptr expected_output(MultiVersionStructV3::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + expected_output->f_string = "hello"; + + MultiVersionStructV3Ptr output = + SerializeAndDeserialize<MultiVersionStructV3Ptr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV1Ptr expected_output(MultiVersionStructV1::New()); + expected_output->f_int32 = 123; + expected_output->f_rect = MakeRect(5); + + MultiVersionStructV1Ptr output = + SerializeAndDeserialize<MultiVersionStructV1Ptr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV0Ptr expected_output(MultiVersionStructV0::New()); + expected_output->f_int32 = 123; + + MultiVersionStructV0Ptr output = + SerializeAndDeserialize<MultiVersionStructV0Ptr>(input.Pass()); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc new file mode 100644 index 0000000..704110a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc @@ -0,0 +1,204 @@ +// Copyright 2013 The Chromium 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 "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +struct RedmondRect { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct RedmondNamedRegion { + std::string name; + std::vector<RedmondRect> rects; +}; + +bool AreEqualRectArrays(const Array<test::RectPtr>& rects1, + const Array<test::RectPtr>& rects2) { + if (rects1.size() != rects2.size()) + return false; + + for (size_t i = 0; i < rects1.size(); ++i) { + if (rects1[i]->x != rects2[i]->x || rects1[i]->y != rects2[i]->y || + rects1[i]->width != rects2[i]->width || + rects1[i]->height != rects2[i]->height) { + return false; + } + } + + return true; +} + +} // namespace + +template <> +struct TypeConverter<test::RectPtr, RedmondRect> { + static test::RectPtr Convert(const RedmondRect& input) { + test::RectPtr rect(test::Rect::New()); + rect->x = input.left; + rect->y = input.top; + rect->width = input.right - input.left; + rect->height = input.bottom - input.top; + return rect.Pass(); + } +}; + +template <> +struct TypeConverter<RedmondRect, test::RectPtr> { + static RedmondRect Convert(const test::RectPtr& input) { + RedmondRect rect; + rect.left = input->x; + rect.top = input->y; + rect.right = input->x + input->width; + rect.bottom = input->y + input->height; + return rect; + } +}; + +template <> +struct TypeConverter<test::NamedRegionPtr, RedmondNamedRegion> { + static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) { + test::NamedRegionPtr region(test::NamedRegion::New()); + region->name = input.name; + region->rects = Array<test::RectPtr>::From(input.rects); + return region.Pass(); + } +}; + +template <> +struct TypeConverter<RedmondNamedRegion, test::NamedRegionPtr> { + static RedmondNamedRegion Convert(const test::NamedRegionPtr& input) { + RedmondNamedRegion region; + region.name = input->name; + region.rects = input->rects.To<std::vector<RedmondRect>>(); + return region; + } +}; + +namespace test { +namespace { + +TEST(TypeConversionTest, String) { + const char kText[6] = "hello"; + + String a = std::string(kText); + String b(kText); + String c(static_cast<const char*>(kText)); + + EXPECT_EQ(std::string(kText), a.To<std::string>()); + EXPECT_EQ(std::string(kText), b.To<std::string>()); + EXPECT_EQ(std::string(kText), c.To<std::string>()); +} + +TEST(TypeConversionTest, String_Null) { + String a; + EXPECT_TRUE(a.is_null()); + EXPECT_EQ(std::string(), a.To<std::string>()); + + String b = String::From(static_cast<const char*>(nullptr)); + EXPECT_TRUE(b.is_null()); +} + +TEST(TypeConversionTest, String_Empty) { + String a = ""; + EXPECT_EQ(std::string(), a.To<std::string>()); + + String b = std::string(); + EXPECT_FALSE(b.is_null()); + EXPECT_EQ(std::string(), b.To<std::string>()); +} + +TEST(TypeConversionTest, StringWithEmbeddedNull) { + const std::string kText("hel\0lo", 6); + + String a(kText); + EXPECT_EQ(kText, a.To<std::string>()); + + // Expect truncation: + String b(kText.c_str()); + EXPECT_EQ(std::string("hel"), b.To<std::string>()); +} + +TEST(TypeConversionTest, CustomTypeConverter) { + RectPtr rect(Rect::New()); + rect->x = 10; + rect->y = 20; + rect->width = 50; + rect->height = 45; + + RedmondRect rr = rect.To<RedmondRect>(); + EXPECT_EQ(10, rr.left); + EXPECT_EQ(20, rr.top); + EXPECT_EQ(60, rr.right); + EXPECT_EQ(65, rr.bottom); + + RectPtr rect2(Rect::From(rr)); + EXPECT_EQ(rect->x, rect2->x); + EXPECT_EQ(rect->y, rect2->y); + EXPECT_EQ(rect->width, rect2->width); + EXPECT_EQ(rect->height, rect2->height); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array_Null) { + Array<RectPtr> rects; + + std::vector<RedmondRect> redmond_rects = rects.To<std::vector<RedmondRect>>(); + + EXPECT_TRUE(redmond_rects.empty()); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array) { + const RedmondRect kBase = {10, 20, 30, 40}; + + Array<RectPtr> rects(10); + for (size_t i = 0; i < rects.size(); ++i) { + RedmondRect rr = kBase; + rr.left += static_cast<int32_t>(i); + rr.top += static_cast<int32_t>(i); + rects[i] = Rect::From(rr); + } + + std::vector<RedmondRect> redmond_rects = rects.To<std::vector<RedmondRect>>(); + + Array<RectPtr> rects2 = Array<RectPtr>::From(redmond_rects); + EXPECT_TRUE(AreEqualRectArrays(rects, rects2)); +} + +TEST(TypeConversionTest, CustomTypeConverter_Nested) { + RedmondNamedRegion redmond_region; + redmond_region.name = "foopy"; + + const RedmondRect kBase = {10, 20, 30, 40}; + + for (size_t i = 0; i < 10; ++i) { + RedmondRect rect = kBase; + rect.left += static_cast<int32_t>(i); + rect.top += static_cast<int32_t>(i); + redmond_region.rects.push_back(rect); + } + + // Round-trip through generated struct and TypeConverter. + + NamedRegionPtr copy = NamedRegion::From(redmond_region); + RedmondNamedRegion redmond_region2 = copy.To<RedmondNamedRegion>(); + + EXPECT_EQ(redmond_region.name, redmond_region2.name); + EXPECT_EQ(redmond_region.rects.size(), redmond_region2.rects.size()); + for (size_t i = 0; i < redmond_region.rects.size(); ++i) { + EXPECT_EQ(redmond_region.rects[i].left, redmond_region2.rects[i].left); + EXPECT_EQ(redmond_region.rects[i].top, redmond_region2.rects[i].top); + EXPECT_EQ(redmond_region.rects[i].right, redmond_region2.rects[i].right); + EXPECT_EQ(redmond_region.rects[i].bottom, redmond_region2.rects[i].bottom); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc new file mode 100644 index 0000000..e6fddb7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/union_unittest.cc @@ -0,0 +1,1131 @@ +// Copyright 2014 The Chromium 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 <vector> + +#include "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(UnionTest, PlainOldDataGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + + pod->set_f_int8(10); + EXPECT_EQ(10, pod->get_f_int8()); + EXPECT_TRUE(pod->is_f_int8()); + EXPECT_FALSE(pod->is_f_int8_other()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT8); + + pod->set_f_uint8(11); + EXPECT_EQ(11, pod->get_f_uint8()); + EXPECT_TRUE(pod->is_f_uint8()); + EXPECT_FALSE(pod->is_f_int8()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT8); + + pod->set_f_int16(12); + EXPECT_EQ(12, pod->get_f_int16()); + EXPECT_TRUE(pod->is_f_int16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT16); + + pod->set_f_uint16(13); + EXPECT_EQ(13, pod->get_f_uint16()); + EXPECT_TRUE(pod->is_f_uint16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT16); + + pod->set_f_int32(14); + EXPECT_EQ(14, pod->get_f_int32()); + EXPECT_TRUE(pod->is_f_int32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32); + + pod->set_f_uint32(static_cast<uint32_t>(15)); + EXPECT_EQ(static_cast<uint32_t>(15), pod->get_f_uint32()); + EXPECT_TRUE(pod->is_f_uint32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32); + + pod->set_f_int64(16); + EXPECT_EQ(16, pod->get_f_int64()); + EXPECT_TRUE(pod->is_f_int64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64); + + pod->set_f_uint64(static_cast<uint64_t>(17)); + EXPECT_EQ(static_cast<uint64_t>(17), pod->get_f_uint64()); + EXPECT_TRUE(pod->is_f_uint64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64); + + pod->set_f_float(1.5); + EXPECT_EQ(1.5, pod->get_f_float()); + EXPECT_TRUE(pod->is_f_float()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_FLOAT); + + pod->set_f_double(1.9); + EXPECT_EQ(1.9, pod->get_f_double()); + EXPECT_TRUE(pod->is_f_double()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_DOUBLE); + + pod->set_f_bool(true); + EXPECT_TRUE(pod->get_f_bool()); + pod->set_f_bool(false); + EXPECT_FALSE(pod->get_f_bool()); + EXPECT_TRUE(pod->is_f_bool()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_BOOL); + + pod->set_f_enum(AN_ENUM_SECOND); + EXPECT_EQ(AN_ENUM_SECOND, pod->get_f_enum()); + EXPECT_TRUE(pod->is_f_enum()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodEquals) { + PodUnionPtr pod1(PodUnion::New()); + PodUnionPtr pod2(PodUnion::New()); + + pod1->set_f_int8(10); + pod2->set_f_int8(10); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_int8(11); + EXPECT_FALSE(pod1.Equals(pod2)); + + pod2->set_f_int8_other(10); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, PodClone) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + PodUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(10, pod_clone->get_f_int8()); + EXPECT_TRUE(pod_clone->is_f_int8()); + EXPECT_EQ(pod_clone->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, PodSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_int8(10); + + size_t size = GetSerializedSize_(pod1, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + SerializeUnion_(pod1.Pass(), &buf, &data, false); + + PodUnionPtr pod2; + Deserialize_(data, &pod2); + + EXPECT_EQ(10, pod2->get_f_int8()); + EXPECT_TRUE(pod2->is_f_int8()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, EnumSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_enum(AN_ENUM_SECOND); + + size_t size = GetSerializedSize_(pod1, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + SerializeUnion_(pod1.Pass(), &buf, &data, false); + + PodUnionPtr pod2; + Deserialize_(data, &pod2); + + EXPECT_EQ(AN_ENUM_SECOND, pod2->get_f_enum()); + EXPECT_TRUE(pod2->is_f_enum()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + size_t size = GetSerializedSize_(pod, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + SerializeUnion_(pod.Pass(), &buf, &data, false); + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE( + internal::PodUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, SerializeNotNull) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(0); + size_t size = GetSerializedSize_(pod, false); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + SerializeUnion_(pod.Pass(), &buf, &data, false); + EXPECT_FALSE(data->is_null()); +} + +TEST(UnionTest, SerializeIsNullInlined) { + PodUnionPtr pod; + size_t size = GetSerializedSize_(pod, false); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + + // Check that dirty output buffers are handled correctly by serialization. + data->size = 16U; + data->tag = PodUnion::Tag::F_UINT16; + data->data.f_f_int16 = 20; + + SerializeUnion_(pod.Pass(), &buf, &data, true); + EXPECT_TRUE(data->is_null()); + + PodUnionPtr pod2; + Deserialize_(data, &pod2); + EXPECT_TRUE(pod2.is_null()); +} + +TEST(UnionTest, SerializeIsNullNotInlined) { + PodUnionPtr pod; + size_t size = GetSerializedSize_(pod, false); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + SerializeUnion_(pod.Pass(), &buf, &data, false); + EXPECT_EQ(nullptr, data); +} + +TEST(UnionTest, NullValidation) { + void* buf = nullptr; + mojo::internal::BoundsChecker bounds_checker(buf, 0, 0); + EXPECT_TRUE(internal::PodUnion_Data::Validate(buf, &bounds_checker, false)); +} + +TEST(UnionTest, OutOfAlignmentValidation) { + size_t size = sizeof(internal::PodUnion_Data); + // Get an aligned object and shift the alignment. + mojo::internal::FixedBufferForTesting aligned_buf(size + 1); + void* raw_buf = aligned_buf.Leak(); + char* buf = reinterpret_cast<char*>(raw_buf) + 1; + + internal::PodUnion_Data* data = + reinterpret_cast<internal::PodUnion_Data*>(buf); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_FALSE(internal::PodUnion_Data::Validate(buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, OOBValidation) { + size_t size = sizeof(internal::PodUnion_Data) - 1; + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownTagValidation) { + size_t size = sizeof(internal::PodUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + data->tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, StringGetterSetter) { + ObjectUnionPtr pod(ObjectUnion::New()); + + String hello("hello world"); + pod->set_f_string(hello); + EXPECT_EQ(hello, pod->get_f_string()); + EXPECT_TRUE(pod->is_f_string()); + EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringEquals) { + ObjectUnionPtr pod1(ObjectUnion::New()); + ObjectUnionPtr pod2(ObjectUnion::New()); + + pod1->set_f_string("hello world"); + pod2->set_f_string("hello world"); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_string("hello universe"); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, StringClone) { + ObjectUnionPtr pod(ObjectUnion::New()); + + String hello("hello world"); + pod->set_f_string(hello); + ObjectUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(hello, pod_clone->get_f_string()); + EXPECT_TRUE(pod_clone->is_f_string()); + EXPECT_EQ(pod_clone->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringSerialization) { + ObjectUnionPtr pod1(ObjectUnion::New()); + + String hello("hello world"); + pod1->set_f_string(hello); + + size_t size = GetSerializedSize_(pod1, false); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(pod1.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + ObjectUnionPtr pod2; + Deserialize_(data, &pod2); + EXPECT_EQ(hello, pod2->get_f_string()); + EXPECT_TRUE(pod2->is_f_string()); + EXPECT_EQ(pod2->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, NullStringValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0x0; + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, StringPointerOverflowValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0xFFFFFFFFFFFFFFFF; + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, StringValidateOOB) { + size_t size = 32; + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + + data->data.f_f_string.offset = 8; + char* ptr = reinterpret_cast<char*>(&data->data.f_f_string); + mojo::internal::ArrayHeader* array_header = + reinterpret_cast<mojo::internal::ArrayHeader*>(ptr + *ptr); + array_header->num_bytes = 20; // This should go out of bounds. + array_header->num_elements = 20; + mojo::internal::BoundsChecker bounds_checker(data, 32, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +// TODO(azani): Move back in array_unittest.cc when possible. +// Array tests +TEST(UnionTest, PodUnionInArray) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_array = Array<PodUnionPtr>(2); + small_struct->pod_union_array[0] = PodUnion::New(); + small_struct->pod_union_array[1] = PodUnion::New(); + + small_struct->pod_union_array[0]->set_f_int8(10); + small_struct->pod_union_array[1]->set_f_int16(12); + + EXPECT_EQ(10, small_struct->pod_union_array[0]->get_f_int8()); + EXPECT_EQ(12, small_struct->pod_union_array[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerialization) { + Array<PodUnionPtr> array(2); + array[0] = PodUnion::New(); + array[1] = PodUnion::New(); + + array[0]->set_f_int8(10); + array[1]->set_f_int16(12); + EXPECT_EQ(2U, array.size()); + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data<internal::PodUnion_Data>* data; + mojo::internal::ArrayValidateParams validate_params(0, false, nullptr); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<PodUnionPtr> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_EQ(12, array2[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerializationWithNull) { + Array<PodUnionPtr> array(2); + array[0] = PodUnion::New(); + + array[0]->set_f_int8(10); + EXPECT_EQ(2U, array.size()); + + size_t size = GetSerializedSize_(array); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data<internal::PodUnion_Data>* data; + mojo::internal::ArrayValidateParams validate_params(0, true, nullptr); + SerializeArray_(array.Pass(), &buf, &data, &validate_params); + + Array<PodUnionPtr> array2; + Deserialize_(data, &array2); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_TRUE(array2[1].is_null()); +} + +// TODO(azani): Move back in struct_unittest.cc when possible. +// Struct tests +TEST(UnionTest, Clone_Union) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int8(10); + + SmallStructPtr clone = small_struct.Clone(); + EXPECT_EQ(10, clone->pod_union->get_f_int8()); +} + +// Serialization test of a struct with a union of plain old data. +TEST(UnionTest, Serialization_UnionOfPods) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + size_t size = GetSerializedSize_(small_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + Serialize_(small_struct.Pass(), &buf, &data); + + SmallStructPtr deserialized; + Deserialize_(data, &deserialized); + + EXPECT_EQ(10, deserialized->pod_union->get_f_int32()); +} + +// Serialization test of a struct with a union of structs. +TEST(UnionTest, Serialization_UnionOfObjects) { + SmallObjStructPtr obj_struct(SmallObjStruct::New()); + obj_struct->obj_union = ObjectUnion::New(); + String hello("hello world"); + obj_struct->obj_union->set_f_string(hello); + + size_t size = GetSerializedSize_(obj_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallObjStruct_Data* data = nullptr; + Serialize_(obj_struct.Pass(), &buf, &data); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + SmallObjStructPtr deserialized; + Deserialize_(data, &deserialized); + + EXPECT_EQ(hello, deserialized->obj_union->get_f_string()); +} + +// Validation test of a struct with a union. +TEST(UnionTest, Validation_UnionsInStruct) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + size_t size = GetSerializedSize_(small_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + Serialize_(small_struct.Pass(), &buf, &data); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate(raw_buf, &bounds_checker)); + free(raw_buf); +} + +// Validation test of a struct union fails due to unknown union tag. +TEST(UnionTest, Validation_PodUnionInStruct_Failure) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + size_t size = GetSerializedSize_(small_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + Serialize_(small_struct.Pass(), &buf, &data); + data->pod_union.tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(100); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_FALSE(internal::SmallStruct_Data::Validate(raw_buf, &bounds_checker)); + free(raw_buf); +} + +// Validation fails due to non-nullable null union in struct. +TEST(UnionTest, Validation_NullUnion_Failure) { + SmallStructNonNullableUnionPtr small_struct( + SmallStructNonNullableUnion::New()); + + size_t size = GetSerializedSize_(small_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStructNonNullableUnion_Data* data = + internal::SmallStructNonNullableUnion_Data::New(&buf); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_FALSE(internal::SmallStructNonNullableUnion_Data::Validate( + raw_buf, &bounds_checker)); + free(raw_buf); +} + +// Validation passes with nullable null union. +TEST(UnionTest, Validation_NullableUnion) { + SmallStructPtr small_struct(SmallStruct::New()); + + size_t size = GetSerializedSize_(small_struct); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + Serialize_(small_struct.Pass(), &buf, &data); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate(raw_buf, &bounds_checker)); + free(raw_buf); +} + +// TODO(azani): Move back in map_unittest.cc when possible. +// Map Tests +TEST(UnionTest, PodUnionInMap) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_map = Map<String, PodUnionPtr>(); + small_struct->pod_union_map.insert("one", PodUnion::New()); + small_struct->pod_union_map.insert("two", PodUnion::New()); + + small_struct->pod_union_map["one"]->set_f_int8(8); + small_struct->pod_union_map["two"]->set_f_int16(16); + + EXPECT_EQ(8, small_struct->pod_union_map["one"]->get_f_int8()); + EXPECT_EQ(16, small_struct->pod_union_map["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerialization) { + Map<String, PodUnionPtr> map; + map.insert("one", PodUnion::New()); + map.insert("two", PodUnion::New()); + + map["one"]->set_f_int8(8); + map["two"]->set_f_int16(16); + + size_t size = GetSerializedSize_(map); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Map_Data<mojo::internal::String_Data*, + internal::PodUnion_Data>* data; + mojo::internal::ArrayValidateParams validate_params(0, false, nullptr); + SerializeMap_(map.Pass(), &buf, &data, &validate_params); + + Map<String, PodUnionPtr> map2; + Deserialize_(data, &map2); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_EQ(16, map2["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerializationWithNull) { + Map<String, PodUnionPtr> map; + map.insert("one", PodUnion::New()); + map.insert("two", nullptr); + + map["one"]->set_f_int8(8); + + size_t size = GetSerializedSize_(map); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Map_Data<mojo::internal::String_Data*, + internal::PodUnion_Data>* data; + mojo::internal::ArrayValidateParams validate_params(0, true, nullptr); + SerializeMap_(map.Pass(), &buf, &data, &validate_params); + + Map<String, PodUnionPtr> map2; + Deserialize_(data, &map2); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_TRUE(map2["two"].is_null()); +} + +TEST(UnionTest, StructInUnionGetterSetterPasser) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(dummy.Pass()); + + EXPECT_EQ(8, obj->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionSerialization) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(dummy.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + ObjectUnionPtr obj2; + Deserialize_(data, &obj2); + EXPECT_EQ(8, obj2->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionValidation) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(dummy.Pass()); + + size_t size = GetSerializedSize_(obj, false); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNonNullable) { + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(dummy.Pass()); + + size_t size = GetSerializedSize_(obj, false); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_FALSE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNullable) { + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_nullable(dummy.Pass()); + + size_t size = GetSerializedSize_(obj, false); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, ArrayInUnionGetterSetter) { + Array<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(array.Pass()); + + EXPECT_EQ(8, obj->get_f_array_int8()[0]); + EXPECT_EQ(9, obj->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionSerialization) { + Array<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(array.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + ObjectUnionPtr obj2; + Deserialize_(data, &obj2); + + EXPECT_EQ(8, obj2->get_f_array_int8()[0]); + EXPECT_EQ(9, obj2->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionValidation) { + Array<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(array.Pass()); + + size_t size = GetSerializedSize_(obj, false); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + + EXPECT_TRUE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, MapInUnionGetterSetter) { + Map<String, int8_t> map; + map.insert("one", 1); + map.insert("two", 2); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(map.Pass()); + + EXPECT_EQ(1, obj->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionSerialization) { + Map<String, int8_t> map; + map.insert("one", 1); + map.insert("two", 2); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(map.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + ObjectUnionPtr obj2; + Deserialize_(data, &obj2); + + EXPECT_EQ(1, obj2->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj2->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionValidation) { + Map<String, int8_t> map; + map.insert("one", 1); + map.insert("two", 2); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(map.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + + EXPECT_TRUE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(pod.Pass()); + + EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionSerialization) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(pod.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + data->DecodePointersAndHandles(&handles); + + ObjectUnionPtr obj2; + Deserialize_(data, &obj2); + EXPECT_EQ(10, obj2->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(pod.Pass()); + + size_t size = GetSerializedSize_(obj, false); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_TRUE(handles.empty()); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_TRUE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionValidationNonNullable) { + PodUnionPtr pod(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(pod.Pass()); + + size_t size = GetSerializedSize_(obj, false); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + SerializeUnion_(obj.Pass(), &buf, &data, false); + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 0); + EXPECT_FALSE( + internal::ObjectUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionGetterSetter) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(pipe1.Pass()); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionSerialization) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(pipe1.Pass()); + + size_t size = GetSerializedSize_(handle, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + SerializeUnion_(handle.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_EQ(1U, handles.size()); + data->DecodePointersAndHandles(&handles); + + HandleUnionPtr handle2(HandleUnion::New()); + Deserialize_(data, &handle2); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle2->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionValidation) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(pipe1.Pass()); + + size_t size = GetSerializedSize_(handle, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + SerializeUnion_(handle.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 1); + EXPECT_TRUE( + internal::HandleUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionValidationNull) { + ScopedMessagePipeHandle pipe; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(pipe.Pass()); + + size_t size = GetSerializedSize_(handle, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + SerializeUnion_(handle.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + + void* raw_buf = buf.Leak(); + mojo::internal::BoundsChecker bounds_checker(data, + static_cast<uint32_t>(size), 1); + EXPECT_FALSE( + internal::HandleUnion_Data::Validate(raw_buf, &bounds_checker, false)); + free(raw_buf); +} + +class SmallCacheImpl : public SmallCache { + public: + SmallCacheImpl() : int_value_(0) {} + ~SmallCacheImpl() override {} + int64_t int_value() const { return int_value_; } + + private: + void SetIntValue(int64_t int_value) override { int_value_ = int_value; } + void GetIntValue(const GetIntValueCallback& callback) override { + callback.Run(int_value_); + } + + int64_t int_value_; +}; + +TEST(UnionTest, InterfaceInUnion) { + base::MessageLoop run_loop(common::MessagePumpMojo::Create()); + SmallCacheImpl impl; + SmallCachePtr ptr; + Binding<SmallCache> bindings(&impl, GetProxy(&ptr)); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(ptr.Pass()); + + handle->get_f_small_cache()->SetIntValue(10); + run_loop.RunUntilIdle(); + EXPECT_EQ(10, impl.int_value()); +} + +TEST(UnionTest, InterfaceInUnionSerialization) { + base::MessageLoop run_loop(common::MessagePumpMojo::Create()); + SmallCacheImpl impl; + SmallCachePtr ptr; + Binding<SmallCache> bindings(&impl, GetProxy(&ptr)); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(ptr.Pass()); + size_t size = GetSerializedSize_(handle, false); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + SerializeUnion_(handle.Pass(), &buf, &data, false); + + std::vector<Handle> handles; + data->EncodePointersAndHandles(&handles); + EXPECT_EQ(1U, handles.size()); + data->DecodePointersAndHandles(&handles); + + HandleUnionPtr handle2(HandleUnion::New()); + Deserialize_(data, &handle2); + + handle2->get_f_small_cache()->SetIntValue(10); + run_loop.RunUntilIdle(); + EXPECT_EQ(10, impl.int_value()); +} + +class UnionInterfaceImpl : public UnionInterface { + public: + UnionInterfaceImpl() {} + ~UnionInterfaceImpl() override {} + + private: + void Echo(PodUnionPtr in, const EchoCallback& callback) override { + callback.Run(in.Pass()); + } +}; + +TEST(UnionTest, UnionInInterface) { + base::MessageLoop run_loop(common::MessagePumpMojo::Create()); + UnionInterfaceImpl impl; + UnionInterfacePtr ptr; + Binding<UnionInterface> bindings(&impl, GetProxy(&ptr)); + + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int16(16); + + ptr->Echo(pod.Pass(), + [](PodUnionPtr out) { EXPECT_EQ(16, out->get_f_int16()); }); + run_loop.RunUntilIdle(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc new file mode 100644 index 0000000..9d2607d --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc @@ -0,0 +1,410 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include <limits> +#include <map> +#include <set> +#include <utility> + +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace test { +namespace { + +class ValidationTestInputParser { + public: + ValidationTestInputParser(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message); + ~ValidationTestInputParser(); + + bool Run(); + + private: + struct DataType; + + typedef std::pair<const char*, const char*> Range; + + typedef bool (ValidationTestInputParser::*ParseDataFunc)( + const DataType& type, + const std::string& value_string); + + struct DataType { + const char* name; + size_t name_size; + size_t data_size; + ParseDataFunc parse_data_func; + }; + + // A dist4/8 item that hasn't been matched with an anchr item. + struct PendingDistanceItem { + // Where this data item is located in |data_|. + size_t pos; + // Either 4 or 8 (bytes). + size_t data_size; + }; + + bool GetNextItem(Range* range); + + bool ParseItem(const Range& range); + + bool ParseUnsignedInteger(const DataType& type, + const std::string& value_string); + bool ParseSignedInteger(const DataType& type, + const std::string& value_string); + bool ParseFloat(const DataType& type, const std::string& value_string); + bool ParseDouble(const DataType& type, const std::string& value_string); + bool ParseBinarySequence(const DataType& type, + const std::string& value_string); + bool ParseDistance(const DataType& type, const std::string& value_string); + bool ParseAnchor(const DataType& type, const std::string& value_string); + bool ParseHandles(const DataType& type, const std::string& value_string); + + bool StartsWith(const Range& range, const char* prefix, size_t prefix_length); + + bool ConvertToUnsignedInteger(const std::string& value_string, + unsigned long long int* value); + + template <typename T> + void AppendData(T data) { + size_t pos = data_->size(); + data_->resize(pos + sizeof(T)); + memcpy(&(*data_)[pos], &data, sizeof(T)); + } + + template <typename TargetType, typename InputType> + bool ConvertAndAppendData(InputType value) { + if (value > std::numeric_limits<TargetType>::max() || + value < std::numeric_limits<TargetType>::min()) { + return false; + } + AppendData(static_cast<TargetType>(value)); + return true; + } + + template <typename TargetType, typename InputType> + bool ConvertAndFillData(size_t pos, InputType value) { + if (value > std::numeric_limits<TargetType>::max() || + value < std::numeric_limits<TargetType>::min()) { + return false; + } + TargetType target_value = static_cast<TargetType>(value); + assert(pos + sizeof(TargetType) <= data_->size()); + memcpy(&(*data_)[pos], &target_value, sizeof(TargetType)); + return true; + } + + static const DataType kDataTypes[]; + static const size_t kDataTypeCount; + + const std::string& input_; + size_t input_cursor_; + + std::vector<uint8_t>* data_; + size_t* num_handles_; + std::string* error_message_; + + std::map<std::string, PendingDistanceItem> pending_distance_items_; + std::set<std::string> anchors_; +}; + +#define DATA_TYPE(name, data_size, parse_data_func) \ + { name, sizeof(name) - 1, data_size, parse_data_func } + +const ValidationTestInputParser::DataType + ValidationTestInputParser::kDataTypes[] = { + DATA_TYPE("[u1]", 1, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u2]", 2, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u4]", 4, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u8]", 8, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[s1]", 1, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s2]", 2, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s4]", 4, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s8]", 8, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[b]", 1, &ValidationTestInputParser::ParseBinarySequence), + DATA_TYPE("[f]", 4, &ValidationTestInputParser::ParseFloat), + DATA_TYPE("[d]", 8, &ValidationTestInputParser::ParseDouble), + DATA_TYPE("[dist4]", 4, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[dist8]", 8, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[anchr]", 0, &ValidationTestInputParser::ParseAnchor), + DATA_TYPE("[handles]", 0, &ValidationTestInputParser::ParseHandles)}; + +const size_t ValidationTestInputParser::kDataTypeCount = + sizeof(ValidationTestInputParser::kDataTypes) / + sizeof(ValidationTestInputParser::kDataTypes[0]); + +ValidationTestInputParser::ValidationTestInputParser(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message) + : input_(input), + input_cursor_(0), + data_(data), + num_handles_(num_handles), + error_message_(error_message) { + assert(data_); + assert(num_handles_); + assert(error_message_); + data_->clear(); + *num_handles_ = 0; + error_message_->clear(); +} + +ValidationTestInputParser::~ValidationTestInputParser() { +} + +bool ValidationTestInputParser::Run() { + Range range; + bool result = true; + while (result && GetNextItem(&range)) + result = ParseItem(range); + + if (!result) { + *error_message_ = + "Error occurred when parsing " + std::string(range.first, range.second); + } else if (!pending_distance_items_.empty()) { + // We have parsed all the contents in |input_| successfully, but there are + // unmatched dist4/8 items. + *error_message_ = "Error occurred when matching [dist4/8] and [anchr]."; + result = false; + } + if (!result) { + data_->clear(); + *num_handles_ = 0; + } else { + assert(error_message_->empty()); + } + + return result; +} + +bool ValidationTestInputParser::GetNextItem(Range* range) { + const char kWhitespaceChars[] = " \t\n\r"; + const char kItemDelimiters[] = " \t\n\r/"; + const char kEndOfLineChars[] = "\n\r"; + while (true) { + // Skip leading whitespaces. + // If there are no non-whitespace characters left, |input_cursor_| will be + // set to std::npos. + input_cursor_ = input_.find_first_not_of(kWhitespaceChars, input_cursor_); + + if (input_cursor_ >= input_.size()) + return false; + + if (StartsWith( + Range(&input_[0] + input_cursor_, &input_[0] + input_.size()), + "//", + 2)) { + // Skip contents until the end of the line. + input_cursor_ = input_.find_first_of(kEndOfLineChars, input_cursor_); + } else { + range->first = &input_[0] + input_cursor_; + input_cursor_ = input_.find_first_of(kItemDelimiters, input_cursor_); + range->second = input_cursor_ >= input_.size() + ? &input_[0] + input_.size() + : &input_[0] + input_cursor_; + return true; + } + } + return false; +} + +bool ValidationTestInputParser::ParseItem(const Range& range) { + for (size_t i = 0; i < kDataTypeCount; ++i) { + if (StartsWith(range, kDataTypes[i].name, kDataTypes[i].name_size)) { + return (this->*kDataTypes[i].parse_data_func)( + kDataTypes[i], + std::string(range.first + kDataTypes[i].name_size, range.second)); + } + } + + // "[u1]" is optional. + return ParseUnsignedInteger(kDataTypes[0], + std::string(range.first, range.second)); +} + +bool ValidationTestInputParser::ParseUnsignedInteger( + const DataType& type, + const std::string& value_string) { + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData<uint8_t>(value); + case 2: + return ConvertAndAppendData<uint16_t>(value); + case 4: + return ConvertAndAppendData<uint32_t>(value); + case 8: + return ConvertAndAppendData<uint64_t>(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseSignedInteger( + const DataType& type, + const std::string& value_string) { + long long int value; + if (sscanf(value_string.c_str(), "%lli", &value) != 1) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData<int8_t>(value); + case 2: + return ConvertAndAppendData<int16_t>(value); + case 4: + return ConvertAndAppendData<int32_t>(value); + case 8: + return ConvertAndAppendData<int64_t>(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseFloat(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(float) == 4, "sizeof(float) is not 4"); + + float value; + if (sscanf(value_string.c_str(), "%f", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDouble(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(double) == 8, "sizeof(double) is not 8"); + + double value; + if (sscanf(value_string.c_str(), "%lf", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseBinarySequence( + const DataType& type, + const std::string& value_string) { + if (value_string.size() != 8) + return false; + + uint8_t value = 0; + for (std::string::const_iterator iter = value_string.begin(); + iter != value_string.end(); + ++iter) { + value <<= 1; + if (*iter == '1') + value++; + else if (*iter != '0') + return false; + } + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDistance(const DataType& type, + const std::string& value_string) { + if (pending_distance_items_.find(value_string) != + pending_distance_items_.end()) + return false; + + PendingDistanceItem item = {data_->size(), type.data_size}; + data_->resize(data_->size() + type.data_size); + pending_distance_items_[value_string] = item; + + return true; +} + +bool ValidationTestInputParser::ParseAnchor(const DataType& type, + const std::string& value_string) { + if (anchors_.find(value_string) != anchors_.end()) + return false; + anchors_.insert(value_string); + + std::map<std::string, PendingDistanceItem>::iterator iter = + pending_distance_items_.find(value_string); + if (iter == pending_distance_items_.end()) + return false; + + PendingDistanceItem dist_item = iter->second; + pending_distance_items_.erase(iter); + + size_t distance = data_->size() - dist_item.pos; + switch (dist_item.data_size) { + case 4: + return ConvertAndFillData<uint32_t>(dist_item.pos, distance); + case 8: + return ConvertAndFillData<uint64_t>(dist_item.pos, distance); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseHandles(const DataType& type, + const std::string& value_string) { + // It should be the first item. + if (!data_->empty()) + return false; + + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + if (value > std::numeric_limits<size_t>::max()) + return false; + + *num_handles_ = static_cast<size_t>(value); + return true; +} + +bool ValidationTestInputParser::StartsWith(const Range& range, + const char* prefix, + size_t prefix_length) { + if (static_cast<size_t>(range.second - range.first) < prefix_length) + return false; + + return memcmp(range.first, prefix, prefix_length) == 0; +} + +bool ValidationTestInputParser::ConvertToUnsignedInteger( + const std::string& value_string, + unsigned long long int* value) { + const char* format = nullptr; + if (value_string.find_first_of("xX") != std::string::npos) + format = "%llx"; + else + format = "%llu"; + return sscanf(value_string.c_str(), format, value) == 1; +} + +} // namespace + +bool ParseValidationTestInput(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message) { + ValidationTestInputParser parser(input, data, num_handles, error_message); + return parser.Run(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.h b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h new file mode 100644 index 0000000..d5f8c81 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +namespace mojo { +namespace test { + +// Input Format of Mojo Message Validation Tests. +// +// Data items are separated by whitespaces: +// - ' ' (0x20) space; +// - '\t' (0x09) horizontal tab; +// - '\n' (0x0a) newline; +// - '\r' (0x0d) carriage return. +// A comment starts with //, extending to the end of the line. +// Each data item is of the format [<type>]<value>. The types defined and the +// corresponding value formats are described below. +// +// Type: u1 / u2 / u4 / u8 +// Description: Little-endian 1/2/4/8-byte unsigned integer. +// Value Format: +// - Decimal integer: 0|[1-9][0-9]* +// - Hexadecimal integer: 0[xX][0-9a-fA-F]+ +// - The type prefix (including the square brackets) of 1-byte unsigned +// integer is optional. +// +// Type: s1 / s2 / s4 / s8 +// Description: Little-endian 1/2/4/8-byte signed integer. +// Value Format: +// - Decimal integer: [-+]?(0|[1-9][0-9]*) +// - Hexadecimal integer: [-+]?0[xX][0-9a-fA-F]+ +// +// Type: b +// Description: Binary sequence of 1 byte. +// Value Format: [01]{8} +// +// Type: f / d +// Description: Little-endian IEEE-754 format of float (4 bytes) and double (8 +// bytes). +// Value Format: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? +// +// Type: dist4 / dist8 +// Description: Little-endian 4/8-byte unsigned integer. The actual value is set +// to the byte distance from the location of this integer to the location of the +// anchr item with the same ID. A dist8 and anchr pair can be used to easily +// represent an encoded pointer. A dist4 and anchr pair can be used to easily +// calculate struct/array size. +// Value Format: The value is an ID: [0-9a-zA-Z_]+ +// +// Type: anchr +// Description: Mark an anchor location. It doesn’t translate into any actual +// data. +// Value Format: The value is an ID of the same format as that of dist4/8. +// +// Type: handles +// Description: The number of handles that are associated with the message. This +// special item is not part of the message data. If specified, it should be the +// first item. +// Value Format: The same format as u1/2/4/8. +// +// EXAMPLE: +// +// Suppose you have the following Mojo types defined: +// struct Bar { +// int32 a; +// bool b; +// bool c; +// }; +// struct Foo { +// Bar x; +// uint32 y; +// }; +// +// The following describes a valid message whose payload is a Foo struct: +// // message header +// [dist4]message_header // num_bytes +// [u4]3 // version +// [u4]0 // type +// [u4]1 // flags +// [u8]1234 // request_id +// [anchr]message_header +// +// // payload +// [dist4]foo // num_bytes +// [u4]2 // version +// [dist8]bar_ptr // x +// [u4]0xABCD // y +// [u4]0 // padding +// [anchr]foo +// +// [anchr]bar_ptr +// [dist4]bar // num_bytes +// [u4]3 // version +// [s4]-1 // a +// [b]00000010 // b and c +// 0 0 0 // padding +// [anchr]bar + +// Parses validation test input. +// On success, |data| and |num_handles| store the parsing result, +// |error_message| is cleared; on failure, |error_message| is set to a message +// describing the error, |data| is cleared and |num_handles| set to 0. +// Note: For now, this method only works on little-endian platforms. +bool ParseValidationTestInput(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc new file mode 100644 index 0000000..fc536c2 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc @@ -0,0 +1,480 @@ +// Copyright 2014 The Chromium 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 <stdio.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "mojo/message_pump/message_pump_mojo.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/lib/connector.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename T> +void Append(std::vector<uint8_t>* data_vector, T data) { + size_t pos = data_vector->size(); + data_vector->resize(pos + sizeof(T)); + memcpy(&(*data_vector)[pos], &data, sizeof(T)); +} + +bool TestInputParser(const std::string& input, + bool expected_result, + const std::vector<uint8_t>& expected_data, + size_t expected_num_handles) { + std::vector<uint8_t> data; + size_t num_handles; + std::string error_message; + + bool result = + ParseValidationTestInput(input, &data, &num_handles, &error_message); + if (expected_result) { + if (result && error_message.empty() && expected_data == data && + expected_num_handles == num_handles) { + return true; + } + + // Compare with an empty string instead of checking |error_message.empty()|, + // so that the message will be printed out if the two are not equal. + EXPECT_EQ(std::string(), error_message); + EXPECT_EQ(expected_data, data); + EXPECT_EQ(expected_num_handles, num_handles); + return false; + } + + EXPECT_FALSE(error_message.empty()); + return !result && !error_message.empty(); +} + +std::vector<std::string> GetMatchingTests(const std::vector<std::string>& names, + const std::string& prefix) { + const std::string suffix = ".data"; + std::vector<std::string> tests; + for (size_t i = 0; i < names.size(); ++i) { + if (names[i].size() >= suffix.size() && + names[i].substr(0, prefix.size()) == prefix && + names[i].substr(names[i].size() - suffix.size()) == suffix) + tests.push_back(names[i].substr(0, names[i].size() - suffix.size())); + } + return tests; +} + +bool ReadFile(const std::string& path, std::string* result) { + FILE* fp = OpenSourceRootRelativeFile(path.c_str()); + if (!fp) { + ADD_FAILURE() << "File not found: " << path; + return false; + } + fseek(fp, 0, SEEK_END); + size_t size = static_cast<size_t>(ftell(fp)); + if (size == 0) { + result->clear(); + fclose(fp); + return true; + } + fseek(fp, 0, SEEK_SET); + result->resize(size); + size_t size_read = fread(&result->at(0), 1, size, fp); + fclose(fp); + return size == size_read; +} + +bool ReadAndParseDataFile(const std::string& path, + std::vector<uint8_t>* data, + size_t* num_handles) { + std::string input; + if (!ReadFile(path, &input)) + return false; + + std::string error_message; + if (!ParseValidationTestInput(input, data, num_handles, &error_message)) { + ADD_FAILURE() << error_message; + return false; + } + + return true; +} + +bool ReadResultFile(const std::string& path, std::string* result) { + if (!ReadFile(path, result)) + return false; + + // Result files are new-line delimited text files. Remove any CRs. + result->erase(std::remove(result->begin(), result->end(), '\r'), + result->end()); + + // Remove trailing LFs. + size_t pos = result->find_last_not_of('\n'); + if (pos == std::string::npos) + result->clear(); + else + result->resize(pos + 1); + + return true; +} + +std::string GetPath(const std::string& root, const std::string& suffix) { + return "mojo/public/interfaces/bindings/tests/data/validation/" + root + + suffix; +} + +// |message| should be a newly created object. +bool ReadTestCase(const std::string& test, + Message* message, + std::string* expected) { + std::vector<uint8_t> data; + size_t num_handles; + if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) || + !ReadResultFile(GetPath(test, ".expected"), expected)) { + return false; + } + + message->AllocUninitializedData(static_cast<uint32_t>(data.size())); + if (!data.empty()) + memcpy(message->mutable_data(), &data[0], data.size()); + message->mutable_handles()->resize(num_handles); + + return true; +} + +void RunValidationTests(const std::string& prefix, + MessageReceiver* test_message_receiver) { + std::vector<std::string> names = + EnumerateSourceRootRelativeDirectory(GetPath("", "")); + std::vector<std::string> tests = GetMatchingTests(names, prefix); + + for (size_t i = 0; i < tests.size(); ++i) { + Message message; + std::string expected; + ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected)); + + std::string result; + mojo::internal::ValidationErrorObserverForTesting observer; + mojo_ignore_result(test_message_receiver->Accept(&message)); + if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE) + result = "PASS"; + else + result = mojo::internal::ValidationErrorToString(observer.last_error()); + + EXPECT_EQ(expected, result) << "failed test: " << tests[i]; + } +} + +class DummyMessageReceiver : public MessageReceiver { + public: + bool Accept(Message* message) override { + return true; // Any message is OK. + } +}; + +using ValidationTest = testing::Test; + +class ValidationIntegrationTest : public ValidationTest { + public: + ValidationIntegrationTest() + : loop_(common::MessagePumpMojo::Create()), + test_message_receiver_(nullptr) {} + + ~ValidationIntegrationTest() override {} + + void SetUp() override { + ScopedMessagePipeHandle tester_endpoint; + ASSERT_EQ(MOJO_RESULT_OK, + CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_)); + test_message_receiver_ = + new TestMessageReceiver(this, tester_endpoint.Pass()); + } + + void TearDown() override { + delete test_message_receiver_; + test_message_receiver_ = nullptr; + + // Make sure that the other end receives the OnConnectionError() + // notification. + PumpMessages(); + } + + MessageReceiver* test_message_receiver() { return test_message_receiver_; } + + ScopedMessagePipeHandle testee_endpoint() { return testee_endpoint_.Pass(); } + + private: + class TestMessageReceiver : public MessageReceiver { + public: + TestMessageReceiver(ValidationIntegrationTest* owner, + ScopedMessagePipeHandle handle) + : owner_(owner), connector_(handle.Pass()) { + connector_.set_enforce_errors_from_incoming_receiver(false); + } + ~TestMessageReceiver() override {} + + bool Accept(Message* message) override { + bool rv = connector_.Accept(message); + owner_->PumpMessages(); + return rv; + } + + public: + ValidationIntegrationTest* owner_; + mojo::internal::Connector connector_; + }; + + void PumpMessages() { loop_.RunUntilIdle(); } + + base::MessageLoop loop_; + TestMessageReceiver* test_message_receiver_; + ScopedMessagePipeHandle testee_endpoint_; +}; + +class IntegrationTestInterfaceImpl : public IntegrationTestInterface { + public: + ~IntegrationTestInterfaceImpl() override {} + + void Method0(BasicStructPtr param0, + const Method0Callback& callback) override { + callback.Run(Array<uint8_t>::New(0u)); + } +}; + +TEST_F(ValidationTest, InputParser) { + { + // The parser, as well as Append() defined above, assumes that this code is + // running on a little-endian platform. Test whether that is true. + uint16_t x = 1; + ASSERT_EQ(1, *(reinterpret_cast<char*>(&x))); + } + { + // Test empty input. + std::string input; + std::vector<uint8_t> expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + // Test input that only consists of comments and whitespaces. + std::string input = " \t // hello world \n\r \t// the answer is 42 "; + std::vector<uint8_t> expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = + "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint8_t>(0x10)); + Append(&expected, static_cast<uint16_t>(65535)); + Append(&expected, static_cast<uint32_t>(65536)); + Append(&expected, static_cast<uint64_t>(0xffffffffffffffff)); + Append(&expected, static_cast<uint8_t>(0)); + Append(&expected, static_cast<uint8_t>(0xff)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + std::vector<uint8_t> expected; + Append(&expected, -static_cast<int64_t>(0x800)); + Append(&expected, static_cast<int8_t>(-128)); + Append(&expected, static_cast<int16_t>(0)); + Append(&expected, static_cast<int32_t>(-40)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint8_t>(11)); + Append(&expected, static_cast<uint8_t>(128)); + Append(&expected, static_cast<uint8_t>(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[f]+.3e9 [d]-10.03"; + std::vector<uint8_t> expected; + Append(&expected, +.3e9f); + Append(&expected, -10.03); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint32_t>(14)); + Append(&expected, static_cast<uint8_t>(0)); + Append(&expected, static_cast<uint64_t>(9)); + Append(&expected, static_cast<uint8_t>(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "// This message has handles! \n[handles]50 [u8]2"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint64_t>(2)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 50)); + } + + // Test some failure cases. + { + const char* error_inputs[] = {"/ hello world", + "[u1]x", + "[u2]-1000", + "[u1]0x100", + "[s2]-0x8001", + "[b]1", + "[b]1111111k", + "[dist4]unmatched", + "[anchr]hello [dist8]hello", + "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", + "0 [handles]50", + nullptr}; + + for (size_t i = 0; error_inputs[i]; ++i) { + std::vector<uint8_t> expected; + if (!TestInputParser(error_inputs[i], false, expected, 0)) + ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i]; + } + } +} + +TEST_F(ValidationTest, Conformance) { + DummyMessageReceiver dummy_receiver; + mojo::internal::FilterChain validators(&dummy_receiver); + validators.Append<mojo::internal::MessageHeaderValidator>(); + validators.Append<ConformanceTestInterface::RequestValidator_>(); + + RunValidationTests("conformance_", validators.GetHead()); +} + +// This test is similar to Conformance test but its goal is specifically +// do bounds-check testing of message validation. For example we test the +// detection of off-by-one errors in method ordinals. +TEST_F(ValidationTest, BoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::internal::FilterChain validators(&dummy_receiver); + validators.Append<mojo::internal::MessageHeaderValidator>(); + validators.Append<BoundsCheckTestInterface::RequestValidator_>(); + + RunValidationTests("boundscheck_", validators.GetHead()); +} + +// This test is similar to the Conformance test but for responses. +TEST_F(ValidationTest, ResponseConformance) { + DummyMessageReceiver dummy_receiver; + mojo::internal::FilterChain validators(&dummy_receiver); + validators.Append<mojo::internal::MessageHeaderValidator>(); + validators.Append<ConformanceTestInterface::ResponseValidator_>(); + + RunValidationTests("resp_conformance_", validators.GetHead()); +} + +// This test is similar to the BoundsCheck test but for responses. +TEST_F(ValidationTest, ResponseBoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::internal::FilterChain validators(&dummy_receiver); + validators.Append<mojo::internal::MessageHeaderValidator>(); + validators.Append<BoundsCheckTestInterface::ResponseValidator_>(); + + RunValidationTests("resp_boundscheck_", validators.GetHead()); +} + +// Test that InterfacePtr<X> applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::ResponseValidator_ +TEST_F(ValidationIntegrationTest, InterfacePtr) { + IntegrationTestInterfacePtr interface_ptr = MakeProxy( + InterfacePtrInfo<IntegrationTestInterface>(testee_endpoint().Pass(), 0u)); + interface_ptr.internal_state()->router_for_testing()->EnableTestingMode(); + + RunValidationTests("integration_intf_resp", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test that Binding<X> applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::RequestValidator_ +TEST_F(ValidationIntegrationTest, Binding) { + IntegrationTestInterfaceImpl interface_impl; + Binding<IntegrationTestInterface> binding( + &interface_impl, + MakeRequest<IntegrationTestInterface>(testee_endpoint().Pass())); + binding.internal_router()->EnableTestingMode(); + + RunValidationTests("integration_intf_rqst", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test pointer validation (specifically, that the encoded offset is 32-bit) +TEST_F(ValidationTest, ValidateEncodedPointer) { + uint64_t offset; + + offset = 0ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + offset = 1ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + // offset must be <= 32-bit. + offset = std::numeric_limits<uint32_t>::max() + 1ULL; + EXPECT_FALSE(mojo::internal::ValidateEncodedPointer(&offset)); +} + +// Tests the IsValidValue() function generated for BasicEnum. +TEST(EnumValueValidationTest, BasicEnum) { + // BasicEnum can have -3,0,1,10 as possible integral values. + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(-4))); + EXPECT_TRUE(BasicEnum_IsValidValue(static_cast<BasicEnum>(-3))); + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(-2))); + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(-1))); + EXPECT_TRUE(BasicEnum_IsValidValue(static_cast<BasicEnum>(0))); + EXPECT_TRUE(BasicEnum_IsValidValue(static_cast<BasicEnum>(1))); + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(2))); + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(9))); + // In the mojom, we represent this value as hex (0xa). + EXPECT_TRUE(BasicEnum_IsValidValue(static_cast<BasicEnum>(10))); + EXPECT_FALSE(BasicEnum_IsValidValue(static_cast<BasicEnum>(11))); +} + +// Tests the IsValidValue() method generated for StructWithEnum. +TEST(EnumValueValidationTest, EnumWithin) { + // StructWithEnum::EnumWithin can have [0,4] as possible integral values. + EXPECT_FALSE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(-1))); + EXPECT_TRUE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(0))); + EXPECT_TRUE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(1))); + EXPECT_TRUE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(2))); + EXPECT_TRUE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(3))); + EXPECT_FALSE(StructWithEnum::EnumWithin_IsValidValue( + static_cast<StructWithEnum::EnumWithin>(4))); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/versioning_apptest.cc b/mojo/public/cpp/bindings/tests/versioning_apptest.cc new file mode 100644 index 0000000..a1cb37f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_apptest.cc @@ -0,0 +1,121 @@ +// Copyright 2015 The Chromium 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 "mojo/application/public/cpp/application_impl.h" +#include "mojo/application/public/cpp/application_test_base.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_client.mojom.h" + +namespace mojo { +namespace test { +namespace versioning { + +class VersioningApplicationTest : public ApplicationTestBase { + public: + VersioningApplicationTest() : ApplicationTestBase() {} + ~VersioningApplicationTest() override {} + + protected: + // ApplicationTestBase overrides. + void SetUp() override { + ApplicationTestBase::SetUp(); + + application_impl()->ConnectToService("mojo:versioning_test_service", + &database_); + } + + HumanResourceDatabasePtr database_; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(VersioningApplicationTest); +}; + +TEST_F(VersioningApplicationTest, Struct) { + // The service side uses a newer version of Employee defintion. + // The returned struct should be truncated. + EmployeePtr employee(Employee::New()); + employee->employee_id = 1; + employee->name = "Homer Simpson"; + employee->department = DEPARTMENT_DEV; + + database_->QueryEmployee(1, true, + [&employee](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) { + EXPECT_TRUE(employee->Equals(*returned_employee)); + EXPECT_FALSE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); + + // Passing a struct of older version to the service side works. + EmployeePtr new_employee(Employee::New()); + new_employee->employee_id = 2; + new_employee->name = "Marge Simpson"; + new_employee->department = DEPARTMENT_SALES; + + database_->AddEmployee(new_employee.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + database_->QueryEmployee( + 2, false, [&new_employee](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) { + EXPECT_TRUE(new_employee->Equals(*returned_employee)); + EXPECT_TRUE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); +} + +TEST_F(VersioningApplicationTest, QueryVersion) { + EXPECT_EQ(0u, database_.version()); + database_.QueryVersion([](uint32_t version) { EXPECT_EQ(1u, version); }); + database_.WaitForIncomingResponse(); + EXPECT_EQ(1u, database_.version()); +} + +TEST_F(VersioningApplicationTest, RequireVersion) { + EXPECT_EQ(0u, database_.version()); + + database_.RequireVersion(1); + EXPECT_EQ(1u, database_.version()); + database_->QueryEmployee(3, false, + [](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_FALSE(database_.encountered_error()); + + // Requiring a version higher than what the service side implements will close + // the pipe. + database_.RequireVersion(3); + EXPECT_EQ(3u, database_.version()); + database_->QueryEmployee(1, false, + [](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +TEST_F(VersioningApplicationTest, CallNonexistentMethod) { + EXPECT_EQ(0u, database_.version()); + + Array<uint8_t> new_finger_print(128); + for (size_t i = 0; i < 128; ++i) + new_finger_print[i] = i + 13; + + // Although the client side doesn't know whether the service side supports + // version 1, calling a version 1 method succeeds as long as the service side + // supports version 1. + database_->AttachFingerPrint(1, new_finger_print.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + // Calling a version 2 method (which the service side doesn't support) closes + // the pipe. + database_->ListEmployeeIds([](Array<uint64_t> ids) { EXPECT_TRUE(false); }); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +} // namespace versioning +} // namespace examples +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/versioning_test_service.cc b/mojo/public/cpp/bindings/tests/versioning_test_service.cc new file mode 100644 index 0000000..cc52595 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_test_service.cc @@ -0,0 +1,125 @@ +// Copyright 2015 The Chromium 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 <map> + +#include "mojo/application/public/cpp/application_connection.h" +#include "mojo/application/public/cpp/application_delegate.h" +#include "mojo/application/public/cpp/application_runner.h" +#include "mojo/application/public/cpp/interface_factory.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_service.mojom.h" + +namespace mojo { +namespace test { +namespace versioning { + +struct EmployeeInfo { + public: + EmployeeInfo() {} + + EmployeePtr employee; + Array<uint8_t> finger_print; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(EmployeeInfo); +}; + +class HumanResourceDatabaseImpl : public HumanResourceDatabase { + public: + explicit HumanResourceDatabaseImpl( + InterfaceRequest<HumanResourceDatabase> request) + : strong_binding_(this, request.Pass()) { + // Pretend that there is already some data in the system. + EmployeeInfo* info = new EmployeeInfo(); + employees_[1] = info; + info->employee = Employee::New(); + info->employee->employee_id = 1; + info->employee->name = "Homer Simpson"; + info->employee->department = DEPARTMENT_DEV; + info->employee->birthday = Date::New(); + info->employee->birthday->year = 1955; + info->employee->birthday->month = 5; + info->employee->birthday->day = 12; + info->finger_print.resize(1024); + for (uint32_t i = 0; i < 1024; ++i) + info->finger_print[i] = i; + } + + ~HumanResourceDatabaseImpl() override { + for (auto iter = employees_.begin(); iter != employees_.end(); ++iter) + delete iter->second; + } + + void AddEmployee(EmployeePtr employee, + const AddEmployeeCallback& callback) override { + uint64_t id = employee->employee_id; + if (employees_.find(id) == employees_.end()) + employees_[id] = new EmployeeInfo(); + employees_[id]->employee = employee.Pass(); + callback.Run(true); + } + + void QueryEmployee(uint64_t id, + bool retrieve_finger_print, + const QueryEmployeeCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(nullptr, Array<uint8_t>()); + return; + } + callback.Run(employees_[id]->employee.Clone(), + retrieve_finger_print ? employees_[id]->finger_print.Clone() + : Array<uint8_t>()); + } + + void AttachFingerPrint(uint64_t id, + Array<uint8_t> finger_print, + const AttachFingerPrintCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(false); + return; + } + employees_[id]->finger_print = finger_print.Pass(); + callback.Run(true); + } + + private: + std::map<uint64_t, EmployeeInfo*> employees_; + + StrongBinding<HumanResourceDatabase> strong_binding_; +}; + +class HumanResourceSystemServer + : public ApplicationDelegate, + public InterfaceFactory<HumanResourceDatabase> { + public: + HumanResourceSystemServer() {} + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService<HumanResourceDatabase>(this); + return true; + } + + // InterfaceFactory<HumanResourceDatabase> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<HumanResourceDatabase> request) override { + // It will be deleted automatically when the underlying pipe encounters a + // connection error. + new HumanResourceDatabaseImpl(request.Pass()); + } +}; + +} // namespace versioning +} // namespace test +} // namespace mojo + +MojoResult MojoMain(MojoHandle application_request) { + mojo::ApplicationRunner runner( + new mojo::test::versioning::HumanResourceSystemServer()); + + return runner.Run(application_request); +} diff --git a/mojo/public/cpp/bindings/type_converter.h b/mojo/public/cpp/bindings/type_converter.h new file mode 100644 index 0000000..ff94cda --- /dev/null +++ b/mojo/public/cpp/bindings/type_converter.h @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ + +namespace mojo { + +// Specialize the following class: +// template <typename T, typename U> struct TypeConverter; +// to perform type conversion for Mojom-defined structs and arrays. Here, T is +// the target type; U is the input type. +// +// Specializations should implement the following interfaces: +// namespace mojo { +// template <> +// struct TypeConverter<X, Y> { +// static X Convert(const Y& input); +// }; +// template <> +// struct TypeConverter<Y, X> { +// static Y Convert(const X& input); +// }; +// } +// +// EXAMPLE: +// +// Suppose you have the following Mojom-defined struct: +// +// module geometry { +// struct Point { +// int32 x; +// int32 y; +// }; +// } +// +// Now, imagine you wanted to write a TypeConverter specialization for +// gfx::Point. It might look like this: +// +// namespace mojo { +// template <> +// struct TypeConverter<geometry::PointPtr, gfx::Point> { +// static geometry::PointPtr Convert(const gfx::Point& input) { +// geometry::PointPtr result; +// result->x = input.x(); +// result->y = input.y(); +// return result.Pass(); +// } +// }; +// template <> +// struct TypeConverter<gfx::Point, geometry::PointPtr> { +// static gfx::Point Convert(const geometry::PointPtr& input) { +// return input ? gfx::Point(input->x, input->y) : gfx::Point(); +// } +// }; +// } +// +// With the above TypeConverter defined, it is possible to write code like this: +// +// void AcceptPoint(const geometry::PointPtr& input) { +// // With an explicit cast using the .To<> method. +// gfx::Point pt = input.To<gfx::Point>(); +// +// // With an explicit cast using the static From() method. +// geometry::PointPtr output = geometry::Point::From(pt); +// +// // Inferring the input type using the ConvertTo helper function. +// gfx::Point pt2 = ConvertTo<gfx::Point>(input); +// } +// +template <typename T, typename U> +struct TypeConverter; + +// The following specialization is useful when you are converting between +// Array<POD> and std::vector<POD>. +template <typename T> +struct TypeConverter<T, T> { + static T Convert(const T& obj) { return obj; } +}; + +// The following helper function is useful for shorthand. The compiler can infer +// the input type, so you can write: +// OutputType out = ConvertTo<OutputType>(input); +template <typename T, typename U> +inline T ConvertTo(const U& obj) { + return TypeConverter<T, U>::Convert(obj); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ diff --git a/mojo/public/cpp/environment/BUILD.gn b/mojo/public/cpp/environment/BUILD.gn new file mode 100644 index 0000000..fe3a011 --- /dev/null +++ b/mojo/public/cpp/environment/BUILD.gn @@ -0,0 +1,47 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../mojo_sdk.gni") + +mojo_sdk_source_set("environment") { + sources = [ + "async_waiter.h", + "environment.h", + "logging.h", + "task_tracker.h", + ] + + mojo_sdk_public_deps = [ "mojo/public/c/environment" ] + + mojo_sdk_deps = [ + "mojo/public/cpp/bindings:callback", + "mojo/public/cpp/system", + ] +} + +mojo_sdk_source_set("standalone") { + sources = [ + "lib/async_waiter.cc", + "lib/default_async_waiter.cc", + "lib/default_async_waiter.h", + "lib/default_logger.cc", + "lib/default_logger.h", + "lib/default_task_tracker.cc", + "lib/default_task_tracker.h", + "lib/environment.cc", + "lib/logging.cc", + "lib/scoped_task_tracking.cc", + "lib/scoped_task_tracking.h", + ] + + public_deps = [ + ":environment", + ] + + mojo_sdk_deps = [ + "mojo/public/c/environment", + "mojo/public/cpp/system", + "mojo/public/cpp/utility", + ] +} diff --git a/mojo/public/cpp/environment/async_waiter.h b/mojo/public/cpp/environment/async_waiter.h new file mode 100644 index 0000000..83b7c8a --- /dev/null +++ b/mojo/public/cpp/environment/async_waiter.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_ASYNC_WAITER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_ASYNC_WAITER_H_ + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { + +// A class that waits until a handle is ready and calls |callback| with the +// result. If the AsyncWaiter is deleted before the handle is ready, the wait is +// cancelled and the callback will not be called. +class AsyncWaiter { + public: + typedef mojo::Callback<void(MojoResult)> Callback; + + AsyncWaiter(Handle handle, + MojoHandleSignals signals, + const Callback& callback); + ~AsyncWaiter(); + + private: + static void WaitComplete(void* waiter, MojoResult result); + void WaitCompleteInternal(MojoResult result); + + const MojoAsyncWaiter* waiter_; + MojoAsyncWaitID id_; + const Callback callback_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaiter); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_ASYNC_WAITER_H_ diff --git a/mojo/public/cpp/environment/environment.h b/mojo/public/cpp/environment/environment.h new file mode 100644 index 0000000..3a54428 --- /dev/null +++ b/mojo/public/cpp/environment/environment.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ + +#include "mojo/public/cpp/system/macros.h" + +struct MojoAsyncWaiter; +struct MojoLogger; + +namespace mojo { + +struct TaskTracker; + +// Other parts of the Mojo C++ APIs use the *static* methods of this class. +// +// The "standalone" implementation of this class requires that this class (in +// the lib/ subdirectory) be instantiated (and remain so) while using the Mojo +// C++ APIs. I.e., the static methods depend on things set up by the constructor +// and torn down by the destructor. +// +// Other implementations may not have this requirement. +class Environment { + public: + Environment(); + // This constructor allows the standard implementations to be overridden (set + // a parameter to null to get the standard implementation). + Environment(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger, + const TaskTracker* default_task_tracker); + ~Environment(); + + static const MojoAsyncWaiter* GetDefaultAsyncWaiter(); + static const MojoLogger* GetDefaultLogger(); + static const TaskTracker* GetDefaultTaskTracker(); + + // These instantiate and destroy an environment-specific run loop for the + // current thread, allowing |GetDefaultAsyncWaiter()| to be used. (The run + // loop itself should be accessible via thread-local storage, using methods + // specific to the run loop implementation.) Creating and destroying nested + // run loops is not supported. + static void InstantiateDefaultRunLoop(); + static void DestroyDefaultRunLoop(); + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(Environment); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ diff --git a/mojo/public/cpp/environment/lib/async_waiter.cc b/mojo/public/cpp/environment/lib/async_waiter.cc new file mode 100644 index 0000000..599a649 --- /dev/null +++ b/mojo/public/cpp/environment/lib/async_waiter.cc @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/async_waiter.h" + +namespace mojo { + +AsyncWaiter::AsyncWaiter(Handle handle, + MojoHandleSignals signals, + const Callback& callback) + : waiter_(Environment::GetDefaultAsyncWaiter()), + id_(0), + callback_(callback) { + id_ = waiter_->AsyncWait(handle.value(), signals, MOJO_DEADLINE_INDEFINITE, + &AsyncWaiter::WaitComplete, this); +} + +AsyncWaiter::~AsyncWaiter() { + if (id_) + waiter_->CancelWait(id_); +} + +// static +void AsyncWaiter::WaitComplete(void* waiter, MojoResult result) { + static_cast<AsyncWaiter*>(waiter)->WaitCompleteInternal(result); +} + +void AsyncWaiter::WaitCompleteInternal(MojoResult result) { + id_ = 0; + callback_.Run(result); +} + +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/default_async_waiter.cc b/mojo/public/cpp/environment/lib/default_async_waiter.cc new file mode 100644 index 0000000..4a588a9 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_async_waiter.cc @@ -0,0 +1,85 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/lib/default_async_waiter.h" + +#include <assert.h> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/public/cpp/utility/run_loop_handler.h" + +namespace mojo { + +namespace { + +// RunLoopHandler implementation used for a request to AsyncWait(). There are +// two ways RunLoopHandlerImpl is deleted: +// . when the handle is ready (or errored). +// . when CancelWait() is invoked. +class RunLoopHandlerImpl : public RunLoopHandler { + public: + RunLoopHandlerImpl(const Handle& handle, + MojoAsyncWaitCallback callback, + void* closure) + : handle_(handle), callback_(callback), closure_(closure) {} + + ~RunLoopHandlerImpl() override { RunLoop::current()->RemoveHandler(handle_); } + + // RunLoopHandler: + void OnHandleReady(const Handle& handle) override { + NotifyCallback(MOJO_RESULT_OK); + } + + void OnHandleError(const Handle& handle, MojoResult result) override { + NotifyCallback(result); + } + + private: + void NotifyCallback(MojoResult result) { + // Delete this to unregister the handle. That way if the callback + // reregisters everything is ok. + MojoAsyncWaitCallback callback = callback_; + void* closure = closure_; + delete this; + + callback(closure, result); + } + + const Handle handle_; + MojoAsyncWaitCallback callback_; + void* closure_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoopHandlerImpl); +}; + +MojoAsyncWaitID AsyncWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline, + MojoAsyncWaitCallback callback, + void* closure) { + RunLoop* run_loop = RunLoop::current(); + assert(run_loop); + + // |run_loop_handler| is destroyed either when the handle is ready or if + // CancelWait is invoked. + RunLoopHandlerImpl* run_loop_handler = + new RunLoopHandlerImpl(Handle(handle), callback, closure); + run_loop->AddHandler(run_loop_handler, Handle(handle), signals, deadline); + return reinterpret_cast<MojoAsyncWaitID>(run_loop_handler); +} + +void CancelWait(MojoAsyncWaitID wait_id) { + delete reinterpret_cast<RunLoopHandlerImpl*>(wait_id); +} + +} // namespace + +namespace internal { + +const MojoAsyncWaiter kDefaultAsyncWaiter = {AsyncWait, CancelWait}; + +} // namespace internal + +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/default_async_waiter.h b/mojo/public/cpp/environment/lib/default_async_waiter.h new file mode 100644 index 0000000..49ce233 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_async_waiter.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ + +struct MojoAsyncWaiter; + +namespace mojo { +namespace internal { + +extern const MojoAsyncWaiter kDefaultAsyncWaiter; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ diff --git a/mojo/public/cpp/environment/lib/default_logger.cc b/mojo/public/cpp/environment/lib/default_logger.cc new file mode 100644 index 0000000..ba787d1 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_logger.cc @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/lib/default_logger.h" + +#include <stdio.h> +#include <stdlib.h> // For |abort()|. + +#include <algorithm> + +#include "mojo/public/c/environment/logger.h" + +namespace mojo { + +namespace { + +MojoLogLevel g_minimum_log_level = MOJO_LOG_LEVEL_INFO; + +const char* GetLogLevelString(MojoLogLevel log_level) { + if (log_level <= MOJO_LOG_LEVEL_VERBOSE - 3) + return "VERBOSE4+"; + switch (log_level) { + case MOJO_LOG_LEVEL_VERBOSE - 2: + return "VERBOSE3"; + case MOJO_LOG_LEVEL_VERBOSE - 1: + return "VERBOSE2"; + case MOJO_LOG_LEVEL_VERBOSE: + return "VERBOSE1"; + case MOJO_LOG_LEVEL_INFO: + return "INFO"; + case MOJO_LOG_LEVEL_WARNING: + return "WARNING"; + case MOJO_LOG_LEVEL_ERROR: + return "ERROR"; + } + // Consider everything higher to be fatal. + return "FATAL"; +} + +void LogMessage(MojoLogLevel log_level, + const char* source_file, + uint32_t source_line, + const char* message) { + if (log_level < g_minimum_log_level) + return; + + // TODO(vtl): Add timestamp also? + if (source_file) { + fprintf(stderr, "%s: %s(%u): %s\n", GetLogLevelString(log_level), + source_file, static_cast<unsigned>(source_line), message); + } else { + fprintf(stderr, "%s: %s\n", GetLogLevelString(log_level), message); + } + if (log_level >= MOJO_LOG_LEVEL_FATAL) + abort(); +} + +MojoLogLevel GetMinimumLogLevel() { + return g_minimum_log_level; +} + +void SetMinimumLogLevel(MojoLogLevel minimum_log_level) { + g_minimum_log_level = std::min(minimum_log_level, MOJO_LOG_LEVEL_FATAL); +} + +} // namespace + +namespace internal { + +const MojoLogger kDefaultLogger = {LogMessage, + GetMinimumLogLevel, + SetMinimumLogLevel}; + +} // namespace internal + +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/default_logger.h b/mojo/public/cpp/environment/lib/default_logger.h new file mode 100644 index 0000000..4db3233 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_logger.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ + +struct MojoLogger; + +namespace mojo { +namespace internal { + +extern const MojoLogger kDefaultLogger; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ diff --git a/mojo/public/cpp/environment/lib/default_task_tracker.cc b/mojo/public/cpp/environment/lib/default_task_tracker.cc new file mode 100644 index 0000000..a99f1c7 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_task_tracker.cc @@ -0,0 +1,40 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/environment/lib/default_task_tracker.h" + +#include "mojo/public/cpp/environment/task_tracker.h" + +namespace mojo { + +namespace { + +// +// The standalone task tracker does nothing. +// + +TaskTrackingId StartTracking(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter) { + return TaskTrackingId(0); +} + +void EndTracking(const TaskTrackingId id) { +} + +void SetEnabled(bool enabled) { +} + +} // namespace + +namespace internal { + +const TaskTracker kDefaultTaskTracker = {&StartTracking, + &EndTracking, + &SetEnabled}; + +} // namespace internal + +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/default_task_tracker.h b/mojo/public/cpp/environment/lib/default_task_tracker.h new file mode 100644 index 0000000..7a0c064 --- /dev/null +++ b/mojo/public/cpp/environment/lib/default_task_tracker.h @@ -0,0 +1,19 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_TASK_TRACKER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_TASK_TRACKER_H_ + +namespace mojo { + +struct TaskTracker; + +namespace internal { + +extern const TaskTracker kDefaultTaskTracker; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_TASK_TRACKER_H_ diff --git a/mojo/public/cpp/environment/lib/environment.cc b/mojo/public/cpp/environment/lib/environment.cc new file mode 100644 index 0000000..8c7c930 --- /dev/null +++ b/mojo/public/cpp/environment/lib/environment.cc @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/environment.h" + +#include <assert.h> + +#include "mojo/public/c/environment/logger.h" +#include "mojo/public/cpp/environment/lib/default_async_waiter.h" +#include "mojo/public/cpp/environment/lib/default_logger.h" +#include "mojo/public/cpp/environment/lib/default_task_tracker.h" +#include "mojo/public/cpp/utility/run_loop.h" + +namespace mojo { + +namespace { + +const MojoAsyncWaiter* g_default_async_waiter = nullptr; +const MojoLogger* g_default_logger = nullptr; +const TaskTracker* g_default_task_tracker = nullptr; + +void Init(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger, + const TaskTracker* default_task_tracker) { + g_default_async_waiter = default_async_waiter + ? default_async_waiter + : &internal::kDefaultAsyncWaiter; + g_default_logger = + default_logger ? default_logger : &internal::kDefaultLogger; + + g_default_task_tracker = default_task_tracker + ? default_task_tracker + : &internal::kDefaultTaskTracker; + + RunLoop::SetUp(); +} + +} // namespace + +Environment::Environment() { + Init(nullptr, nullptr, nullptr); +} + +Environment::Environment(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger, + const TaskTracker* default_task_tracker) { + Init(default_async_waiter, default_logger, default_task_tracker); +} + +Environment::~Environment() { + RunLoop::TearDown(); + + // TODO(vtl): Maybe we should allow nesting, and restore previous default + // async waiters and loggers? + g_default_async_waiter = nullptr; + g_default_logger = nullptr; +} + +// static +const MojoAsyncWaiter* Environment::GetDefaultAsyncWaiter() { + assert(g_default_async_waiter); // Fails if not "inside" |Environment|. + return g_default_async_waiter; +} + +// static +const MojoLogger* Environment::GetDefaultLogger() { + assert(g_default_logger); // Fails if not "inside" |Environment|. + return g_default_logger; +} + +// static +const TaskTracker* Environment::GetDefaultTaskTracker() { + return g_default_task_tracker; +} + +// static +void Environment::InstantiateDefaultRunLoop() { + assert(!RunLoop::current()); + // Not leaked: accessible from |RunLoop::current()|. + RunLoop* run_loop = new RunLoop(); + MOJO_ALLOW_UNUSED_LOCAL(run_loop); + assert(run_loop == RunLoop::current()); +} + +// static +void Environment::DestroyDefaultRunLoop() { + assert(RunLoop::current()); + delete RunLoop::current(); + assert(!RunLoop::current()); +} + +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/logging.cc b/mojo/public/cpp/environment/lib/logging.cc new file mode 100644 index 0000000..57f1892 --- /dev/null +++ b/mojo/public/cpp/environment/lib/logging.cc @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/logging.h" + +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { +namespace internal { + +namespace { + +// Gets a pointer to the filename portion of |s|. Assumes that the filename +// follows the last slash or backslash in |s|, or is |s| if no slash or +// backslash is present. +// +// E.g., a pointer to "foo.cc" is returned for the following inputs: "foo.cc", +// "./foo.cc", ".\foo.cc", "/absolute/path/to/foo.cc", +// "relative/path/to/foo.cc", "C:\absolute\path\to\foo.cc", etc. +const char* GetFilename(const char* s) { + const char* rv = s; + while (*s) { + if (*s == '/' || *s == '\\') + rv = s + 1; + s++; + } + return rv; +} + +} // namespace + +// TODO(vtl): Maybe we should preserve the full path and strip it out at a +// different level instead? +LogMessage::LogMessage(MojoLogLevel log_level, const char* file, int line) + : log_level_(log_level), file_(GetFilename(file)), line_(line) { +} + +LogMessage::~LogMessage() { + Environment::GetDefaultLogger()->LogMessage( + log_level_, file_, static_cast<uint32_t>(line_), stream_.str().c_str()); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/scoped_task_tracking.cc b/mojo/public/cpp/environment/lib/scoped_task_tracking.cc new file mode 100644 index 0000000..9e253d5 --- /dev/null +++ b/mojo/public/cpp/environment/lib/scoped_task_tracking.cc @@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium 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 "mojo/public/cpp/environment/lib/scoped_task_tracking.h" + +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { +namespace internal { + +ScopedTaskTracking::ScopedTaskTracking(const char* function_name, + const char* file_name, + int line, + const void* program_counter) + : id_(Environment::GetDefaultTaskTracker()->StartTracking( + function_name, + file_name, + line, + program_counter)) { +} + +ScopedTaskTracking::ScopedTaskTracking(const char* function_name, + const char* file_name, + int line) + : id_(Environment::GetDefaultTaskTracker()->StartTracking(function_name, + file_name, + line, + nullptr)) { +} + +ScopedTaskTracking::~ScopedTaskTracking() { + Environment::GetDefaultTaskTracker()->EndTracking(id_); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/environment/lib/scoped_task_tracking.h b/mojo/public/cpp/environment/lib/scoped_task_tracking.h new file mode 100644 index 0000000..b6e32c1 --- /dev/null +++ b/mojo/public/cpp/environment/lib/scoped_task_tracking.h @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_SCOPED_TASK_TRACKING_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_SCOPED_TASK_TRACKING_H_ + +#include "mojo/public/cpp/environment/task_tracker.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// An RAII wrapper for |TaskTrackingId|. +class ScopedTaskTracking { + public: + ScopedTaskTracking(const char* function_name, + const char* file_name, + int line, + const void* program_counter); + ScopedTaskTracking(const char* function_name, + const char* file_name, + int line); + ~ScopedTaskTracking(); + + private: + TaskTrackingId id_; + MOJO_DISALLOW_COPY_AND_ASSIGN(ScopedTaskTracking); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_SCOPED_TASK_TRACKING_H_ diff --git a/mojo/public/cpp/environment/logging.h b/mojo/public/cpp/environment/logging.h new file mode 100644 index 0000000..bfc5c13 --- /dev/null +++ b/mojo/public/cpp/environment/logging.h @@ -0,0 +1,90 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Logging macros, similar to Chromium's base/logging.h, except with |MOJO_| +// prefixes and missing some features (notably |CHECK_EQ()|, etc.). + +// TODO(vtl): It's weird that this is in the environment directory, since its +// implementation (in environment/lib) is meant to be used by any implementation +// of the environment. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ + +#include <sstream> + +#include "mojo/public/c/environment/logger.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +#define MOJO_LOG_STREAM(level) \ + ::mojo::internal::LogMessage(MOJO_LOG_LEVEL_##level, __FILE__, __LINE__) \ + .stream() + +#define MOJO_LAZY_LOG_STREAM(level, condition) \ + !(condition) ? (void)0 \ + : ::mojo::internal::VoidifyOstream() & MOJO_LOG_STREAM(level) + +#define MOJO_SHOULD_LOG(level) \ + (MOJO_LOG_LEVEL_##level >= \ + ::mojo::Environment::GetDefaultLogger()->GetMinimumLogLevel()) + +#define MOJO_LOG(level) MOJO_LAZY_LOG_STREAM(level, MOJO_SHOULD_LOG(level)) + +#define MOJO_LOG_IF(level, condition) \ + MOJO_LAZY_LOG_STREAM(level, MOJO_SHOULD_LOG(level) && (condition)) + +#define MOJO_CHECK(condition) \ + MOJO_LAZY_LOG_STREAM(FATAL, !(condition)) << "Check failed: " #condition "." \ + " " + +// Note: For non-debug builds, |MOJO_DLOG_IF()| *eliminates* (i.e., doesn't +// compile) the condition, whereas |MOJO_DCHECK()| "neuters" the condition +// (i.e., compiles, but doesn't evaluate). +#ifdef NDEBUG +#define MOJO_DLOG(level) MOJO_LAZY_LOG_STREAM(level, false) +#define MOJO_DLOG_IF(level, condition) MOJO_LAZY_LOG_STREAM(level, false) +#else +#define MOJO_DLOG(level) MOJO_LOG(level) +#define MOJO_DLOG_IF(level, condition) MOJO_LOG_IF(level, condition) +#endif // NDEBUG + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +#define MOJO_DCHECK(condition) \ + MOJO_LAZY_LOG_STREAM(FATAL, false ? !(condition) : false) +#else +#define MOJO_DCHECK(condition) MOJO_CHECK(condition) +#endif // NDEBUG && !defined(DCHECK_ALWAYS_ON) + +#define MOJO_NOTREACHED() MOJO_DCHECK(false) + +namespace mojo { +namespace internal { + +class LogMessage { + public: + LogMessage(MojoLogLevel log_level, const char* file, int line); + ~LogMessage(); + + std::ostream& stream() { return stream_; } + + private: + const MojoLogLevel log_level_; + const char* const file_; + const int line_; + std::ostringstream stream_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Used to ignore a stream. +struct VoidifyOstream { + // Use & since it has precedence lower than << but higher than ?:. + void operator&(std::ostream&) {} +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ diff --git a/mojo/public/cpp/environment/task_tracker.h b/mojo/public/cpp/environment/task_tracker.h new file mode 100644 index 0000000..d6d3020 --- /dev/null +++ b/mojo/public/cpp/environment/task_tracker.h @@ -0,0 +1,30 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_TASK_TRACKER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_TASK_TRACKER_H_ + +#include <stdint.h> + +namespace mojo { + +typedef intptr_t TaskTrackingId; + +// Interface for wiring task-level profiling. This API is mainly used by the +// generated interface implementation. +struct TaskTracker { + // Start tracking. The returned id must be reclaimed through |EndTracking()|. + TaskTrackingId (*StartTracking)(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter); + // Finish tracking. The |id| is one that is returned from |StartTracking()|. + void (*EndTracking)(const TaskTrackingId id); + // Enable or disable tracking. It is disabled by default. + void (*SetEnabled)(bool enabled); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_TASK_TRACKER_H_ diff --git a/mojo/public/cpp/environment/tests/BUILD.gn b/mojo/public/cpp/environment/tests/BUILD.gn new file mode 100644 index 0000000..10fd056 --- /dev/null +++ b/mojo/public/cpp/environment/tests/BUILD.gn @@ -0,0 +1,29 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../../mojo_sdk.gni") + +mojo_sdk_source_set("tests") { + testonly = true + + sources = [ + "async_wait_unittest.cc", + "async_waiter_unittest.cc", + "logger_unittest.cc", + "logging_unittest.cc", + ] + + deps = [ + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/c/environment", + "mojo/public/cpp/bindings:callback", + "mojo/public/cpp/environment:standalone", + "mojo/public/cpp/system", + "mojo/public/cpp/test_support:test_utils", + "mojo/public/cpp/utility", + ] +} diff --git a/mojo/public/cpp/environment/tests/async_wait_unittest.cc b/mojo/public/cpp/environment/tests/async_wait_unittest.cc new file mode 100644 index 0000000..83c5ca0 --- /dev/null +++ b/mojo/public/cpp/environment/tests/async_wait_unittest.cc @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium 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 <string> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class TestAsyncWaitCallback { + public: + TestAsyncWaitCallback() : result_count_(0), last_result_(MOJO_RESULT_OK) {} + ~TestAsyncWaitCallback() {} + + int result_count() const { return result_count_; } + + MojoResult last_result() const { return last_result_; } + + // MojoAsyncWaitCallback: + static void OnHandleReady(void* closure, MojoResult result) { + TestAsyncWaitCallback* self = static_cast<TestAsyncWaitCallback*>(closure); + self->result_count_++; + self->last_result_ = result; + } + + private: + int result_count_; + MojoResult last_result_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestAsyncWaitCallback); +}; + +MojoAsyncWaitID CallAsyncWait(const Handle& handle, + MojoHandleSignals signals, + TestAsyncWaitCallback* callback) { + return Environment::GetDefaultAsyncWaiter()->AsyncWait( + handle.value(), + signals, + MOJO_DEADLINE_INDEFINITE, + &TestAsyncWaitCallback::OnHandleReady, + callback); +} + +void CallCancelWait(MojoAsyncWaitID wait_id) { + Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id); +} + +class AsyncWaitTest : public testing::Test { + public: + AsyncWaitTest() {} + + private: + Environment environment_; + RunLoop run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaitTest); +}; + +// Verifies AsyncWaitCallback is notified when pipe is ready. +TEST_F(AsyncWaitTest, CallbackNotified) { + TestAsyncWaitCallback callback; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + CallAsyncWait( + test_pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, &callback); + RunLoop::current()->Run(); + EXPECT_EQ(1, callback.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback.last_result()); +} + +// Verifies 2 AsyncWaitCallbacks are notified when there pipes are ready. +TEST_F(AsyncWaitTest, TwoCallbacksNotified) { + TestAsyncWaitCallback callback1; + TestAsyncWaitCallback callback2; + MessagePipe test_pipe1; + MessagePipe test_pipe2; + EXPECT_TRUE(test::WriteTextMessage(test_pipe1.handle1.get(), std::string())); + EXPECT_TRUE(test::WriteTextMessage(test_pipe2.handle1.get(), std::string())); + + CallAsyncWait( + test_pipe1.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, &callback1); + CallAsyncWait( + test_pipe2.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, &callback2); + + RunLoop::current()->Run(); + EXPECT_EQ(1, callback1.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback1.last_result()); + EXPECT_EQ(1, callback2.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback2.last_result()); +} + +// Verifies cancel works. +TEST_F(AsyncWaitTest, CancelCallback) { + TestAsyncWaitCallback callback; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + CallCancelWait(CallAsyncWait( + test_pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, &callback)); + RunLoop::current()->Run(); + EXPECT_EQ(0, callback.result_count()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/environment/tests/async_waiter_unittest.cc b/mojo/public/cpp/environment/tests/async_waiter_unittest.cc new file mode 100644 index 0000000..1c1c2bf --- /dev/null +++ b/mojo/public/cpp/environment/tests/async_waiter_unittest.cc @@ -0,0 +1,107 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/environment/async_waiter.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class TestAsyncWaitCallback { + public: + TestAsyncWaitCallback() : result_count_(0), last_result_(MOJO_RESULT_OK) {} + ~TestAsyncWaitCallback() {} + + int result_count() const { return result_count_; } + + MojoResult last_result() const { return last_result_; } + + void OnHandleReady(MojoResult result) { + result_count_++; + last_result_ = result; + } + + private: + int result_count_; + MojoResult last_result_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestAsyncWaitCallback); +}; + +// Manual code to create a callback since we don't have mojo::Bind yet. +class ManualCallback { + public: + explicit ManualCallback(TestAsyncWaitCallback* callback) + : callback_(callback) {} + + void Run(MojoResult result) const { callback_->OnHandleReady(result); } + + private: + TestAsyncWaitCallback* callback_; +}; + +class AsyncWaiterTest : public testing::Test { + public: + AsyncWaiterTest() {} + + private: + Environment environment_; + RunLoop run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaiterTest); +}; + +// Verifies AsyncWaitCallback is notified when pipe is ready. +TEST_F(AsyncWaiterTest, CallbackNotified) { + TestAsyncWaitCallback callback; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + AsyncWaiter waiter(test_pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, + ManualCallback(&callback)); + RunLoop::current()->Run(); + EXPECT_EQ(1, callback.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback.last_result()); +} + +// Verifies 2 AsyncWaitCallbacks are notified when there pipes are ready. +TEST_F(AsyncWaiterTest, TwoCallbacksNotified) { + TestAsyncWaitCallback callback1; + TestAsyncWaitCallback callback2; + MessagePipe test_pipe1; + MessagePipe test_pipe2; + EXPECT_TRUE(test::WriteTextMessage(test_pipe1.handle1.get(), std::string())); + EXPECT_TRUE(test::WriteTextMessage(test_pipe2.handle1.get(), std::string())); + + AsyncWaiter waiter1(test_pipe1.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, + ManualCallback(&callback1)); + AsyncWaiter waiter2(test_pipe2.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, + ManualCallback(&callback2)); + + RunLoop::current()->Run(); + EXPECT_EQ(1, callback1.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback1.last_result()); + EXPECT_EQ(1, callback2.result_count()); + EXPECT_EQ(MOJO_RESULT_OK, callback2.last_result()); +} + +// Verifies cancel works. +TEST_F(AsyncWaiterTest, CancelCallback) { + TestAsyncWaitCallback callback; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + { + AsyncWaiter waiter(test_pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, + ManualCallback(&callback)); + } + RunLoop::current()->Run(); + EXPECT_EQ(0, callback.result_count()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/environment/tests/logger_unittest.cc b/mojo/public/cpp/environment/tests/logger_unittest.cc new file mode 100644 index 0000000..8ca5538 --- /dev/null +++ b/mojo/public/cpp/environment/tests/logger_unittest.cc @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium 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 "mojo/public/c/environment/logger.h" +#include "mojo/public/cpp/environment/environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +TEST(LoggerTest, Basic) { + const char kPath[] = "/fake/path/to/file.cc"; + + Environment environment; + const MojoLogger* const logger = Environment::GetDefaultLogger(); + + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE - 1, kPath, 123, + "Logged at VERBOSE-1 level"); + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE, kPath, 123, + "Logged at VERBOSE level"); + logger->LogMessage(MOJO_LOG_LEVEL_INFO, kPath, 123, "Logged at INFO level"); + logger->LogMessage(MOJO_LOG_LEVEL_WARNING, kPath, 123, + "Logged at WARNING level"); + logger->LogMessage(MOJO_LOG_LEVEL_ERROR, kPath, 123, "Logged at ERROR level"); + + // This should kill us: + EXPECT_DEATH_IF_SUPPORTED({ + logger->LogMessage(MOJO_LOG_LEVEL_FATAL, kPath, 123, + "Logged at FATAL level"); + }, ""); +} + +TEST(LoggerTest, LogLevels) { + const char kPath[] = "/fake/path/to/file.cc"; + + Environment environment; + const MojoLogger* const logger = Environment::GetDefaultLogger(); + + for (MojoLogLevel log_level = MOJO_LOG_LEVEL_VERBOSE - 1; + log_level <= MOJO_LOG_LEVEL_FATAL + 1; + log_level++) { + logger->SetMinimumLogLevel(log_level); + + if (log_level <= MOJO_LOG_LEVEL_FATAL) + EXPECT_EQ(log_level, logger->GetMinimumLogLevel()); + else + EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, logger->GetMinimumLogLevel()); + + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE - 1, kPath, 123, + "Logged at VERBOSE-1 level"); + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE, kPath, 123, + "Logged at VERBOSE level"); + logger->LogMessage(MOJO_LOG_LEVEL_INFO, kPath, 123, "Logged at INFO level"); + logger->LogMessage(MOJO_LOG_LEVEL_WARNING, kPath, 123, + "Logged at WARNING level"); + logger->LogMessage(MOJO_LOG_LEVEL_ERROR, kPath, 123, + "Logged at ERROR level"); + + // This should kill us: + EXPECT_DEATH_IF_SUPPORTED({ + logger->LogMessage(MOJO_LOG_LEVEL_FATAL, kPath, 123, + "Logged at FATAL level"); + }, ""); + } +} + +TEST(LoggerTest, NoFile) { + Environment environment; + const MojoLogger* const logger = Environment::GetDefaultLogger(); + + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE - 1, nullptr, 0, + "Logged at VERBOSE-1 level"); + logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE, nullptr, 0, + "Logged at VERBOSE level"); + logger->LogMessage(MOJO_LOG_LEVEL_INFO, nullptr, 0, "Logged at INFO level"); + logger->LogMessage(MOJO_LOG_LEVEL_WARNING, nullptr, 0, + "Logged at WARNING level"); + logger->LogMessage(MOJO_LOG_LEVEL_ERROR, nullptr, 0, "Logged at ERROR level"); + + // This should kill us: + EXPECT_DEATH_IF_SUPPORTED({ + logger->LogMessage(MOJO_LOG_LEVEL_FATAL, nullptr, 0, + "Logged at FATAL level"); + }, ""); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/environment/tests/logging_unittest.cc b/mojo/public/cpp/environment/tests/logging_unittest.cc new file mode 100644 index 0000000..489888c --- /dev/null +++ b/mojo/public/cpp/environment/tests/logging_unittest.cc @@ -0,0 +1,521 @@ +// Copyright 2014 The Chromium 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 <stdlib.h> + +#include <sstream> +#include <string> + +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/environment/logging.h" +#include "mojo/public/cpp/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +// The current logging system strips the path, so we need our filename. +const char kOurFilename[] = "logging_unittest.cc"; + +class PtrToMemberHelper { + public: + int member; +}; + +bool DcheckTestHelper(bool* was_called) { + *was_called = true; + return false; +} + +class LoggingTest : public testing::Test { + public: + LoggingTest() : environment_(nullptr, &kMockLogger, nullptr) { + minimum_log_level_ = MOJO_LOG_LEVEL_INFO; + ResetMockLogger(); + } + ~LoggingTest() override {} + + protected: + // Note: Does not reset |minimum_log_level_|. + static void ResetMockLogger() { + log_message_was_called_ = false; + last_log_level_ = MOJO_LOG_LEVEL_INFO; + last_source_file_.clear(); + last_source_line_ = 0; + last_message_.clear(); + } + + // A function returning |bool| that shouldn't be called. + static bool NotCalledCondition() { + not_called_condition_was_called_ = true; + return false; + } + + static bool log_message_was_called() { return log_message_was_called_; } + static MojoLogLevel last_log_level() { return last_log_level_; } + static const std::string& last_source_file() { return last_source_file_; } + static uint32_t last_source_line() { return last_source_line_; } + static const std::string& last_message() { return last_message_; } + static bool not_called_condition_was_called() { + return not_called_condition_was_called_; + } + + private: + // Note: We record calls even if |log_level| is below |minimum_log_level_| + // (since the macros should mostly avoid this, and we want to be able to check + // that they do). + static void MockLogMessage(MojoLogLevel log_level, + const char* source_file, + uint32_t source_line, + const char* message) { + log_message_was_called_ = true; + last_log_level_ = log_level; + last_source_file_ = source_file; + last_source_line_ = source_line; + last_message_ = message; + } + + static MojoLogLevel MockGetMinimumLogLevel() { return minimum_log_level_; } + + static void MockSetMinimumLogLevel(MojoLogLevel minimum_log_level) { + minimum_log_level_ = minimum_log_level; + } + + Environment environment_; + + static const MojoLogger kMockLogger; + static MojoLogLevel minimum_log_level_; + static bool log_message_was_called_; + static MojoLogLevel last_log_level_; + static std::string last_source_file_; + static uint32_t last_source_line_; + static std::string last_message_; + static bool not_called_condition_was_called_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(LoggingTest); +}; + +// static +const MojoLogger LoggingTest::kMockLogger = { + &LoggingTest::MockLogMessage, + &LoggingTest::MockGetMinimumLogLevel, + &LoggingTest::MockSetMinimumLogLevel}; + +// static +MojoLogLevel LoggingTest::minimum_log_level_ = MOJO_LOG_LEVEL_INFO; + +// static +bool LoggingTest::log_message_was_called_ = MOJO_LOG_LEVEL_INFO; + +// static +MojoLogLevel LoggingTest::last_log_level_ = MOJO_LOG_LEVEL_INFO; + +// static +std::string LoggingTest::last_source_file_; + +// static +uint32_t LoggingTest::last_source_line_ = 0; + +// static +std::string LoggingTest::last_message_; + +// static +bool LoggingTest::not_called_condition_was_called_ = false; + +TEST_F(LoggingTest, InternalLogMessage) { + internal::LogMessage(MOJO_LOG_LEVEL_INFO, "foo.cc", 123).stream() << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_WARNING, "./path/to/foo.cc", 123).stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_WARNING, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_ERROR, "/path/to/foo.cc", 123).stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_FATAL, "path/to/foo.cc", 123).stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_VERBOSE, ".\\xy\\foo.cc", 123).stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_VERBOSE - 1, "xy\\foo.cc", 123).stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE - 1, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_VERBOSE - 9, "C:\\xy\\foo.cc", 123) + .stream() + << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE - 9, last_log_level()); + EXPECT_EQ("foo.cc", last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); + + ResetMockLogger(); + + internal::LogMessage(MOJO_LOG_LEVEL_INFO, __FILE__, 123).stream() << "hello " + << "world"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(123u, last_source_line()); + EXPECT_EQ("hello world", last_message()); +} + +TEST_F(LoggingTest, LogStream) { + MOJO_LOG_STREAM(INFO) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); + + ResetMockLogger(); + + MOJO_LOG_STREAM(ERROR) << "hi " << 123; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hi 123", last_message()); +} + +TEST_F(LoggingTest, LazyLogStream) { + MOJO_LAZY_LOG_STREAM(INFO, true) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); + + ResetMockLogger(); + + MOJO_LAZY_LOG_STREAM(ERROR, true) << "hi " << 123; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hi 123", last_message()); + + ResetMockLogger(); + + MOJO_LAZY_LOG_STREAM(INFO, false) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LAZY_LOG_STREAM(FATAL, false) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + PtrToMemberHelper helper; + helper.member = 1; + int PtrToMemberHelper::*member_ptr = &PtrToMemberHelper::member; + + // This probably fails to compile if we forget to parenthesize the condition + // in the macro (.* has lower precedence than !, which can't apply to + // |helper|). + MOJO_LAZY_LOG_STREAM(ERROR, helper.*member_ptr == 1) << "hello"; + EXPECT_TRUE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LAZY_LOG_STREAM(WARNING, helper.*member_ptr == 0) << "hello"; + EXPECT_FALSE(log_message_was_called()); +} + +TEST_F(LoggingTest, ShouldLog) { + // We start at |MOJO_LOG_LEVEL_INFO|. + EXPECT_FALSE(MOJO_SHOULD_LOG(VERBOSE)); + EXPECT_TRUE(MOJO_SHOULD_LOG(INFO)); + EXPECT_TRUE(MOJO_SHOULD_LOG(WARNING)); + EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR)); + EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL)); + + Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR); + EXPECT_FALSE(MOJO_SHOULD_LOG(VERBOSE)); + EXPECT_FALSE(MOJO_SHOULD_LOG(INFO)); + EXPECT_FALSE(MOJO_SHOULD_LOG(WARNING)); + EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR)); + EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL)); + + Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_VERBOSE - + 1); + EXPECT_TRUE(MOJO_SHOULD_LOG(VERBOSE)); + EXPECT_TRUE(MOJO_SHOULD_LOG(INFO)); + EXPECT_TRUE(MOJO_SHOULD_LOG(WARNING)); + EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR)); + EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL)); +} + +TEST_F(LoggingTest, Log) { + // We start at |MOJO_LOG_LEVEL_INFO|. + MOJO_LOG(VERBOSE) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG(INFO) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); + + ResetMockLogger(); + + MOJO_LOG(ERROR) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); + + ResetMockLogger(); + + Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR); + + MOJO_LOG(VERBOSE) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG(INFO) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG(ERROR) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); +} + +TEST_F(LoggingTest, LogIf) { + // We start at |MOJO_LOG_LEVEL_INFO|. + MOJO_LOG_IF(VERBOSE, true) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG_IF(VERBOSE, false) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR); + + bool x = true; + // Also try to make sure that we parenthesize the condition properly. + MOJO_LOG_IF(INFO, false || x) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG_IF(INFO, 0 != 1) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG_IF(WARNING, 1 + 1 == 2) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_LOG_IF(ERROR, 1 * 2 == 2) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("hello", last_message()); + + ResetMockLogger(); + + MOJO_LOG_IF(FATAL, 1 * 2 == 3) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + // |MOJO_LOG_IF()| shouldn't evaluate its condition if the level is below the + // minimum. + MOJO_LOG_IF(INFO, NotCalledCondition()) << "hello"; + EXPECT_FALSE(not_called_condition_was_called()); + EXPECT_FALSE(log_message_was_called()); +} + +TEST_F(LoggingTest, Check) { + MOJO_CHECK(true) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + PtrToMemberHelper helper; + helper.member = 0; + int PtrToMemberHelper::*member_ptr = &PtrToMemberHelper::member; + + // Also try to make sure that we parenthesize the condition properly. + MOJO_CHECK(helper.*member_ptr == 1) << "hello"; + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 4), last_source_line()); + EXPECT_EQ("Check failed: helper.*member_ptr == 1. hello", last_message()); + + ResetMockLogger(); + + // Also test a "naked" |MOJO_CHECK()|s. + MOJO_CHECK(1 + 2 == 3); + EXPECT_FALSE(log_message_was_called()); +} + +TEST_F(LoggingTest, Dlog) { + // We start at |MOJO_LOG_LEVEL_INFO|. + MOJO_DLOG(VERBOSE) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_DLOG(INFO) << "hello"; +#ifdef NDEBUG + EXPECT_FALSE(log_message_was_called()); +#else + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 7), last_source_line()); + EXPECT_EQ("hello", last_message()); +#endif +} + +TEST_F(LoggingTest, DlogIf) { + // We start at |MOJO_LOG_LEVEL_INFO|. It shouldn't evaluate the condition in + // this case. + MOJO_DLOG_IF(VERBOSE, NotCalledCondition()) << "hello"; + EXPECT_FALSE(not_called_condition_was_called()); + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_DLOG_IF(INFO, 1 == 0) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_DLOG_IF(INFO, 1 == 1) << "hello"; +#ifdef NDEBUG + EXPECT_FALSE(log_message_was_called()); +#else + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 7), last_source_line()); + EXPECT_EQ("hello", last_message()); +#endif + + ResetMockLogger(); + +// |MOJO_DLOG_IF()| shouldn't compile its condition for non-debug builds. +#ifndef NDEBUG + bool debug_only = true; +#endif + MOJO_DLOG_IF(WARNING, debug_only) << "hello"; +#ifdef NDEBUG + EXPECT_FALSE(log_message_was_called()); +#else + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_WARNING, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 7), last_source_line()); + EXPECT_EQ("hello", last_message()); +#endif +} + +TEST_F(LoggingTest, Dcheck) { + MOJO_DCHECK(true); + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + MOJO_DCHECK(true) << "hello"; + EXPECT_FALSE(log_message_was_called()); + + ResetMockLogger(); + + // |MOJO_DCHECK()| should compile (but not evaluate) its condition even for + // non-debug builds. (Hopefully, we'll get an unused variable error if it + // fails to compile the condition.) + bool was_called = false; + MOJO_DCHECK(DcheckTestHelper(&was_called)) << "hello"; +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) + EXPECT_FALSE(was_called); + EXPECT_FALSE(log_message_was_called()); +#else + EXPECT_TRUE(was_called); + EXPECT_TRUE(log_message_was_called()); + EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level()); + EXPECT_EQ(kOurFilename, last_source_file()); + EXPECT_EQ(static_cast<uint32_t>(__LINE__ - 9), last_source_line()); + EXPECT_EQ("Check failed: DcheckTestHelper(&was_called). hello", + last_message()); +#endif + + ResetMockLogger(); + + // Also try to make sure that we parenthesize the condition properly. + bool x = true; + MOJO_DCHECK(false || x) << "hello"; + EXPECT_FALSE(log_message_was_called()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn new file mode 100644 index 0000000..e120462 --- /dev/null +++ b/mojo/public/cpp/system/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../mojo_sdk.gni") + +mojo_sdk_source_set("system") { + sources = [ + "buffer.h", + "core.h", + "data_pipe.h", + "functions.h", + "handle.h", + "macros.h", + "message_pipe.h", + ] + + mojo_sdk_public_deps = [ "mojo/public/c/system" ] +} diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h new file mode 100644 index 0000000..9458c0a --- /dev/null +++ b/mojo/public/cpp/system/buffer.h @@ -0,0 +1,132 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a C++ wrapping around the Mojo C API for shared buffers, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/buffer.h" for complete documentation of the +// API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ + +#include <assert.h> + +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| referring to a shared +// buffer. +class SharedBufferHandle : public Handle { + public: + SharedBufferHandle() {} + explicit SharedBufferHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(SharedBufferHandle) == sizeof(Handle), + "Bad size for C++ SharedBufferHandle"); + +typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle; +static_assert(sizeof(ScopedSharedBufferHandle) == sizeof(SharedBufferHandle), + "Bad size for C++ ScopedSharedBufferHandle"); + +// Creates a shared buffer. See |MojoCreateSharedBuffer()| for complete +// documentation. +inline MojoResult CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + ScopedSharedBufferHandle* shared_buffer) { + assert(shared_buffer); + SharedBufferHandle handle; + MojoResult rv = + MojoCreateSharedBuffer(options, num_bytes, handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + shared_buffer->reset(handle); + return rv; +} + +// Duplicates a handle to a buffer, most commonly so that the buffer can be +// shared with other applications. See |MojoDuplicateBufferHandle()| for +// complete documentation. +// +// TODO(ggowan): Rename this to DuplicateBufferHandle since it is making another +// handle to the same buffer, not duplicating the buffer itself. +// +// TODO(vtl): This (and also the functions below) are templatized to allow for +// future/other buffer types. A bit "safer" would be to overload this function +// manually. (The template enforces that the in and out handles be of the same +// type.) +template <class BufferHandleType> +inline MojoResult DuplicateBuffer( + BufferHandleType buffer, + const MojoDuplicateBufferHandleOptions* options, + ScopedHandleBase<BufferHandleType>* new_buffer) { + assert(new_buffer); + BufferHandleType handle; + MojoResult rv = MojoDuplicateBufferHandle( + buffer.value(), options, handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + new_buffer->reset(handle); + return rv; +} + +// Maps a part of a buffer (specified by |buffer|, |offset|, and |num_bytes|) +// into memory. See |MojoMapBuffer()| for complete documentation. +template <class BufferHandleType> +inline MojoResult MapBuffer(BufferHandleType buffer, + uint64_t offset, + uint64_t num_bytes, + void** pointer, + MojoMapBufferFlags flags) { + assert(buffer.is_valid()); + return MojoMapBuffer(buffer.value(), offset, num_bytes, pointer, flags); +} + +// Unmaps a part of a buffer that was previously mapped with |MapBuffer()|. +// See |MojoUnmapBuffer()| for complete documentation. +inline MojoResult UnmapBuffer(void* pointer) { + assert(pointer); + return MojoUnmapBuffer(pointer); +} + +// A wrapper class that automatically creates a shared buffer and owns the +// handle. +class SharedBuffer { + public: + explicit SharedBuffer(uint64_t num_bytes); + SharedBuffer(uint64_t num_bytes, + const MojoCreateSharedBufferOptions& options); + ~SharedBuffer(); + + ScopedSharedBufferHandle handle; +}; + +inline SharedBuffer::SharedBuffer(uint64_t num_bytes) { + MojoResult result = CreateSharedBuffer(nullptr, num_bytes, &handle); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline SharedBuffer::SharedBuffer( + uint64_t num_bytes, + const MojoCreateSharedBufferOptions& options) { + MojoResult result = CreateSharedBuffer(&options, num_bytes, &handle); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline SharedBuffer::~SharedBuffer() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ diff --git a/mojo/public/cpp/system/core.h b/mojo/public/cpp/system/core.h new file mode 100644 index 0000000..b08a5a6 --- /dev/null +++ b/mojo/public/cpp/system/core.h @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ + +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/functions.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/cpp/system/message_pipe.h" + +#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h new file mode 100644 index 0000000..c451cff --- /dev/null +++ b/mojo/public/cpp/system/data_pipe.h @@ -0,0 +1,162 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a C++ wrapping around the Mojo C API for data pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/data_pipe.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ + +#include <assert.h> + +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to the producer end of a +// data pipe. +class DataPipeProducerHandle : public Handle { + public: + DataPipeProducerHandle() {} + explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeProducerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeProducerHandle"); + +typedef ScopedHandleBase<DataPipeProducerHandle> ScopedDataPipeProducerHandle; +static_assert(sizeof(ScopedDataPipeProducerHandle) == + sizeof(DataPipeProducerHandle), + "Bad size for C++ ScopedDataPipeProducerHandle"); + +// A strongly-typed representation of a |MojoHandle| to the consumer end of a +// data pipe. +class DataPipeConsumerHandle : public Handle { + public: + DataPipeConsumerHandle() {} + explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeConsumerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeConsumerHandle"); + +typedef ScopedHandleBase<DataPipeConsumerHandle> ScopedDataPipeConsumerHandle; +static_assert(sizeof(ScopedDataPipeConsumerHandle) == + sizeof(DataPipeConsumerHandle), + "Bad size for C++ ScopedDataPipeConsumerHandle"); + +// Creates a new data pipe. See |MojoCreateDataPipe()| for complete +// documentation. +inline MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + ScopedDataPipeProducerHandle* data_pipe_producer, + ScopedDataPipeConsumerHandle* data_pipe_consumer) { + assert(data_pipe_producer); + assert(data_pipe_consumer); + DataPipeProducerHandle producer_handle; + DataPipeConsumerHandle consumer_handle; + MojoResult rv = MojoCreateDataPipe(options, + producer_handle.mutable_value(), + consumer_handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + data_pipe_producer->reset(producer_handle); + data_pipe_consumer->reset(consumer_handle); + return rv; +} + +// Writes to a data pipe. See |MojoWriteData| for complete documentation. +inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for +// complete documentation. +inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MojoBeginWriteData( + data_pipe_producer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for +// complete documentation. +inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + uint32_t num_bytes_written) { + return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written); +} + +// Reads from a data pipe. See |MojoReadData()| for complete documentation. +inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase read from a data pipe. See |MojoBeginReadData()| for +// complete documentation. +inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MojoBeginReadData( + data_pipe_consumer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase read from a data pipe. See |MojoEndReadData()| for +// complete documentation. +inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + uint32_t num_bytes_read) { + return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read); +} + +// A wrapper class that automatically creates a data pipe and owns both handles. +// TODO(vtl): Make an even more friendly version? (Maybe templatized for a +// particular type instead of some "element"? Maybe functions that take +// vectors?) +class DataPipe { + public: + DataPipe(); + explicit DataPipe(const MojoCreateDataPipeOptions& options); + ~DataPipe(); + + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; +}; + +inline DataPipe::DataPipe() { + MojoResult result = + CreateDataPipe(nullptr, &producer_handle, &consumer_handle); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) { + MojoResult result = + CreateDataPipe(&options, &producer_handle, &consumer_handle); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline DataPipe::~DataPipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h new file mode 100644 index 0000000..9cfe316 --- /dev/null +++ b/mojo/public/cpp/system/functions.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a C++ wrapping around the standalone functions of the Mojo +// C API, replacing the prefix of "Mojo" with a "mojo" namespace. +// +// Please see "mojo/public/c/system/functions.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ + +#include "mojo/public/c/system/functions.h" + +namespace mojo { + +// Returns the current |MojoTimeTicks| value. See |MojoGetTimeTicksNow()| for +// complete documentation. +inline MojoTimeTicks GetTimeTicksNow() { + return MojoGetTimeTicksNow(); +} + +// The C++ wrappers for |MojoWait()| and |MojoWaitMany()| are defined in +// "handle.h". +// TODO(ggowan): Consider making the C and C++ APIs more consistent in the +// organization of the functions into different header files (since in the C +// API, those functions are defined in "functions.h"). + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h new file mode 100644 index 0000000..45624bc --- /dev/null +++ b/mojo/public/cpp/system/handle.h @@ -0,0 +1,290 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ + +#include <assert.h> +#include <limits> + +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// OVERVIEW +// +// |Handle| and |...Handle|: +// +// |Handle| is a simple, copyable wrapper for the C type |MojoHandle| (which is +// just an integer). Its purpose is to increase type-safety, not provide +// lifetime management. For the same purpose, we have trivial *subclasses* of +// |Handle|, e.g., |MessagePipeHandle| and |DataPipeProducerHandle|. |Handle| +// and its subclasses impose *no* extra overhead over using |MojoHandle|s +// directly. +// +// Note that though we provide constructors for |Handle|/|...Handle| from a +// |MojoHandle|, we do not provide, e.g., a constructor for |MessagePipeHandle| +// from a |Handle|. This is for type safety: If we did, you'd then be able to +// construct a |MessagePipeHandle| from, e.g., a |DataPipeProducerHandle| (since +// it's a |Handle|). +// +// |ScopedHandleBase| and |Scoped...Handle|: +// +// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle +// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped +// wrapper for a |T*|). It provides lifetime management, closing its owned +// handle on destruction. It also provides (emulated) move semantics, again +// along the lines of C++11's |unique_ptr| (and exactly like Chromium's +// |scoped_ptr|). +// +// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|. +// Similarly, |ScopedMessagePipeHandle| is just a +// |ScopedHandleBase<MessagePipeHandle>|. Etc. Note that a +// |ScopedMessagePipeHandle| is *not* a (subclass of) |ScopedHandle|. +// +// Wrapper functions: +// +// We provide simple wrappers for the |Mojo...()| functions (in +// mojo/public/c/system/core.h -- see that file for details on individual +// functions). +// +// The general guideline is functions that imply ownership transfer of a handle +// should take (or produce) an appropriate |Scoped...Handle|, while those that +// don't take a |...Handle|. For example, |CreateMessagePipe()| has two +// |ScopedMessagePipe| "out" parameters, whereas |Wait()| and |WaitMany()| take +// |Handle| parameters. Some, have both: e.g., |DuplicatedBuffer()| takes a +// suitable (unscoped) handle (e.g., |SharedBufferHandle|) "in" parameter and +// produces a suitable scoped handle (e.g., |ScopedSharedBufferHandle| a.k.a. +// |ScopedHandleBase<SharedBufferHandle>|) as an "out" parameter. +// +// An exception are some of the |...Raw()| functions. E.g., |CloseRaw()| takes a +// |Handle|, leaving the user to discard the wrapper. +// +// ScopedHandleBase ------------------------------------------------------------ + +// Scoper for the actual handle types defined further below. It's move-only, +// like the C++11 |unique_ptr|. +template <class HandleType> +class ScopedHandleBase { + MOJO_MOVE_ONLY_TYPE(ScopedHandleBase) + + public: + ScopedHandleBase() {} + explicit ScopedHandleBase(HandleType handle) : handle_(handle) {} + ~ScopedHandleBase() { CloseIfNecessary(); } + + template <class CompatibleHandleType> + explicit ScopedHandleBase(ScopedHandleBase<CompatibleHandleType> other) + : handle_(other.release()) {} + + // Move-only constructor and operator=. + ScopedHandleBase(ScopedHandleBase&& other) : handle_(other.release()) {} + ScopedHandleBase& operator=(ScopedHandleBase&& other) { + if (&other != this) { + CloseIfNecessary(); + handle_ = other.release(); + } + return *this; + } + + const HandleType& get() const { return handle_; } + + template <typename PassedHandleType> + static ScopedHandleBase<HandleType> From( + ScopedHandleBase<PassedHandleType> other) { + static_assert( + sizeof(static_cast<PassedHandleType*>(static_cast<HandleType*>(0))), + "HandleType is not a subtype of PassedHandleType"); + return ScopedHandleBase<HandleType>( + static_cast<HandleType>(other.release().value())); + } + + void swap(ScopedHandleBase& other) { handle_.swap(other.handle_); } + + HandleType release() MOJO_WARN_UNUSED_RESULT { + HandleType rv; + rv.swap(handle_); + return rv; + } + + void reset(HandleType handle = HandleType()) { + CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { return handle_.is_valid(); } + + private: + void CloseIfNecessary() { + if (!handle_.is_valid()) + return; + MojoResult result = MojoClose(handle_.value()); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + } + + HandleType handle_; +}; + +template <typename HandleType> +inline ScopedHandleBase<HandleType> MakeScopedHandle(HandleType handle) { + return ScopedHandleBase<HandleType>(handle); +} + +// Handle ---------------------------------------------------------------------- + +const MojoHandle kInvalidHandleValue = MOJO_HANDLE_INVALID; + +// Wrapper base class for |MojoHandle|. +class Handle { + public: + Handle() : value_(kInvalidHandleValue) {} + explicit Handle(MojoHandle value) : value_(value) {} + ~Handle() {} + + void swap(Handle& other) { + MojoHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { return value_ != kInvalidHandleValue; } + + const MojoHandle& value() const { return value_; } + MojoHandle* mutable_value() { return &value_; } + void set_value(MojoHandle value) { value_ = value; } + + private: + MojoHandle value_; + + // Copying and assignment allowed. +}; + +// Should have zero overhead. +static_assert(sizeof(Handle) == sizeof(MojoHandle), "Bad size for C++ Handle"); + +// The scoper should also impose no more overhead. +typedef ScopedHandleBase<Handle> ScopedHandle; +static_assert(sizeof(ScopedHandle) == sizeof(Handle), + "Bad size for C++ ScopedHandle"); + +inline MojoResult Wait(Handle handle, + MojoHandleSignals signals, + MojoDeadline deadline, + MojoHandleSignalsState* signals_state) { + return MojoWait(handle.value(), signals, deadline, signals_state); +} + +const uint32_t kInvalidWaitManyIndexValue = static_cast<uint32_t>(-1); + +// Simplify the interpretation of the output from |MojoWaitMany()|. +class WaitManyResult { + public: + explicit WaitManyResult(MojoResult mojo_wait_many_result) + : result(mojo_wait_many_result), index(kInvalidWaitManyIndexValue) {} + + WaitManyResult(MojoResult mojo_wait_many_result, uint32_t result_index) + : result(mojo_wait_many_result), index(result_index) {} + + // A valid handle index is always returned if |WaitMany()| succeeds, but may + // or may not be returned if |WaitMany()| returns an error. Use this helper + // function to check if |index| is a valid index into the handle array. + bool IsIndexValid() const { return index != kInvalidWaitManyIndexValue; } + + // The |signals_states| array is always returned by |WaitMany()| on success, + // but may or may not be returned if |WaitMany()| returns an error. Use this + // helper function to check if |signals_states| holds valid data. + bool AreSignalsStatesValid() const { + return result != MOJO_RESULT_INVALID_ARGUMENT && + result != MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + MojoResult result; + uint32_t index; +}; + +// |HandleVectorType| and |FlagsVectorType| should be similar enough to +// |std::vector<Handle>| and |std::vector<MojoHandleSignals>|, respectively: +// - They should have a (const) |size()| method that returns an unsigned type. +// - They must provide contiguous storage, with access via (const) reference to +// that storage provided by a (const) |operator[]()| (by reference). +template <class HandleVectorType, + class FlagsVectorType, + class SignalsStateVectorType> +inline WaitManyResult WaitMany(const HandleVectorType& handles, + const FlagsVectorType& signals, + MojoDeadline deadline, + SignalsStateVectorType* signals_states) { + if (signals.size() != handles.size() || + (signals_states && signals_states->size() != signals.size())) + return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT); + if (handles.size() >= kInvalidWaitManyIndexValue) + return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED); + + if (handles.size() == 0) { + return WaitManyResult( + MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr)); + } + + uint32_t result_index = kInvalidWaitManyIndexValue; + const Handle& first_handle = handles[0]; + const MojoHandleSignals& first_signals = signals[0]; + MojoHandleSignalsState* first_state = + signals_states ? &(*signals_states)[0] : nullptr; + MojoResult result = + MojoWaitMany(reinterpret_cast<const MojoHandle*>(&first_handle), + &first_signals, static_cast<uint32_t>(handles.size()), + deadline, &result_index, first_state); + return WaitManyResult(result, result_index); +} + +// C++ 4.10, regarding pointer conversion, says that an integral null pointer +// constant can be converted to |std::nullptr_t| (which is a typedef for +// |decltype(nullptr)|). The opposite direction is not allowed. +template <class HandleVectorType, class FlagsVectorType> +inline WaitManyResult WaitMany(const HandleVectorType& handles, + const FlagsVectorType& signals, + MojoDeadline deadline, + decltype(nullptr) signals_states) { + if (signals.size() != handles.size()) + return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT); + if (handles.size() >= kInvalidWaitManyIndexValue) + return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED); + + if (handles.size() == 0) { + return WaitManyResult( + MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr)); + } + + uint32_t result_index = kInvalidWaitManyIndexValue; + const Handle& first_handle = handles[0]; + const MojoHandleSignals& first_signals = signals[0]; + MojoResult result = MojoWaitMany( + reinterpret_cast<const MojoHandle*>(&first_handle), &first_signals, + static_cast<uint32_t>(handles.size()), deadline, &result_index, nullptr); + return WaitManyResult(result, result_index); +} + +// |Close()| takes ownership of the handle, since it'll invalidate it. +// Note: There's nothing to do, since the argument will be destroyed when it +// goes out of scope. +template <class HandleType> +inline void Close(ScopedHandleBase<HandleType> /*handle*/) { +} + +// Most users should typically use |Close()| (above) instead. +inline MojoResult CloseRaw(Handle handle) { + return MojoClose(handle.value()); +} + +// Strict weak ordering, so that |Handle|s can be used as keys in |std::map|s, +inline bool operator<(const Handle a, const Handle b) { + return a.value() < b.value(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ diff --git a/mojo/public/cpp/system/macros.h b/mojo/public/cpp/system/macros.h new file mode 100644 index 0000000..7f0fcd7 --- /dev/null +++ b/mojo/public/cpp/system/macros.h @@ -0,0 +1,81 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Define a set of C++ specific macros. +// Mojo C++ API users can assume that mojo/public/cpp/system/macros.h +// includes mojo/public/c/system/macros.h. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ + +#include "mojo/public/c/system/macros.h" // Symbols exposed. + +// A macro to disallow the copy constructor and operator= functions. +#define MOJO_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +// Used to calculate the number of elements in an array. +// (See |arraysize()| in Chromium's base/macros.h for more details.) +namespace mojo { +namespace internal { +template <typename T, size_t N> +char(&ArraySizeHelper(T(&array)[N]))[N]; +#if !defined(_MSC_VER) +template <typename T, size_t N> +char(&ArraySizeHelper(const T(&array)[N]))[N]; +#endif +} // namespace internal +} // namespace mojo +#define MOJO_ARRAYSIZE(array) (sizeof(::mojo::internal::ArraySizeHelper(array))) + +// Used to make a type move-only. See Chromium's base/move.h for more +// details. The MoveOnlyTypeForCPP03 typedef is for Chromium's base/callback.h +// to tell that this type is move-only. +#define MOJO_MOVE_ONLY_TYPE(type) \ + private: \ + type(type&); \ + void operator=(type&); \ + \ + public: \ + type&& Pass() MOJO_WARN_UNUSED_RESULT { return static_cast<type&&>(*this); } \ + typedef void MoveOnlyTypeForCPP03; \ + \ + private: + +// The C++ standard requires that static const members have an out-of-class +// definition (in a single compilation unit), but MSVC chokes on this (when +// language extensions, which are required, are enabled). (You're only likely to +// notice the need for a definition if you take the address of the member or, +// more commonly, pass it to a function that takes it as a reference argument -- +// probably an STL function.) This macro makes MSVC do the right thing. See +// http://msdn.microsoft.com/en-us/library/34h23df8(v=vs.100).aspx for more +// information. This workaround does not appear to be necessary after VS2015. +// Use like: +// +// In the .h file: +// struct Foo { +// static const int kBar = 5; +// }; +// +// In the .cc file: +// MOJO_STATIC_CONST_MEMBER_DEFINITION const int Foo::kBar; +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define MOJO_STATIC_CONST_MEMBER_DEFINITION __declspec(selectany) +#else +#define MOJO_STATIC_CONST_MEMBER_DEFINITION +#endif + +namespace mojo { + +// Used to explicitly mark the return value of a function as unused. (You this +// if you are really sure you don't want to do anything with the return value of +// a function marked with |MOJO_WARN_UNUSED_RESULT|. +template <typename T> +inline void ignore_result(const T&) { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ diff --git a/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h new file mode 100644 index 0000000..e7a1e35 --- /dev/null +++ b/mojo/public/cpp/system/message_pipe.h @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a C++ wrapping around the Mojo C API for message pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/message_pipe.h" for complete documentation +// of the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ + +#include <assert.h> + +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to one end of a message +// pipe. +class MessagePipeHandle : public Handle { + public: + MessagePipeHandle() {} + explicit MessagePipeHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(MessagePipeHandle) == sizeof(Handle), + "Bad size for C++ MessagePipeHandle"); + +typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle; +static_assert(sizeof(ScopedMessagePipeHandle) == sizeof(MessagePipeHandle), + "Bad size for C++ ScopedMessagePipeHandle"); + +// Creates a message pipe. See |MojoCreateMessagePipe()| for complete +// documentation. +inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + ScopedMessagePipeHandle* message_pipe0, + ScopedMessagePipeHandle* message_pipe1) { + assert(message_pipe0); + assert(message_pipe1); + MessagePipeHandle handle0; + MessagePipeHandle handle1; + MojoResult rv = MojoCreateMessagePipe( + options, handle0.mutable_value(), handle1.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + message_pipe0->reset(handle0); + message_pipe1->reset(handle1); + return rv; +} + +// The following "...Raw" versions fully expose the underlying API, and don't +// help with ownership of handles (especially when writing messages). It is +// expected that in most cases these methods will be called through generated +// bindings anyway. +// TODO(vtl): Write friendlier versions of these functions (using scoped +// handles and/or vectors) if there is a demonstrated need for them. + +// Writes to a message pipe. If handles are attached, on success the handles +// will no longer be valid (the receiver will receive equivalent, but logically +// different, handles). See |MojoWriteMessage()| for complete documentation. +inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return MojoWriteMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// Reads from a message pipe. See |MojoReadMessage()| for complete +// documentation. +inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return MojoReadMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// A wrapper class that automatically creates a message pipe and owns both +// handles. +class MessagePipe { + public: + MessagePipe(); + explicit MessagePipe(const MojoCreateMessagePipeOptions& options); + ~MessagePipe(); + + ScopedMessagePipeHandle handle0; + ScopedMessagePipeHandle handle1; +}; + +inline MessagePipe::MessagePipe() { + MojoResult result = CreateMessagePipe(nullptr, &handle0, &handle1); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline MessagePipe::MessagePipe(const MojoCreateMessagePipeOptions& options) { + MojoResult result = CreateMessagePipe(&options, &handle0, &handle1); + MOJO_ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); +} + +inline MessagePipe::~MessagePipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn new file mode 100644 index 0000000..3b6058c --- /dev/null +++ b/mojo/public/cpp/system/tests/BUILD.gn @@ -0,0 +1,25 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../../mojo_sdk.gni") + +mojo_sdk_source_set("tests") { + testonly = true + + sources = [ + "core_unittest.cc", + "macros_unittest.cc", + ] + + deps = [ + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/c/system/tests", + "mojo/public/cpp/environment:standalone", + "mojo/public/cpp/system", + "mojo/public/cpp/test_support:test_utils", + ] +} diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc new file mode 100644 index 0000000..4dcad43 --- /dev/null +++ b/mojo/public/cpp/system/tests/core_unittest.cc @@ -0,0 +1,495 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file tests the C++ Mojo system core wrappers. +// TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets +// compiled into a different binary from the C API tests. + +#include "mojo/public/cpp/system/core.h" + +#include <stddef.h> + +#include <map> + +#include "mojo/public/cpp/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const MojoHandleSignals kSignalReadableWritable = + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + +const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +TEST(CoreCppTest, GetTimeTicksNow) { + const MojoTimeTicks start = GetTimeTicksNow(); + EXPECT_NE(static_cast<MojoTimeTicks>(0), start) + << "GetTimeTicksNow should return nonzero value"; +} + +TEST(CoreCppTest, Basic) { + // Basic |Handle| implementation: + { + EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue); + + Handle h0; + EXPECT_EQ(kInvalidHandleValue, h0.value()); + EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value()); + EXPECT_FALSE(h0.is_valid()); + + Handle h1(static_cast<MojoHandle>(123)); + EXPECT_EQ(static_cast<MojoHandle>(123), h1.value()); + EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value()); + EXPECT_TRUE(h1.is_valid()); + *h1.mutable_value() = static_cast<MojoHandle>(456); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + h1.swap(h0); + EXPECT_EQ(static_cast<MojoHandle>(456), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_FALSE(h1.is_valid()); + + h1.set_value(static_cast<MojoHandle>(789)); + h0.swap(h1); + EXPECT_EQ(static_cast<MojoHandle>(789), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + // Make sure copy constructor works. + Handle h2(h0); + EXPECT_EQ(static_cast<MojoHandle>(789), h2.value()); + // And assignment. + h2 = h1; + EXPECT_EQ(static_cast<MojoHandle>(456), h2.value()); + + // Make sure that we can put |Handle|s into |std::map|s. + h0 = Handle(static_cast<MojoHandle>(987)); + h1 = Handle(static_cast<MojoHandle>(654)); + h2 = Handle(static_cast<MojoHandle>(321)); + Handle h3; + std::map<Handle, int> handle_to_int; + handle_to_int[h0] = 0; + handle_to_int[h1] = 1; + handle_to_int[h2] = 2; + handle_to_int[h3] = 3; + + EXPECT_EQ(4u, handle_to_int.size()); + EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end()); + EXPECT_EQ(0, handle_to_int[h0]); + EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end()); + EXPECT_EQ(1, handle_to_int[h1]); + EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end()); + EXPECT_EQ(2, handle_to_int[h2]); + EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end()); + EXPECT_EQ(3, handle_to_int[h3]); + EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) == + handle_to_int.end()); + + // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out + // how to support the variations of |hash_map|.) + } + + // |Handle|/|ScopedHandle| functions: + { + ScopedHandle h; + + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + // This should be a no-op. + Close(h.Pass()); + + // It should still be invalid. + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000, nullptr)); + + std::vector<Handle> wh; + wh.push_back(h.get()); + std::vector<MojoHandleSignals> sigs; + sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE); + WaitManyResult wait_many_result = + WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, nullptr); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result); + EXPECT_TRUE(wait_many_result.IsIndexValid()); + EXPECT_FALSE(wait_many_result.AreSignalsStatesValid()); + + // Make sure that our specialized template correctly handles |NULL| as well + // as |nullptr|. + wait_many_result = WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, NULL); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result); + EXPECT_EQ(0u, wait_many_result.index); + EXPECT_TRUE(wait_many_result.IsIndexValid()); + EXPECT_FALSE(wait_many_result.AreSignalsStatesValid()); + } + + // |MakeScopedHandle| (just compilation tests): + { + EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid()); + } + + // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions: + { + MessagePipeHandle h_invalid; + EXPECT_FALSE(h_invalid.is_valid()); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw( + h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + char buffer[10] = {0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw(h_invalid, + buffer, + sizeof(buffer), + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + nullptr, + nullptr, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Basic tests of waiting and closing. + MojoHandle hv0 = kInvalidHandleValue; + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + EXPECT_FALSE(h0.get().is_valid()); + EXPECT_FALSE(h1.get().is_valid()); + + CreateMessagePipe(nullptr, &h0, &h1); + EXPECT_TRUE(h0.get().is_valid()); + EXPECT_TRUE(h1.get().is_valid()); + EXPECT_NE(h0.get().value(), h1.get().value()); + // Save the handle values, so we can check that things got closed + // correctly. + hv0 = h0.get().value(); + MojoHandle hv1 = h1.get().value(); + MojoHandleSignalsState state; + + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + std::vector<Handle> wh; + wh.push_back(h0.get()); + wh.push_back(h1.get()); + std::vector<MojoHandleSignals> sigs; + sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE); + sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE); + std::vector<MojoHandleSignalsState> states(sigs.size()); + WaitManyResult wait_many_result = WaitMany(wh, sigs, 1000, &states); + EXPECT_EQ(MOJO_RESULT_OK, wait_many_result.result); + EXPECT_EQ(1u, wait_many_result.index); + EXPECT_TRUE(wait_many_result.IsIndexValid()); + EXPECT_TRUE(wait_many_result.AreSignalsStatesValid()); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals); + EXPECT_EQ(kSignalAll, states[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals); + EXPECT_EQ(kSignalAll, states[1].satisfiable_signals); + + // Test closing |h1| explicitly. + Close(h1.Pass()); + EXPECT_FALSE(h1.get().is_valid()); + + // Make sure |h1| is closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE, + MOJO_DEADLINE_INDEFINITE, nullptr)); + + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + } + // |hv0| should have been closed when |h0| went out of scope, so this close + // should fail. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + + // Actually test writing/reading messages. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h0.get(), + kHello, + kHelloSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + char buffer[10] = {0}; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h1.get(), + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + + // Send a handle over the previously-establish message pipe. Use the + // |MessagePipe| wrapper (to test it), which automatically creates a + // message pipe. + MessagePipe mp; + + // Write a message to |mp.handle0|, before we send |mp.handle1|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(mp.handle0.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |mp.handle1| over |h1| to |h0|. + MojoHandle handles[5]; + handles[0] = mp.handle1.release().value(); + EXPECT_NE(kInvalidHandleValue, handles[0]); + EXPECT_FALSE(mp.handle1.get().is_valid()); + uint32_t handles_count = 1; + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + handles, + handles_count, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |handles[0]| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0])); + + // Read "hello" and the sent handle. + EXPECT_EQ(MOJO_RESULT_OK, Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h0.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(1u, handles_count); + EXPECT_NE(kInvalidHandleValue, handles[0]); + + // Read from the sent/received handle. + mp.handle1.reset(MessagePipeHandle(handles[0])); + // Save |handles[0]| to check that it gets properly closed. + hv0 = handles[0]; + + EXPECT_EQ(MOJO_RESULT_OK, + Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(mp.handle1.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, buffer_size); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(0u, handles_count); + } + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + } + + // TODO(vtl): Test |CloseRaw()|. + // TODO(vtl): Test |reset()| more thoroughly? +} + +TEST(CoreCppTest, TearDownWithMessagesEnqueued) { + // Tear down a message pipe which still has a message enqueued, with the + // message also having a valid message pipe handle. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + CreateMessagePipe(nullptr, &h2, &h3); + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + } + + // Do this in a different order: make the enqueued message pipe handle only + // half-alive. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + CreateMessagePipe(nullptr, &h2, &h3); + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + } +} + +TEST(CoreCppTest, ScopedHandleMoveCtor) { + ScopedSharedBufferHandle buffer1; + EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1)); + EXPECT_TRUE(buffer1.is_valid()); + + ScopedSharedBufferHandle buffer2; + EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer2)); + EXPECT_TRUE(buffer2.is_valid()); + + // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will + // assert. + buffer1 = buffer2.Pass(); + + EXPECT_TRUE(buffer1.is_valid()); + EXPECT_FALSE(buffer2.is_valid()); +} + +TEST(CoreCppTest, ScopedHandleMoveCtorSelf) { + ScopedSharedBufferHandle buffer1; + EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1)); + EXPECT_TRUE(buffer1.is_valid()); + + buffer1 = buffer1.Pass(); + + EXPECT_TRUE(buffer1.is_valid()); +} + +// TODO(vtl): Write data pipe tests. + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/macros_unittest.cc b/mojo/public/cpp/system/tests/macros_unittest.cc new file mode 100644 index 0000000..72a9b24 --- /dev/null +++ b/mojo/public/cpp/system/tests/macros_unittest.cc @@ -0,0 +1,159 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file tests the C++ Mojo system macros and consists of "positive" tests, +// i.e., those verifying that things work (without compile errors, or even +// warnings if warnings are treated as errors). +// TODO(vtl): Maybe rename "MacrosCppTest" -> "MacrosTest" if/when this gets +// compiled into a different binary from the C API tests. +// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388) +// and write some "negative" tests. + +#include "mojo/public/cpp/system/macros.h" + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { + +// The test for |MOJO_STATIC_CONST_MEMBER_DEFINITION| is really a compile/link +// test. To test it fully would really require a header file and multiple .cc +// files, but we'll just cursorily verify it. +// +// This is defined outside of an anonymous namespace because +// MOJO_STATIC_CONST_MEMBER_DEFINITION may not be used on internal symbols. +struct StructWithStaticConstMember { + static const int kStaticConstMember = 123; +}; +MOJO_STATIC_CONST_MEMBER_DEFINITION +const int StructWithStaticConstMember::kStaticConstMember; + +namespace { + +// Note: MSVS is very strict (and arguably buggy) about warnings for classes +// defined in a local scope, so define these globally. +struct TestOverrideBaseClass { + virtual ~TestOverrideBaseClass() {} + virtual void ToBeOverridden() {} + virtual void AlsoToBeOverridden() = 0; +}; + +struct TestOverrideSubclass : public TestOverrideBaseClass { + ~TestOverrideSubclass() override {} + void ToBeOverridden() override {} + void AlsoToBeOverridden() override {} +}; + +TEST(MacrosCppTest, Override) { + TestOverrideSubclass x; + x.ToBeOverridden(); + x.AlsoToBeOverridden(); +} + +// Note: MSVS is very strict (and arguably buggy) about warnings for classes +// defined in a local scope, so define these globally. +class TestDisallowCopyAndAssignClass { + public: + TestDisallowCopyAndAssignClass() {} + explicit TestDisallowCopyAndAssignClass(int) {} + void NoOp() {} + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(TestDisallowCopyAndAssignClass); +}; + +TEST(MacrosCppTest, DisallowCopyAndAssign) { + TestDisallowCopyAndAssignClass x; + x.NoOp(); + TestDisallowCopyAndAssignClass y(789); + y.NoOp(); +} + +// Test that |MOJO_ARRAYSIZE()| works in a |static_assert()|. +const int kGlobalArray[5] = {1, 2, 3, 4, 5}; +static_assert(MOJO_ARRAYSIZE(kGlobalArray) == 5u, + "MOJO_ARRAY_SIZE() failed in static_assert()"); + +TEST(MacrosCppTest, ArraySize) { + double local_array[4] = {6.7, 7.8, 8.9, 9.0}; + // MSVS considers this local variable unused since MOJO_ARRAYSIZE only takes + // the size of the type of the local and not the values itself. + MOJO_ALLOW_UNUSED_LOCAL(local_array); + EXPECT_EQ(4u, MOJO_ARRAYSIZE(local_array)); +} + +// Note: MSVS is very strict (and arguably buggy) about warnings for classes +// defined in a local scope, so define these globally. +class MoveOnlyInt { + MOJO_MOVE_ONLY_TYPE(MoveOnlyInt) + + public: + MoveOnlyInt() : is_set_(false), value_() {} + explicit MoveOnlyInt(int value) : is_set_(true), value_(value) {} + ~MoveOnlyInt() {} + + // Move-only constructor and operator=. + MoveOnlyInt(MoveOnlyInt&& other) { *this = other.Pass(); } + MoveOnlyInt& operator=(MoveOnlyInt&& other) { + if (&other != this) { + is_set_ = other.is_set_; + value_ = other.value_; + other.is_set_ = false; + } + return *this; + } + + int value() const { + assert(is_set()); + return value_; + } + bool is_set() const { return is_set_; } + + private: + bool is_set_; + int value_; +}; + +TEST(MacrosCppTest, MoveOnlyType) { + MoveOnlyInt x(123); + EXPECT_TRUE(x.is_set()); + EXPECT_EQ(123, x.value()); + MoveOnlyInt y; + EXPECT_FALSE(y.is_set()); + y = x.Pass(); + EXPECT_FALSE(x.is_set()); + EXPECT_TRUE(y.is_set()); + EXPECT_EQ(123, y.value()); + MoveOnlyInt z(y.Pass()); + EXPECT_FALSE(y.is_set()); + EXPECT_TRUE(z.is_set()); + EXPECT_EQ(123, z.value()); + z = z.Pass(); + EXPECT_TRUE(z.is_set()); + EXPECT_EQ(123, z.value()); +} + +// Use it, to make sure things get linked in and to avoid any warnings about +// unused things. +TEST(MacrosCppTest, StaticConstMemberDefinition) { + EXPECT_EQ(123, StructWithStaticConstMember::kStaticConstMember); +} + +// The test for |ignore_result()| is also just a compilation test. (Note that +// |MOJO_WARN_UNUSED_RESULT| can only be used in the prototype. +int ReturnsIntYouMustUse() MOJO_WARN_UNUSED_RESULT; + +int ReturnsIntYouMustUse() { + return 123; +} + +TEST(MacrosCppTest, IgnoreResult) { + ignore_result(ReturnsIntYouMustUse()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn new file mode 100644 index 0000000..a1f7d31 --- /dev/null +++ b/mojo/public/cpp/test_support/BUILD.gn @@ -0,0 +1,25 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../mojo_sdk.gni") + +# GYP version: mojo/public/mojo_public.gyp:mojo_public_test_utils +mojo_sdk_source_set("test_utils") { + testonly = true + + sources = [ + "lib/test_support.cc", + "lib/test_utils.cc", + "test_utils.h", + ] + + deps = [ + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/c/test_support", + "mojo/public/cpp/system", + ] +} diff --git a/mojo/public/cpp/test_support/lib/test_support.cc b/mojo/public/cpp/test_support/lib/test_support.cc new file mode 100644 index 0000000..0b6035b --- /dev/null +++ b/mojo/public/cpp/test_support/lib/test_support.cc @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/test_support/test_support.h" + +#include <stdlib.h> + +namespace mojo { +namespace test { + +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path) { + char** names = MojoTestSupportEnumerateSourceRootRelativeDirectory( + relative_path.c_str()); + std::vector<std::string> results; + for (char** ptr = names; *ptr != nullptr; ++ptr) { + results.push_back(*ptr); + free(*ptr); + } + free(names); + return results; +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc new file mode 100644 index 0000000..210c6b1 --- /dev/null +++ b/mojo/public/cpp/test_support/lib/test_utils.cc @@ -0,0 +1,97 @@ +// Copyright 2013 The Chromium 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 "mojo/public/cpp/test_support/test_utils.h" + +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_support.h" + +namespace mojo { +namespace test { + +bool WriteTextMessage(const MessagePipeHandle& handle, + const std::string& text) { + MojoResult rv = WriteMessageRaw(handle, + text.data(), + static_cast<uint32_t>(text.size()), + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) { + MojoResult rv; + bool did_wait = false; + + uint32_t num_bytes = 0, num_handles = 0; + for (;;) { + rv = ReadMessageRaw(handle, + nullptr, + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv == MOJO_RESULT_SHOULD_WAIT) { + if (did_wait) { + assert(false); // Looping endlessly!? + return false; + } + rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, + nullptr); + if (rv != MOJO_RESULT_OK) + return false; + did_wait = true; + } else { + assert(!num_handles); + break; + } + } + + text->resize(num_bytes); + rv = ReadMessageRaw(handle, + &text->at(0), + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool DiscardMessage(const MessagePipeHandle& handle) { + MojoResult rv = ReadMessageRaw(handle, + nullptr, + nullptr, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + return rv == MOJO_RESULT_OK; +} + +void IterateAndReportPerf(const char* test_name, + const char* sub_test_name, + PerfTestSingleIteration single_iteration, + void* closure) { + // TODO(vtl): These should be specifiable using command-line flags. + static const size_t kGranularity = 100; + static const MojoTimeTicks kPerftestTimeMicroseconds = 3 * 1000000; + + const MojoTimeTicks start_time = GetTimeTicksNow(); + MojoTimeTicks end_time; + size_t iterations = 0; + do { + for (size_t i = 0; i < kGranularity; i++) + (*single_iteration)(closure); + iterations += kGranularity; + + end_time = GetTimeTicksNow(); + } while (end_time - start_time < kPerftestTimeMicroseconds); + + MojoTestSupportLogPerfResult(test_name, sub_test_name, + 1000000.0 * iterations / (end_time - start_time), + "iterations/second"); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/test_support/test_support.h b/mojo/public/cpp/test_support/test_support.h new file mode 100644 index 0000000..9a536e6 --- /dev/null +++ b/mojo/public/cpp/test_support/test_support.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ + +#include <string> +#include <vector> + +#include "mojo/public/c/test_support/test_support.h" + +namespace mojo { +namespace test { + +inline void LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) { + MojoTestSupportLogPerfResult(test_name, sub_test_name, value, units); +} + +// Opens text file relative to the source root for reading. +inline FILE* OpenSourceRootRelativeFile(const std::string& relative_path) { + return MojoTestSupportOpenSourceRootRelativeFile(relative_path.c_str()); +} + +// Returns the list of regular files in a directory relative to the source root. +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h new file mode 100644 index 0000000..6fd5a9e --- /dev/null +++ b/mojo/public/cpp/test_support/test_utils.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ + +#include <string> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace test { + +// Writes a message to |handle| with message data |text|. Returns true on +// success. +bool WriteTextMessage(const MessagePipeHandle& handle, const std::string& text); + +// Reads a message from |handle|, putting its contents into |*text|. Returns +// true on success. (This blocks if necessary and will call |MojoReadMessage()| +// multiple times, e.g., to query the size of the message.) +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text); + +// Discards a message from |handle|. Returns true on success. (This does not +// block. It will fail if no message is available to discard.) +bool DiscardMessage(const MessagePipeHandle& handle); + +// Run |single_iteration| an appropriate number of times and report its +// performance appropriately. (This actually runs |single_iteration| for a fixed +// amount of time and reports the number of iterations per unit time.) +typedef void (*PerfTestSingleIteration)(void* closure); +void IterateAndReportPerf(const char* test_name, + const char* sub_test_name, + PerfTestSingleIteration single_iteration, + void* closure); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ diff --git a/mojo/public/cpp/utility/BUILD.gn b/mojo/public/cpp/utility/BUILD.gn new file mode 100644 index 0000000..96c1d11 --- /dev/null +++ b/mojo/public/cpp/utility/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../mojo_sdk.gni") + +mojo_sdk_source_set("utility") { + sources = [ + "lib/mutex.cc", + "lib/run_loop.cc", + "lib/thread.cc", + "lib/thread_local.h", + "lib/thread_local_posix.cc", + "lib/thread_local_win.cc", + "mutex.h", + "run_loop.h", + "run_loop_handler.h", + "thread.h", + ] + + mojo_sdk_deps = [ + "mojo/public/cpp/bindings:callback", + "mojo/public/cpp/system", + ] + + if (is_win) { + # See crbug.com/342893: + sources -= [ + "lib/mutex.cc", + "lib/thread.cc", + "mutex.h", + "thread.h", + ] + } +} diff --git a/mojo/public/cpp/utility/lib/mutex.cc b/mojo/public/cpp/utility/lib/mutex.cc new file mode 100644 index 0000000..23370e1 --- /dev/null +++ b/mojo/public/cpp/utility/lib/mutex.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/mutex.h" + +#include <assert.h> +#include <errno.h> + +namespace mojo { + +// Release builds have inlined (non-error-checking) definitions in the header. +#if !defined(NDEBUG) +Mutex::Mutex() { + pthread_mutexattr_t mutexattr; + int rv = pthread_mutexattr_init(&mutexattr); + assert(rv == 0); + rv = pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK); + assert(rv == 0); + rv = pthread_mutex_init(&mutex_, &mutexattr); + assert(rv == 0); + rv = pthread_mutexattr_destroy(&mutexattr); + assert(rv == 0); +} + +Mutex::~Mutex() { + int rv = pthread_mutex_destroy(&mutex_); + assert(rv == 0); +} + +void Mutex::Lock() { + int rv = pthread_mutex_lock(&mutex_); + assert(rv == 0); +} + +void Mutex::Unlock() { + int rv = pthread_mutex_unlock(&mutex_); + assert(rv == 0); +} + +bool Mutex::TryLock() { + int rv = pthread_mutex_trylock(&mutex_); + assert(rv == 0 || rv == EBUSY); + return rv == 0; +} + +void Mutex::AssertHeld() { + assert(pthread_mutex_lock(&mutex_) == EDEADLK); +} +#endif // !defined(NDEBUG) + +} // namespace mojo diff --git a/mojo/public/cpp/utility/lib/run_loop.cc b/mojo/public/cpp/utility/lib/run_loop.cc new file mode 100644 index 0000000..7faf748 --- /dev/null +++ b/mojo/public/cpp/utility/lib/run_loop.cc @@ -0,0 +1,267 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/run_loop.h" + +#include <assert.h> + +#include <algorithm> +#include <vector> + +#include "mojo/public/cpp/utility/lib/thread_local.h" +#include "mojo/public/cpp/utility/run_loop_handler.h" + +namespace mojo { +namespace { + +internal::ThreadLocalPointer<RunLoop> current_run_loop; + +const MojoTimeTicks kInvalidTimeTicks = static_cast<MojoTimeTicks>(0); + +} // namespace + +// State needed for one iteration of WaitMany(). +struct RunLoop::WaitState { + WaitState() : deadline(MOJO_DEADLINE_INDEFINITE) {} + + std::vector<Handle> handles; + std::vector<MojoHandleSignals> handle_signals; + MojoDeadline deadline; +}; + +struct RunLoop::RunState { + RunState() : should_quit(false) {} + + bool should_quit; +}; + +RunLoop::RunLoop() + : run_state_(nullptr), next_handler_id_(0), next_sequence_number_(0) { + assert(!current()); + current_run_loop.Set(this); +} + +RunLoop::~RunLoop() { + assert(current() == this); + NotifyHandlers(MOJO_RESULT_ABORTED, IGNORE_DEADLINE); + current_run_loop.Set(nullptr); +} + +// static +void RunLoop::SetUp() { + current_run_loop.Allocate(); +} + +// static +void RunLoop::TearDown() { + assert(!current()); + current_run_loop.Free(); +} + +// static +RunLoop* RunLoop::current() { + return current_run_loop.Get(); +} + +void RunLoop::AddHandler(RunLoopHandler* handler, + const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline) { + assert(current() == this); + assert(handler); + assert(handle.is_valid()); + // Assume it's an error if someone tries to reregister an existing handle. + assert(0u == handler_data_.count(handle)); + HandlerData handler_data; + handler_data.handler = handler; + handler_data.handle_signals = handle_signals; + handler_data.deadline = + (deadline == MOJO_DEADLINE_INDEFINITE) + ? kInvalidTimeTicks + : GetTimeTicksNow() + static_cast<MojoTimeTicks>(deadline); + handler_data.id = next_handler_id_++; + handler_data_[handle] = handler_data; +} + +void RunLoop::RemoveHandler(const Handle& handle) { + assert(current() == this); + handler_data_.erase(handle); +} + +bool RunLoop::HasHandler(const Handle& handle) const { + return handler_data_.find(handle) != handler_data_.end(); +} + +void RunLoop::Run() { + RunInternal(UNTIL_EMPTY); +} + +void RunLoop::RunUntilIdle() { + RunInternal(UNTIL_IDLE); +} + +void RunLoop::RunInternal(RunMode run_mode) { + assert(current() == this); + RunState* old_state = run_state_; + RunState run_state; + run_state_ = &run_state; + for (;;) { + bool did_work = DoDelayedWork(); + if (run_state.should_quit) + break; + did_work |= Wait(run_mode == UNTIL_IDLE); + if (run_state.should_quit) + break; + if (!did_work && run_mode == UNTIL_IDLE) + break; + } + run_state_ = old_state; +} + +bool RunLoop::DoDelayedWork() { + MojoTimeTicks now = GetTimeTicksNow(); + if (!delayed_tasks_.empty() && delayed_tasks_.top().run_time <= now) { + PendingTask task = delayed_tasks_.top(); + delayed_tasks_.pop(); + task.task.Run(); + return true; + } + return false; +} + +void RunLoop::Quit() { + assert(current() == this); + if (run_state_) + run_state_->should_quit = true; +} + +void RunLoop::PostDelayedTask(const Closure& task, MojoTimeTicks delay) { + assert(current() == this); + MojoTimeTicks run_time = delay + GetTimeTicksNow(); + delayed_tasks_.push(PendingTask(task, run_time, next_sequence_number_++)); +} + +bool RunLoop::Wait(bool non_blocking) { + const WaitState wait_state = GetWaitState(non_blocking); + if (wait_state.handles.empty()) { + if (delayed_tasks_.empty()) + Quit(); + return false; + } + + const WaitManyResult wmr = + WaitMany(wait_state.handles, wait_state.handle_signals, + wait_state.deadline, nullptr); + + if (!wmr.IsIndexValid()) { + assert(wmr.result == MOJO_RESULT_DEADLINE_EXCEEDED); + return NotifyHandlers(MOJO_RESULT_DEADLINE_EXCEEDED, CHECK_DEADLINE); + } + + Handle handle = wait_state.handles[wmr.index]; + assert(handler_data_.find(handle) != handler_data_.end()); + RunLoopHandler* handler = handler_data_[handle].handler; + + switch (wmr.result) { + case MOJO_RESULT_OK: + handler->OnHandleReady(handle); + return true; + case MOJO_RESULT_INVALID_ARGUMENT: + case MOJO_RESULT_FAILED_PRECONDITION: + // Remove the handle first, this way if OnHandleError() tries to remove + // the handle our iterator isn't invalidated. + handler_data_.erase(handle); + handler->OnHandleError(handle, wmr.result); + return true; + default: + assert(false); + return false; + } +} + +bool RunLoop::NotifyHandlers(MojoResult error, CheckDeadline check) { + bool notified = false; + + // Make a copy in case someone tries to add/remove new handlers as part of + // notifying. + const HandleToHandlerData cloned_handlers(handler_data_); + const MojoTimeTicks now(GetTimeTicksNow()); + for (HandleToHandlerData::const_iterator i = cloned_handlers.begin(); + i != cloned_handlers.end(); + ++i) { + // Only check deadline exceeded if that's what we're notifying. + if (check == CHECK_DEADLINE && + (i->second.deadline == kInvalidTimeTicks || i->second.deadline > now)) { + continue; + } + + // Since we're iterating over a clone of the handlers, verify the handler + // is still valid before notifying. + if (handler_data_.find(i->first) == handler_data_.end() || + handler_data_[i->first].id != i->second.id) { + continue; + } + + RunLoopHandler* handler = i->second.handler; + handler_data_.erase(i->first); + handler->OnHandleError(i->first, error); + notified = true; + } + + return notified; +} + +RunLoop::WaitState RunLoop::GetWaitState(bool non_blocking) const { + WaitState wait_state; + MojoTimeTicks min_time = kInvalidTimeTicks; + for (HandleToHandlerData::const_iterator i = handler_data_.begin(); + i != handler_data_.end(); + ++i) { + wait_state.handles.push_back(i->first); + wait_state.handle_signals.push_back(i->second.handle_signals); + if (!non_blocking && i->second.deadline != kInvalidTimeTicks && + (min_time == kInvalidTimeTicks || i->second.deadline < min_time)) { + min_time = i->second.deadline; + } + } + if (!delayed_tasks_.empty()) { + MojoTimeTicks delayed_min_time = delayed_tasks_.top().run_time; + if (min_time == kInvalidTimeTicks) + min_time = delayed_min_time; + else + min_time = std::min(min_time, delayed_min_time); + } + if (non_blocking) { + wait_state.deadline = static_cast<MojoDeadline>(0); + } else if (min_time != kInvalidTimeTicks) { + const MojoTimeTicks now = GetTimeTicksNow(); + if (min_time < now) + wait_state.deadline = static_cast<MojoDeadline>(0); + else + wait_state.deadline = static_cast<MojoDeadline>(min_time - now); + } + return wait_state; +} + +RunLoop::PendingTask::PendingTask(const Closure& task, + MojoTimeTicks run_time, + uint64_t sequence_number) + : task(task), run_time(run_time), sequence_number(sequence_number) { +} + +RunLoop::PendingTask::~PendingTask() { +} + +bool RunLoop::PendingTask::operator<(const RunLoop::PendingTask& other) const { + if (run_time != other.run_time) { + // std::priority_queue<> puts the least element at the end of the queue. We + // want the soonest eligible task to be at the head of the queue, so + // run_times further in the future are considered lesser. + return run_time > other.run_time; + } + + return sequence_number > other.sequence_number; +} + +} // namespace mojo diff --git a/mojo/public/cpp/utility/lib/thread.cc b/mojo/public/cpp/utility/lib/thread.cc new file mode 100644 index 0000000..40f0bdd --- /dev/null +++ b/mojo/public/cpp/utility/lib/thread.cc @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/thread.h" + +#include <assert.h> + +namespace mojo { + +Thread::Thread() : options_(), thread_(), started_(false), joined_(false) { +} + +Thread::Thread(const Options& options) + : options_(options), thread_(), started_(false), joined_(false) { +} + +Thread::~Thread() { + // If it was started, it must have been joined. + assert(!started_ || joined_); +} + +void Thread::Start() { + assert(!started_); + assert(!joined_); + + pthread_attr_t attr; + int rv = pthread_attr_init(&attr); + MOJO_ALLOW_UNUSED_LOCAL(rv); + assert(rv == 0); + + // Non-default stack size? + if (options_.stack_size() != 0) { + rv = pthread_attr_setstacksize(&attr, options_.stack_size()); + assert(rv == 0); + } + + started_ = true; + rv = pthread_create(&thread_, &attr, &ThreadRunTrampoline, this); + assert(rv == 0); + + rv = pthread_attr_destroy(&attr); + assert(rv == 0); +} + +void Thread::Join() { + // Must have been started but not yet joined. + assert(started_); + assert(!joined_); + + joined_ = true; + int rv = pthread_join(thread_, nullptr); + MOJO_ALLOW_UNUSED_LOCAL(rv); + assert(rv == 0); +} + +// static +void* Thread::ThreadRunTrampoline(void* arg) { + Thread* self = static_cast<Thread*>(arg); + self->Run(); + return nullptr; +} + +} // namespace mojo diff --git a/mojo/public/cpp/utility/lib/thread_local.h b/mojo/public/cpp/utility/lib/thread_local.h new file mode 100644 index 0000000..f5461ee --- /dev/null +++ b/mojo/public/cpp/utility/lib/thread_local.h @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ +#define MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ + +#ifndef _WIN32 +#include <pthread.h> +#endif + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// Helper functions that abstract the cross-platform APIs. +struct ThreadLocalPlatform { +#ifdef _WIN32 + typedef unsigned long SlotType; +#else + typedef pthread_key_t SlotType; +#endif + + static void AllocateSlot(SlotType* slot); + static void FreeSlot(SlotType slot); + static void* GetValueFromSlot(SlotType slot); + static void SetValueInSlot(SlotType slot, void* value); +}; + +// This class is intended to be statically allocated. +template <typename P> +class ThreadLocalPointer { + public: + ThreadLocalPointer() : slot_() {} + + void Allocate() { ThreadLocalPlatform::AllocateSlot(&slot_); } + + void Free() { ThreadLocalPlatform::FreeSlot(slot_); } + + P* Get() { + return static_cast<P*>(ThreadLocalPlatform::GetValueFromSlot(slot_)); + } + + void Set(P* value) { ThreadLocalPlatform::SetValueInSlot(slot_, value); } + + private: + ThreadLocalPlatform::SlotType slot_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ diff --git a/mojo/public/cpp/utility/lib/thread_local_posix.cc b/mojo/public/cpp/utility/lib/thread_local_posix.cc new file mode 100644 index 0000000..ea7343e --- /dev/null +++ b/mojo/public/cpp/utility/lib/thread_local_posix.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/lib/thread_local.h" + +#include <assert.h> + +namespace mojo { +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + if (pthread_key_create(slot, nullptr) != 0) { + assert(false); + } +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType slot) { + if (pthread_key_delete(slot) != 0) { + assert(false); + } +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { + return pthread_getspecific(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { + if (pthread_setspecific(slot, value) != 0) { + assert(false); + } +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/utility/lib/thread_local_win.cc b/mojo/public/cpp/utility/lib/thread_local_win.cc new file mode 100644 index 0000000..b8239cb --- /dev/null +++ b/mojo/public/cpp/utility/lib/thread_local_win.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/lib/thread_local.h" + +#include <windows.h> +#include <assert.h> + +namespace mojo { +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + *slot = TlsAlloc(); + assert(*slot != TLS_OUT_OF_INDEXES); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType slot) { + if (!TlsFree(slot)) { + assert(false); + } +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { + return TlsGetValue(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { + if (!TlsSetValue(slot, value)) { + assert(false); + } +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/utility/mutex.h b/mojo/public/cpp/utility/mutex.h new file mode 100644 index 0000000..4dc4aee --- /dev/null +++ b/mojo/public/cpp/utility/mutex.h @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ +#define MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ + +#ifdef _WIN32 +#error "Not implemented: See crbug.com/342893." +#endif + +#include <pthread.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +#ifdef NDEBUG +// Note: Make a C++ constant for |PTHREAD_MUTEX_INITIALIZER|. (We can't directly +// use the C macro in an initializer list, since it might expand to |{ ... }|.) +namespace internal { +const pthread_mutex_t kPthreadMutexInitializer = PTHREAD_MUTEX_INITIALIZER; +} +#endif + +class Mutex { + public: +#ifdef NDEBUG + Mutex() : mutex_(internal::kPthreadMutexInitializer) {} + ~Mutex() { pthread_mutex_destroy(&mutex_); } + + void Lock() { pthread_mutex_lock(&mutex_); } + void Unlock() { pthread_mutex_unlock(&mutex_); } + bool TryLock() { return pthread_mutex_trylock(&mutex_) == 0; } + + void AssertHeld() {} +#else + Mutex(); + ~Mutex(); + + void Lock(); + void Unlock(); + bool TryLock(); + + void AssertHeld(); +#endif + + private: + pthread_mutex_t mutex_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Mutex); +}; + +class MutexLock { + public: + explicit MutexLock(Mutex* mutex) : mutex_(mutex) { mutex_->Lock(); } + ~MutexLock() { mutex_->Unlock(); } + + private: + Mutex* const mutex_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MutexLock); +}; + +// Catch bug where variable name is omitted (e.g., |MutexLock (&mu)|). +#define MutexLock(x) static_assert(0, "MutexLock() missing variable name"); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ diff --git a/mojo/public/cpp/utility/run_loop.h b/mojo/public/cpp/utility/run_loop.h new file mode 100644 index 0000000..4673eaa --- /dev/null +++ b/mojo/public/cpp/utility/run_loop.h @@ -0,0 +1,156 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ +#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ + +#include <map> +#include <queue> + +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class RunLoopHandler; + +// Watches handles for signals and calls event handlers when they occur. Also +// executes delayed tasks. This class should only be used by a single thread. +class RunLoop { + public: + RunLoop(); + ~RunLoop(); + + // Sets up state needed for RunLoop. This must be invoked before creating a + // RunLoop. + static void SetUp(); + + // Cleans state created by Setup(). + static void TearDown(); + + // Returns the RunLoop for the current thread. Returns null if not yet + // created. + static RunLoop* current(); + + // Registers a RunLoopHandler for the specified handle. It is an error to + // register more than one handler for a handle, and crashes the process. + // + // The handler's OnHandleReady() method is invoked after one of the signals in + // |handle_signals| occurs. Note that the handler remains registered until + // explicitly removed or an error occurs. + // + // The handler's OnHandleError() method is invoked if the deadline elapses, an + // error is detected, or the RunLoop is being destroyed. The handler is + // automatically unregistered before calling OnHandleError(), so it will not + // receive any further notifications. + void AddHandler(RunLoopHandler* handler, + const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline); + void RemoveHandler(const Handle& handle); + bool HasHandler(const Handle& handle) const; + + // Runs the loop servicing handles and tasks as they are ready. This returns + // when Quit() is invoked, or there are no more handles nor tasks. + void Run(); + + // Runs the loop servicing any handles and tasks that are ready. Does not wait + // for handles or tasks to become ready before returning. Returns early if + // Quit() is invoked. + void RunUntilIdle(); + + void Quit(); + + // Adds a task to be performed after delay has elapsed. Must be posted to the + // current thread's RunLoop. + void PostDelayedTask(const Closure& task, MojoTimeTicks delay); + + private: + struct RunState; + struct WaitState; + + // Contains the data needed to track a request to AddHandler(). + struct HandlerData { + HandlerData() + : handler(nullptr), + handle_signals(MOJO_HANDLE_SIGNAL_NONE), + deadline(0), + id(0) {} + + RunLoopHandler* handler; + MojoHandleSignals handle_signals; + MojoTimeTicks deadline; + // See description of |RunLoop::next_handler_id_| for details. + int id; + }; + + typedef std::map<Handle, HandlerData> HandleToHandlerData; + + // Used for NotifyHandlers to specify whether HandlerData's |deadline| + // should be checked prior to notifying. + enum CheckDeadline { CHECK_DEADLINE, IGNORE_DEADLINE }; + + // Mode of operation of the run loop. + enum RunMode { UNTIL_EMPTY, UNTIL_IDLE }; + + // Runs the loop servicing any handles and tasks that are ready. If + // |run_mode| is |UNTIL_IDLE|, does not wait for handles or tasks to become + // ready before returning. Returns early if Quit() is invoked. + void RunInternal(RunMode run_mode); + + // Do one unit of delayed work, if eligible. Returns true is a task was run. + bool DoDelayedWork(); + + // Waits for a handle to be ready or until the next task must be run. Returns + // after servicing at least one handle (or there are no more handles) unless + // a task must be run or |non_blocking| is true, in which case it will also + // return if no task is registered and servicing at least one handle would + // require blocking. Returns true if a RunLoopHandler was notified. + bool Wait(bool non_blocking); + + // Notifies handlers of |error|. If |check| == CHECK_DEADLINE, this will + // only notify handlers whose deadline has expired and skips the rest. + // Returns true if a RunLoopHandler was notified. + bool NotifyHandlers(MojoResult error, CheckDeadline check); + + // Returns the state needed to pass to WaitMany(). + WaitState GetWaitState(bool non_blocking) const; + + HandleToHandlerData handler_data_; + + // If non-null we're running (inside Run()). Member references a value on the + // stack. + RunState* run_state_; + + // An ever increasing value assigned to each HandlerData::id. Used to detect + // uniqueness while notifying. That is, while notifying expired timers we copy + // |handler_data_| and only notify handlers whose id match. If the id does not + // match it means the handler was removed then added so that we shouldn't + // notify it. + int next_handler_id_; + + struct PendingTask { + PendingTask(const Closure& task, + MojoTimeTicks runtime, + uint64_t sequence_number); + ~PendingTask(); + + bool operator<(const PendingTask& other) const; + + Closure task; + MojoTimeTicks run_time; + uint64_t sequence_number; + }; + // An ever increasing sequence number attached to each pending task in order + // to preserve relative order of tasks posted at the 'same' time. + uint64_t next_sequence_number_; + typedef std::priority_queue<PendingTask> DelayedTaskQueue; + DelayedTaskQueue delayed_tasks_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoop); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ diff --git a/mojo/public/cpp/utility/run_loop_handler.h b/mojo/public/cpp/utility/run_loop_handler.h new file mode 100644 index 0000000..69838d5 --- /dev/null +++ b/mojo/public/cpp/utility/run_loop_handler.h @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ +#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// Used by RunLoop to notify when a handle is either ready or has become +// invalid. +class RunLoopHandler { + public: + virtual void OnHandleReady(const Handle& handle) = 0; + virtual void OnHandleError(const Handle& handle, MojoResult result) = 0; + + protected: + virtual ~RunLoopHandler() {} +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ diff --git a/mojo/public/cpp/utility/tests/BUILD.gn b/mojo/public/cpp/utility/tests/BUILD.gn new file mode 100644 index 0000000..acbbc9f --- /dev/null +++ b/mojo/public/cpp/utility/tests/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../../mojo_sdk.gni") + +mojo_sdk_source_set("tests") { + testonly = true + + sources = [ + "run_loop_unittest.cc", + ] + + deps = [ + "//testing/gtest", + ] + + mojo_sdk_deps = [ + "mojo/public/cpp/environment:standalone", + "mojo/public/cpp/system", + "mojo/public/cpp/test_support:test_utils", + "mojo/public/cpp/utility", + ] + + # crbug.com/342893 + if (!is_win) { + sources += [ + "mutex_unittest.cc", + "thread_unittest.cc", + ] + } +} diff --git a/mojo/public/cpp/utility/tests/mutex_unittest.cc b/mojo/public/cpp/utility/tests/mutex_unittest.cc new file mode 100644 index 0000000..78e95c5 --- /dev/null +++ b/mojo/public/cpp/utility/tests/mutex_unittest.cc @@ -0,0 +1,259 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/mutex.h" + +#include <stdlib.h> // For |rand()|. +#include <time.h> // For |nanosleep()| (defined by POSIX). + +#include <vector> + +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/cpp/utility/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +TEST(MutexTest, TrivialSingleThreaded) { + Mutex mutex; + + mutex.Lock(); + mutex.AssertHeld(); + mutex.Unlock(); + + EXPECT_TRUE(mutex.TryLock()); + mutex.AssertHeld(); + mutex.Unlock(); + + { + MutexLock lock(&mutex); + mutex.AssertHeld(); + } + + EXPECT_TRUE(mutex.TryLock()); + mutex.Unlock(); +} + +class Fiddler { + public: + enum Type { kTypeLock, kTypeTry }; + Fiddler(size_t times_to_lock, + Type type, + bool should_sleep, + Mutex* mutex, + int* shared_value) + : times_to_lock_(times_to_lock), + type_(type), + should_sleep_(should_sleep), + mutex_(mutex), + shared_value_(shared_value) { + } + + ~Fiddler() { + } + + void Fiddle() { + for (size_t i = 0; i < times_to_lock_;) { + switch (type_) { + case kTypeLock: { + mutex_->Lock(); + int old_shared_value = *shared_value_; + if (should_sleep_) + SleepALittle(); + *shared_value_ = old_shared_value + 1; + mutex_->Unlock(); + i++; + break; + } + case kTypeTry: + if (mutex_->TryLock()) { + int old_shared_value = *shared_value_; + if (should_sleep_) + SleepALittle(); + *shared_value_ = old_shared_value + 1; + mutex_->Unlock(); + i++; + } else { + SleepALittle(); // Don't spin. + } + break; + } + } + } + + private: + static void SleepALittle() { + static const long kNanosPerMilli = 1000000; + struct timespec req = { + 0, // Seconds. + (rand() % 10) * kNanosPerMilli // Nanoseconds. + }; + int rv = nanosleep(&req, nullptr); + MOJO_ALLOW_UNUSED_LOCAL(rv); + assert(rv == 0); + } + + const size_t times_to_lock_; + const Type type_; + const bool should_sleep_; + Mutex* const mutex_; + int* const shared_value_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Fiddler); +}; + +class FiddlerThread : public Thread { + public: + // Takes ownership of |fiddler|. + FiddlerThread(Fiddler* fiddler) + : fiddler_(fiddler) { + } + + ~FiddlerThread() override { delete fiddler_; } + + void Run() override { fiddler_->Fiddle(); } + + private: + Fiddler* const fiddler_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(FiddlerThread); +}; + +// This does a stress test (that also checks exclusion). +TEST(MutexTest, ThreadedStress) { + static const size_t kNumThreads = 20; + static const int kTimesToLockEach = 20; + assert(kNumThreads % 4 == 0); + + Mutex mutex; + int shared_value = 0; + + std::vector<FiddlerThread*> fiddler_threads; + + for (size_t i = 0; i < kNumThreads; i += 4) { + fiddler_threads.push_back(new FiddlerThread(new Fiddler( + kTimesToLockEach, Fiddler::kTypeLock, false, &mutex, &shared_value))); + fiddler_threads.push_back(new FiddlerThread(new Fiddler( + kTimesToLockEach, Fiddler::kTypeTry, false, &mutex, &shared_value))); + fiddler_threads.push_back(new FiddlerThread(new Fiddler( + kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value))); + fiddler_threads.push_back(new FiddlerThread(new Fiddler( + kTimesToLockEach, Fiddler::kTypeTry, true, &mutex, &shared_value))); + } + + for (size_t i = 0; i < kNumThreads; i++) + fiddler_threads[i]->Start(); + + // Do some fiddling ourselves. + Fiddler(kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value) + .Fiddle(); + + // Join. + for (size_t i = 0; i < kNumThreads; i++) + fiddler_threads[i]->Join(); + + EXPECT_EQ(static_cast<int>(kNumThreads + 1) * kTimesToLockEach, shared_value); + + // Delete. + for (size_t i = 0; i < kNumThreads; i++) + delete fiddler_threads[i]; + fiddler_threads.clear(); +} + +class TryThread : public Thread { + public: + explicit TryThread(Mutex* mutex) : mutex_(mutex), try_lock_succeeded_() {} + ~TryThread() override {} + + void Run() override { + try_lock_succeeded_ = mutex_->TryLock(); + if (try_lock_succeeded_) + mutex_->Unlock(); + } + + bool try_lock_succeeded() const { return try_lock_succeeded_; } + + private: + Mutex* const mutex_; + bool try_lock_succeeded_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TryThread); +}; + +TEST(MutexTest, TryLock) { + Mutex mutex; + + // |TryLock()| should succeed -- we don't have the lock. + { + TryThread thread(&mutex); + thread.Start(); + thread.Join(); + EXPECT_TRUE(thread.try_lock_succeeded()); + } + + // Take the lock. + ASSERT_TRUE(mutex.TryLock()); + + // Now it should fail. + { + TryThread thread(&mutex); + thread.Start(); + thread.Join(); + EXPECT_FALSE(thread.try_lock_succeeded()); + } + + // Release the lock. + mutex.Unlock(); + + // It should succeed again. + { + TryThread thread(&mutex); + thread.Start(); + thread.Join(); + EXPECT_TRUE(thread.try_lock_succeeded()); + } +} + + +// Tests of assertions for Debug builds. +#if !defined(NDEBUG) +// Test |AssertHeld()| (which is an actual user API). +TEST(MutexTest, DebugAssertHeldFailure) { + Mutex mutex; + EXPECT_DEATH_IF_SUPPORTED(mutex.AssertHeld(), ""); +} + +// Test other consistency checks. +TEST(MutexTest, DebugAssertionFailures) { + // Unlock without lock held. + EXPECT_DEATH_IF_SUPPORTED({ + Mutex mutex; + mutex.Unlock(); + }, ""); + + // Lock with lock held (on same thread). + EXPECT_DEATH_IF_SUPPORTED({ + Mutex mutex; + mutex.Lock(); + mutex.Lock(); + }, ""); + + // Try lock with lock held. + EXPECT_DEATH_IF_SUPPORTED({ + Mutex mutex; + mutex.Lock(); + mutex.TryLock(); + }, ""); + + // Destroy lock with lock held. + EXPECT_DEATH_IF_SUPPORTED({ + Mutex mutex; + mutex.Lock(); + }, ""); +} +#endif // !defined(NDEBUG) + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/utility/tests/run_loop_unittest.cc b/mojo/public/cpp/utility/tests/run_loop_unittest.cc new file mode 100644 index 0000000..4ab4876 --- /dev/null +++ b/mojo/public/cpp/utility/tests/run_loop_unittest.cc @@ -0,0 +1,425 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/run_loop.h" + +#include <string> + +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/cpp/utility/run_loop_handler.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class TestRunLoopHandler : public RunLoopHandler { + public: + TestRunLoopHandler() + : ready_count_(0), + error_count_(0), + last_error_result_(MOJO_RESULT_OK) { + } + ~TestRunLoopHandler() override {} + + void clear_ready_count() { ready_count_ = 0; } + int ready_count() const { return ready_count_; } + + void clear_error_count() { error_count_ = 0; } + int error_count() const { return error_count_; } + + MojoResult last_error_result() const { return last_error_result_; } + + // RunLoopHandler: + void OnHandleReady(const Handle& handle) override { ready_count_++; } + void OnHandleError(const Handle& handle, MojoResult result) override { + error_count_++; + last_error_result_ = result; + } + + private: + int ready_count_; + int error_count_; + MojoResult last_error_result_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestRunLoopHandler); +}; + +class RunLoopTest : public testing::Test { + public: + RunLoopTest() {} + + void SetUp() override { + Test::SetUp(); + RunLoop::SetUp(); + } + void TearDown() override { + RunLoop::TearDown(); + Test::TearDown(); + } + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoopTest); +}; + +// Trivial test to verify Run() with no added handles returns. +TEST_F(RunLoopTest, ExitsWithNoHandles) { + RunLoop run_loop; + run_loop.Run(); +} + +class RemoveOnReadyRunLoopHandler : public TestRunLoopHandler { + public: + RemoveOnReadyRunLoopHandler() : run_loop_(nullptr) {} + ~RemoveOnReadyRunLoopHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + + // RunLoopHandler: + void OnHandleReady(const Handle& handle) override { + run_loop_->RemoveHandler(handle); + TestRunLoopHandler::OnHandleReady(handle); + } + + private: + RunLoop* run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveOnReadyRunLoopHandler); +}; + +// Verifies RunLoop quits when no more handles (handle is removed when ready). +TEST_F(RunLoopTest, HandleReady) { + RemoveOnReadyRunLoopHandler handler; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + RunLoop run_loop; + handler.set_run_loop(&run_loop); + run_loop.AddHandler(&handler, test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); + run_loop.Run(); + EXPECT_EQ(1, handler.ready_count()); + EXPECT_EQ(0, handler.error_count()); + EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get())); +} + +class QuitOnReadyRunLoopHandler : public TestRunLoopHandler { + public: + QuitOnReadyRunLoopHandler() : run_loop_(nullptr) {} + ~QuitOnReadyRunLoopHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + + // RunLoopHandler: + void OnHandleReady(const Handle& handle) override { + run_loop_->Quit(); + TestRunLoopHandler::OnHandleReady(handle); + } + + private: + RunLoop* run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnReadyRunLoopHandler); +}; + +// Verifies Quit() from OnHandleReady() quits the loop. +TEST_F(RunLoopTest, QuitFromReady) { + QuitOnReadyRunLoopHandler handler; + MessagePipe test_pipe; + EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string())); + + RunLoop run_loop; + handler.set_run_loop(&run_loop); + run_loop.AddHandler(&handler, test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); + run_loop.Run(); + EXPECT_EQ(1, handler.ready_count()); + EXPECT_EQ(0, handler.error_count()); + EXPECT_TRUE(run_loop.HasHandler(test_pipe.handle0.get())); +} + +class QuitOnErrorRunLoopHandler : public TestRunLoopHandler { + public: + QuitOnErrorRunLoopHandler() : run_loop_(nullptr) {} + ~QuitOnErrorRunLoopHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + + // RunLoopHandler: + void OnHandleError(const Handle& handle, MojoResult result) override { + run_loop_->Quit(); + TestRunLoopHandler::OnHandleError(handle, result); + } + + private: + RunLoop* run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnErrorRunLoopHandler); +}; + +// Verifies Quit() when the deadline is reached works. +TEST_F(RunLoopTest, QuitWhenDeadlineExpired) { + QuitOnErrorRunLoopHandler handler; + MessagePipe test_pipe; + RunLoop run_loop; + handler.set_run_loop(&run_loop); + run_loop.AddHandler(&handler, test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + static_cast<MojoDeadline>(10000)); + run_loop.Run(); + EXPECT_EQ(0, handler.ready_count()); + EXPECT_EQ(1, handler.error_count()); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, handler.last_error_result()); + EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get())); +} + +// Test that handlers are notified of loop destruction. +TEST_F(RunLoopTest, Destruction) { + TestRunLoopHandler handler; + MessagePipe test_pipe; + { + RunLoop run_loop; + run_loop.AddHandler(&handler, + test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } + EXPECT_EQ(1, handler.error_count()); + EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result()); +} + +class RemoveManyRunLoopHandler : public TestRunLoopHandler { + public: + RemoveManyRunLoopHandler() : run_loop_(nullptr) {} + ~RemoveManyRunLoopHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + void add_handle(const Handle& handle) { handles_.push_back(handle); } + + // RunLoopHandler: + void OnHandleError(const Handle& handle, MojoResult result) override { + for (size_t i = 0; i < handles_.size(); i++) + run_loop_->RemoveHandler(handles_[i]); + TestRunLoopHandler::OnHandleError(handle, result); + } + + private: + std::vector<Handle> handles_; + RunLoop* run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveManyRunLoopHandler); +}; + +// Test that handlers are notified of loop destruction. +TEST_F(RunLoopTest, MultipleHandleDestruction) { + RemoveManyRunLoopHandler odd_handler; + TestRunLoopHandler even_handler; + MessagePipe test_pipe1, test_pipe2, test_pipe3; + { + RunLoop run_loop; + odd_handler.set_run_loop(&run_loop); + odd_handler.add_handle(test_pipe1.handle0.get()); + odd_handler.add_handle(test_pipe3.handle0.get()); + run_loop.AddHandler(&odd_handler, + test_pipe1.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + run_loop.AddHandler(&even_handler, + test_pipe2.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + run_loop.AddHandler(&odd_handler, + test_pipe3.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } + EXPECT_EQ(1, odd_handler.error_count()); + EXPECT_EQ(1, even_handler.error_count()); + EXPECT_EQ(MOJO_RESULT_ABORTED, odd_handler.last_error_result()); + EXPECT_EQ(MOJO_RESULT_ABORTED, even_handler.last_error_result()); +} + +class AddHandlerOnErrorHandler : public TestRunLoopHandler { + public: + AddHandlerOnErrorHandler() : run_loop_(nullptr) {} + ~AddHandlerOnErrorHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + + // RunLoopHandler: + void OnHandleError(const Handle& handle, MojoResult result) override { + run_loop_->AddHandler(this, handle, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + TestRunLoopHandler::OnHandleError(handle, result); + } + + private: + RunLoop* run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(AddHandlerOnErrorHandler); +}; + +TEST_F(RunLoopTest, AddHandlerOnError) { + AddHandlerOnErrorHandler handler; + MessagePipe test_pipe; + { + RunLoop run_loop; + handler.set_run_loop(&run_loop); + run_loop.AddHandler(&handler, + test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } + EXPECT_EQ(1, handler.error_count()); + EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result()); +} + +TEST_F(RunLoopTest, Current) { + EXPECT_TRUE(RunLoop::current() == nullptr); + { + RunLoop run_loop; + EXPECT_EQ(&run_loop, RunLoop::current()); + } + EXPECT_TRUE(RunLoop::current() == nullptr); +} + +class NestingRunLoopHandler : public TestRunLoopHandler { + public: + static const size_t kDepthLimit; + static const char kSignalMagic; + + NestingRunLoopHandler() + : run_loop_(nullptr), + pipe_(nullptr), + depth_(0), + reached_depth_limit_(false) {} + + ~NestingRunLoopHandler() override {} + + void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; } + void set_pipe(MessagePipe* pipe) { pipe_ = pipe; } + bool reached_depth_limit() const { return reached_depth_limit_; } + + // RunLoopHandler: + void OnHandleReady(const Handle& handle) override { + TestRunLoopHandler::OnHandleReady(handle); + EXPECT_EQ(handle.value(), pipe_->handle0.get().value()); + + ReadSignal(); + size_t current_depth = ++depth_; + if (current_depth < kDepthLimit) { + WriteSignal(); + run_loop_->Run(); + if (current_depth == kDepthLimit - 1) { + // The topmost loop Quit()-ed, so its parent takes back the + // control without exeeding deadline. + EXPECT_EQ(error_count(), 0); + } else { + EXPECT_EQ(error_count(), 1); + } + + } else { + EXPECT_EQ(current_depth, kDepthLimit); + reached_depth_limit_ = true; + run_loop_->Quit(); + } + --depth_; + } + + void WriteSignal() { + char write_byte = kSignalMagic; + MojoResult write_result = + WriteMessageRaw(pipe_->handle1.get(), &write_byte, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + EXPECT_EQ(write_result, MOJO_RESULT_OK); + } + + void ReadSignal() { + char read_byte = 0; + uint32_t bytes_read = 1; + uint32_t handles_read = 0; + MojoResult read_result = + ReadMessageRaw(pipe_->handle0.get(), &read_byte, &bytes_read, nullptr, + &handles_read, MOJO_READ_MESSAGE_FLAG_NONE); + EXPECT_EQ(read_result, MOJO_RESULT_OK); + EXPECT_EQ(read_byte, kSignalMagic); + } + + private: + RunLoop* run_loop_; + MessagePipe* pipe_; + size_t depth_; + bool reached_depth_limit_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(NestingRunLoopHandler); +}; + +const size_t NestingRunLoopHandler::kDepthLimit = 10; +const char NestingRunLoopHandler::kSignalMagic = 'X'; + +TEST_F(RunLoopTest, NestedRun) { + NestingRunLoopHandler handler; + MessagePipe test_pipe; + RunLoop run_loop; + handler.set_run_loop(&run_loop); + handler.set_pipe(&test_pipe); + run_loop.AddHandler(&handler, test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + static_cast<MojoDeadline>(10000)); + handler.WriteSignal(); + run_loop.Run(); + + EXPECT_TRUE(handler.reached_depth_limit()); + // Got MOJO_RESULT_DEADLINE_EXCEEDED once then removed from the + // RunLoop's handler list. + EXPECT_EQ(handler.error_count(), 1); + EXPECT_EQ(handler.last_error_result(), MOJO_RESULT_DEADLINE_EXCEEDED); +} + +struct Task { + Task(int num, std::vector<int>* sequence) : num(num), sequence(sequence) {} + + void Run() const { sequence->push_back(num); } + + int num; + std::vector<int>* sequence; +}; + +TEST_F(RunLoopTest, DelayedTaskOrder) { + std::vector<int> sequence; + RunLoop run_loop; + run_loop.PostDelayedTask(Closure(Task(1, &sequence)), 0); + run_loop.PostDelayedTask(Closure(Task(2, &sequence)), 0); + run_loop.PostDelayedTask(Closure(Task(3, &sequence)), 0); + run_loop.RunUntilIdle(); + + ASSERT_EQ(3u, sequence.size()); + EXPECT_EQ(1, sequence[0]); + EXPECT_EQ(2, sequence[1]); + EXPECT_EQ(3, sequence[2]); +} + +struct QuittingTask { + explicit QuittingTask(RunLoop* run_loop) : run_loop(run_loop) {} + + void Run() const { run_loop->Quit(); } + + RunLoop* run_loop; +}; + +TEST_F(RunLoopTest, QuitFromDelayedTask) { + TestRunLoopHandler handler; + MessagePipe test_pipe; + RunLoop run_loop; + run_loop.AddHandler(&handler, + test_pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + run_loop.PostDelayedTask(Closure(QuittingTask(&run_loop)), 0); + run_loop.Run(); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/utility/tests/thread_unittest.cc b/mojo/public/cpp/utility/tests/thread_unittest.cc new file mode 100644 index 0000000..57c4ad9 --- /dev/null +++ b/mojo/public/cpp/utility/tests/thread_unittest.cc @@ -0,0 +1,106 @@ +// Copyright 2014 The Chromium 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 "mojo/public/cpp/utility/thread.h" + +#include "mojo/public/cpp/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class SetIntThread : public Thread { + public: + SetIntThread(int* int_to_set, int value) + : int_to_set_(int_to_set), + value_(value) { + } + SetIntThread(const Options& options, int* int_to_set, int value) + : Thread(options), + int_to_set_(int_to_set), + value_(value) { + } + + ~SetIntThread() override {} + + void Run() override { *int_to_set_ = value_; } + + private: + int* const int_to_set_; + const int value_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(SetIntThread); +}; + +TEST(ThreadTest, CreateAndJoin) { + int value = 0; + + // Not starting the thread should result in a no-op. + { + SetIntThread thread(&value, 1234567); + } + EXPECT_EQ(0, value); + + // Start and join. + { + SetIntThread thread(&value, 12345678); + thread.Start(); + thread.Join(); + EXPECT_EQ(12345678, value); + } + + // Ditto, with non-default (but reasonable) stack size. + { + Thread::Options options; + options.set_stack_size(1024 * 1024); // 1 MB. + SetIntThread thread(options, &value, 12345678); + thread.Start(); + thread.Join(); + EXPECT_EQ(12345678, value); + } +} + +// Tests of assertions for Debug builds. +// Note: It's okay to create threads, despite gtest having to fork. (The threads +// are in the child process.) +#if !defined(NDEBUG) +TEST(ThreadTest, DebugAssertionFailures) { + // Can only start once. + EXPECT_DEATH_IF_SUPPORTED({ + int value = 0; + SetIntThread thread(&value, 1); + thread.Start(); + thread.Start(); + }, ""); + + // Must join (if you start). + EXPECT_DEATH_IF_SUPPORTED({ + int value = 0; + SetIntThread thread(&value, 2); + thread.Start(); + }, ""); + + // Can only join once. + EXPECT_DEATH_IF_SUPPORTED({ + int value = 0; + SetIntThread thread(&value, 3); + thread.Start(); + thread.Join(); + thread.Join(); + }, ""); + + // Stack too big (we're making certain assumptions here). + EXPECT_DEATH_IF_SUPPORTED({ + int value = 0; + Thread::Options options; + options.set_stack_size(static_cast<size_t>(-1)); + SetIntThread thread(options, &value, 4); + thread.Start(); + thread.Join(); + }, ""); +} +#endif // !defined(NDEBUG) + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/utility/thread.h b/mojo/public/cpp/utility/thread.h new file mode 100644 index 0000000..b7d10ee --- /dev/null +++ b/mojo/public/cpp/utility/thread.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ +#define MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ + +#ifdef _WIN32 +#error "Not implemented: See crbug.com/342893." +#endif + +#include <pthread.h> +#include <stddef.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// This class is thread-friendly, not thread-safe (e.g., you mustn't call +// |Join()| from multiple threads and/or simultaneously try to destroy the +// object). +class Thread { + public: + // TODO(vtl): Support non-joinable? priority? + class Options { + public: + Options() : stack_size_(0) {} + + // A stack size of 0 means the default. + size_t stack_size() const { return stack_size_; } + void set_stack_size(size_t stack_size) { stack_size_ = stack_size; } + + private: + size_t stack_size_; + + // Copy and assign allowed. + }; + + // TODO(vtl): Add name or name prefix? + Thread(); + explicit Thread(const Options& options); + virtual ~Thread(); + + void Start(); + void Join(); + + virtual void Run() = 0; + + private: + static void* ThreadRunTrampoline(void* arg); + + const Options options_; + pthread_t thread_; + bool started_; + bool joined_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Thread); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ |