// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef JNI_ZERO_JNI_WRAPPERS_H_ #define JNI_ZERO_JNI_WRAPPERS_H_ #include #include #include #include "third_party/jni_zero/java_refs.h" #include "third_party/jni_zero/logging.h" // Wrapper used to receive int when calling Java from native. // The wrapper disallows automatic conversion of long to int. // This is to avoid a common anti-pattern where a Java int is used // to receive a native pointer. Please use a Java long to receive // native pointers, so that the code works on both 32-bit and 64-bit // platforms. Note the wrapper allows other lossy conversions into // jint that could be consider anti-patterns, such as from size_t. // Checking is only done in debugging builds. #ifdef NDEBUG typedef jint JniIntWrapper; // This inline is sufficiently trivial that it does not change the // final code generated by g++. inline jint as_jint(JniIntWrapper wrapper) { return wrapper; } #else class JniIntWrapper { public: JniIntWrapper() : i_(0) {} JniIntWrapper(int i) : i_(i) {} JniIntWrapper(const JniIntWrapper& ji) : i_(ji.i_) {} template JniIntWrapper(const T& t) : i_(t) {} jint as_jint() const { return i_; } private: // If you get an "is private" error at the line below it is because you used // an implicit conversion to convert a long to an int when calling Java. // We disallow this, as a common anti-pattern allows converting a native // pointer (intptr_t) to a Java int. Please use a Java long to represent // a native pointer. If you want a lossy conversion, please use an // explicit conversion in your C++ code. Note an error is only seen when // compiling on a 64-bit platform, as intptr_t is indistinguishable from // int on 32-bit platforms. JniIntWrapper(long); jint i_; }; inline jint as_jint(const JniIntWrapper& wrapper) { return wrapper.as_jint(); } #endif // NDEBUG namespace jni_zero { // Wrapper for a jobjectArray which supports input iteration, allowing Java // arrays to be iterated over with a range-based for loop, or used with // functions that accept input iterators. // // The iterator returns each object in the array in turn, wrapped in a // ScopedJavaLocalRef. T will usually be jobject, but if you know that the // array contains a more specific type (such as jstring) you can use that // instead. This does not check the type at runtime! // // The wrapper holds a local reference to the array and only queries the size of // the array once, so must only be used as a stack-based object from the current // thread. // // Note that this does *not* update the contents of the array if you mutate the // returned ScopedJavaLocalRef. template class JavaObjectArrayReader { public: class iterator { public: // We can only be an input iterator, as all richer iterator types must // implement the multipass guarantee (always returning the same object for // the same iterator position), which is not practical when returning // temporary objects. using iterator_category = std::input_iterator_tag; using difference_type = ptrdiff_t; using value_type = ScopedJavaLocalRef; // It doesn't make sense to return a reference type as the iterator creates // temporary wrapper objects when dereferenced. Fortunately, it's not // required that input iterators actually use references, and defining it // as value_type is valid. using reference = value_type; // This exists to make operator-> work as expected: its return value must // resolve to an actual pointer (otherwise the compiler just keeps calling // operator-> on the return value until it does), so we need an extra level // of indirection. This is sometimes called an "arrow proxy" or similar, and // this version is adapted from base/value_iterators.h. class pointer { public: explicit pointer(const reference& ref) : ref_(ref) {} pointer(const pointer& ptr) = default; pointer& operator=(const pointer& ptr) = delete; reference* operator->() { return &ref_; } private: reference ref_; }; iterator(const iterator&) = default; ~iterator() = default; iterator& operator=(const iterator&) = default; bool operator==(const iterator& other) const { JNI_ZERO_DCHECK(reader_ == other.reader_); return i_ == other.i_; } bool operator!=(const iterator& other) const { JNI_ZERO_DCHECK(reader_ == other.reader_); return i_ != other.i_; } reference operator*() const { JNI_ZERO_DCHECK(i_ < reader_->size_); // JNIEnv functions return unowned local references; take ownership with // Adopt so that ~ScopedJavaLocalRef will release it automatically later. return value_type::Adopt( reader_->array_.env_, static_cast(reader_->array_.env_->GetObjectArrayElement( reader_->array_.obj(), i_))); } pointer operator->() const { return pointer(operator*()); } iterator& operator++() { JNI_ZERO_DCHECK(i_ < reader_->size_); ++i_; return *this; } iterator operator++(int) { iterator old = *this; ++*this; return old; } private: iterator(const JavaObjectArrayReader* reader, jsize i) : reader_(reader), i_(i) {} const JavaObjectArrayReader* reader_; jsize i_; friend JavaObjectArrayReader; }; JavaObjectArrayReader(const JavaRef& array) : array_(array) { size_ = array_.env_->GetArrayLength(array_.obj()); } // Copy constructor to allow returning it from JavaRef::ReadElements(). JavaObjectArrayReader(const JavaObjectArrayReader& other) = default; // Assignment operator for consistency with copy constructor. JavaObjectArrayReader& operator=(const JavaObjectArrayReader& other) = default; // Allow move constructor and assignment since this owns a local ref. JavaObjectArrayReader(JavaObjectArrayReader&& other) = default; JavaObjectArrayReader& operator=(JavaObjectArrayReader&& other) = default; bool empty() const { return size_ == 0; } jsize size() const { return size_; } iterator begin() const { return iterator(this, 0); } iterator end() const { return iterator(this, size_); } private: ScopedJavaLocalRef array_; jsize size_; friend iterator; }; // Use as: @JniType("jni_zero::ByteArrayView") byte[]. // // This requests a direct pointer to the array data rather than a copy of it, // so can be more efficient than std::vector for large arrays. // // This helper needs to release the array via its destructor, and as a result // has more binary size overhead than using std::vector. As such, you // should prefer std::vector for small arrays. // // Callers must ensure that the passed in array reference outlives this wrapper // (always the case when used with @JniType). class ByteArrayView { public: ByteArrayView(JNIEnv* env, jbyteArray array) : env_(env), array_(array), length_(env->GetArrayLength(array)), bytes_(env->GetByteArrayElements(array, nullptr)) {} ~ByteArrayView() { env_->ReleaseByteArrayElements(array_, bytes_, JNI_ABORT); } ByteArrayView(const ByteArrayView&) = delete; ByteArrayView(ByteArrayView&& other) = delete; ByteArrayView& operator=(const ByteArrayView&) = delete; size_t size() const { return static_cast(length_); } bool empty() const { return length_ == 0; } const jbyte* bytes() const { return bytes_; } const uint8_t* data() const { return reinterpret_cast(bytes_); } const char* chars() const { return reinterpret_cast(bytes_); } std::string_view string_view() const { return std::string_view(chars(), size()); } private: JNIEnv* env_; jbyteArray array_; jsize length_; jbyte* bytes_; }; } // namespace jni_zero #endif // JNI_ZERO_JNI_WRAPPERS_H_