xref: /aosp_15_r20/external/fbjni/test/jni/hybrid_tests.cpp (revision 65c59e023c5336bbd4a23be7af78407e3d80e7e7)
1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <fbjni/fbjni.h>
18 #include <condition_variable>
19 #include <mutex>
20 
21 using namespace facebook::jni;
22 
23 class TestException : public std::runtime_error {
24  public:
TestException()25   TestException() : std::runtime_error("fail") {}
26 };
27 
28 // The C++ half of a hybrid class must extend HybridClass like this.
29 // HybridClass is default constructible.
30 class TestHybridClass : public facebook::jni::HybridClass<TestHybridClass> {
31  public:
32   static constexpr const char* const kJavaDescriptor =
33       "Lcom/facebook/jni/HybridTests$TestHybridClass;";
34 
TestHybridClass()35   TestHybridClass() {}
36 
TestHybridClass(int i,std::string s,bool b)37   TestHybridClass(int i, std::string s, bool b)
38       : i_(i), s_(std::move(s)), b_(b) {}
39 
40   // The C++ implementation of initHybrid must be a static C++ method,
41   // since it has no C++ instance (it creates it).  But it's a normal
42   // java method, because it gets a java this passed in (which is
43   // often unused).  It must call makeCxxInstance and return its
44   // result.
45   static jhybriddata
initHybrid(alias_ref<jhybridobject>,jint i,jstring s,jboolean b)46   initHybrid(alias_ref<jhybridobject>, jint i, jstring s, jboolean b) {
47     // The arguments will be forwarded to the ctor, and the result
48     // will be saved in mHybridPointer in the java object.
49     return makeCxxInstance(i, wrap_alias(s)->toStdString(), b).release();
50   }
initHybrid2(alias_ref<jhybridobject>)51   static local_ref<jhybriddata> initHybrid2(alias_ref<jhybridobject>) {
52     return makeCxxInstance();
53   }
54   // It's also ok to make initHybrid work like a factory, which
55   // eliminates the need to define a ctor and reduces boilerplate
56   // code.  This is a member, so fields can be initialized directly.
57   static local_ref<jhybriddata>
initHybrid3(alias_ref<jhybridobject>,std::string s,int i,bool b)58   initHybrid3(alias_ref<jhybridobject>, std::string s, int i, bool b) {
59     auto cxxPart = std::unique_ptr<TestHybridClass>(new TestHybridClass);
60     cxxPart->s_ = std::move(s);
61     cxxPart->i_ = i;
62     cxxPart->b_ = b;
63     return makeHybridData(std::move(cxxPart));
64   }
65 
getInt()66   jint getInt() {
67     return i_;
68   }
69 
getString()70   std::string getString() {
71     return s_;
72   }
73 
getCharString()74   const char* getCharString() {
75     return s_.c_str();
76   }
77 
setBoth(int i,std::string s)78   void setBoth(int i, std::string s) {
79     i_ = i;
80     s_ = s;
81   }
82 
copy1(alias_ref<jhybridobject> other)83   bool copy1(alias_ref<jhybridobject> other) {
84     i_ = cthis(other)->i_;
85     s_ = cthis(other)->s_;
86     return true;
87   }
88 
89   // This returns a boolean to test more code paths, not for any
90   // functional reason.
copy2(TestHybridClass * other)91   jboolean copy2(TestHybridClass* other) {
92     i_ = other->i_;
93     s_ = other->s_;
94     return true;
95   }
96 
oops()97   void oops() {
98     throw TestException();
99   }
100 
setGlobal(alias_ref<jstring> str)101   void setGlobal(alias_ref<jstring> str) {
102     global_ = make_global(str);
103   }
104 
getGlobal1()105   global_ref<jstring> getGlobal1() {
106     return global_;
107   }
108 
getGlobal2()109   const global_ref<jstring>& getGlobal2() {
110     return global_;
111   }
112 
makeWithTwo(alias_ref<jclass> jthis)113   static jhybridobject makeWithTwo(alias_ref<jclass> jthis) {
114     // The args are passed to java, with autoconversion.
115     return newObjectJavaArgs(2, "two", true).release();
116   }
117 
makeWithThree(alias_ref<jclass> jthis)118   static local_ref<jhybridobject> makeWithThree(alias_ref<jclass> jthis) {
119     // The args are passed to C++ initialization directly.
120     return newObjectCxxArgs(3, "three", true);
121   }
122 
mapException(std::exception_ptr ex)123   static void mapException(std::exception_ptr ex) {
124     try {
125       std::rethrow_exception(ex);
126     } catch (const TestException& ex) {
127       throwNewJavaException("java/lang/ArrayStoreException", "");
128     }
129   }
130 
autoconvertMany(alias_ref<jclass>)131   static void autoconvertMany(alias_ref<jclass>) {
132     // Autoconversion to jstring creates a local ref.  Make sure is is
133     // released properly.  The default local table size is 512.
134     for (int i = 0; i < 1000; ++i) {
135       newObjectJavaArgs(1000, "thousand", false);
136       newObjectJavaArgs(1001, make_jstring("thousand+1"), false);
137     }
138   }
139 
140   // Declaring a register function for each class makes it easy to
141   // manage the process.
registerNatives()142   static void registerNatives() {
143     registerHybrid({
144         // Test registration of static method returning void.
145         makeNativeMethod("initHybrid", TestHybridClass::initHybrid),
146         // overloaded members are ugly to get pointers to.  Use a
147         // different name for each C++ method to keep things readable.
148         makeNativeMethod("initHybrid", TestHybridClass::initHybrid2),
149         makeNativeMethod("initHybrid", TestHybridClass::initHybrid3),
150         // C++ members can be registered directly.  The C++ instance
151         // created by makeCxxInstance will be retrieved from the
152         // HybridData, and the method will be invoked with the
153         // arguments passed to java.  These test registration of
154         // method pointers returning non-void.
155         makeNativeMethod("getInt", TestHybridClass::getInt),
156         makeNativeMethod("getString", TestHybridClass::getString),
157         makeNativeMethod("getCharString", TestHybridClass::getCharString),
158         makeNativeMethod("setBoth", TestHybridClass::setBoth),
159         makeNativeMethod("copy1", TestHybridClass::copy1),
160         makeNativeMethod("copy2", TestHybridClass::copy2),
161         makeNativeMethod("oops", TestHybridClass::oops),
162         makeNativeMethod("setGlobal", TestHybridClass::setGlobal),
163         makeNativeMethod("getGlobal1", TestHybridClass::getGlobal1),
164         makeNativeMethod("getGlobal2", TestHybridClass::getGlobal2),
165         makeNativeMethod("makeWithTwo", TestHybridClass::makeWithTwo),
166         makeNativeMethod("makeWithThree", TestHybridClass::makeWithThree),
167         makeNativeMethod("autoconvertMany", TestHybridClass::autoconvertMany),
168     });
169   }
170 
171  private:
172   friend HybridBase;
173 
174   int i_ = 0;
175   std::string s_;
176   bool b_ = false;
177   global_ref<jstring> global_;
178 };
179 
180 class AbstractTestHybrid
181     : public facebook::jni::HybridClass<AbstractTestHybrid> {
182  public:
183   static constexpr const char* const kJavaDescriptor =
184       "Lcom/facebook/jni/HybridTests$AbstractTestHybrid;";
185 
AbstractTestHybrid(int nn)186   AbstractTestHybrid(int nn) : nativeNum_(nn) {}
187 
nativeNum()188   int nativeNum() {
189     return nativeNum_;
190   }
191   // This is different than the java method!
192   virtual int sum() = 0;
193 
registerNatives()194   static void registerNatives() {
195     registerHybrid({
196         makeNativeMethod("nativeNum", AbstractTestHybrid::nativeNum),
197     });
198   }
199 
200  private:
201   int nativeNum_;
202 };
203 
204 class ConcreteTestHybrid
205     : public facebook::jni::
206           HybridClass<ConcreteTestHybrid, AbstractTestHybrid> {
207  public:
208   static constexpr const char* const kJavaDescriptor =
209       "Lcom/facebook/jni/HybridTests$ConcreteTestHybrid;";
210 
initHybrid(alias_ref<jclass>,int nn,int cn)211   static local_ref<jhybriddata> initHybrid(alias_ref<jclass>, int nn, int cn) {
212     return makeCxxInstance(nn, cn);
213   }
214 
concreteNum()215   int concreteNum() {
216     return concreteNum_;
217   }
sum()218   int sum() override {
219     return nativeNum() + concreteNum();
220   }
221 
registerNatives()222   static void registerNatives() {
223     registerHybrid({
224         makeNativeMethod("initHybrid", ConcreteTestHybrid::initHybrid),
225         makeNativeMethod("concreteNum", ConcreteTestHybrid::concreteNum),
226     });
227   }
228 
229  private:
230   friend HybridBase;
ConcreteTestHybrid(int nn,int cn)231   ConcreteTestHybrid(int nn, int cn) : HybridBase(nn), concreteNum_(cn) {}
232 
233   int concreteNum_;
234 };
235 
cxxTestInheritance(alias_ref<jclass>,AbstractTestHybrid * ath)236 static jboolean cxxTestInheritance(alias_ref<jclass>, AbstractTestHybrid* ath) {
237   bool ret = true;
238 
239   ret &= ath->nativeNum() == 5;
240   ret &= ath->sum() == 11;
241 
242   auto cth = dynamic_cast<ConcreteTestHybrid*>(ath);
243 
244   ret &= cth != nullptr;
245   ret &= cth->concreteNum() == 6;
246 
247   return ret;
248 }
249 
makeAbstractHybrid(alias_ref<jclass>)250 static local_ref<AbstractTestHybrid::jhybridobject> makeAbstractHybrid(
251     alias_ref<jclass>) {
252   auto cth = ConcreteTestHybrid::newObjectJavaArgs(0, 0, 0);
253   weak_ref<ConcreteTestHybrid::jhybridobject> wcth = make_weak(cth);
254   weak_ref<AbstractTestHybrid::jhybridobject> wath = wcth;
255 
256   local_ref<AbstractTestHybrid::jhybridobject> ath = cth;
257   return ConcreteTestHybrid::newObjectJavaArgs(
258       cthis(ath)->nativeNum() + 7, 8, 9);
259 }
260 
261 struct Base : public JavaClass<Base> {
262   static constexpr const char* const kJavaDescriptor =
263       "Lcom/facebook/jni/HybridTests$Base;";
264 };
265 
266 class Derived : public HybridClass<Derived, Base> {
267  public:
268   static constexpr const char* const kJavaDescriptor =
269       "Lcom/facebook/jni/HybridTests$Derived;";
270 
271  private:
272   friend HybridBase;
Derived()273   Derived() {}
274 };
275 
276 struct Destroyable : public HybridClass<Destroyable> {
277  public:
278   static constexpr const char* const kJavaDescriptor =
279       "Lcom/facebook/jni/HybridTests$Destroyable;";
280 
~DestroyableDestroyable281   ~Destroyable() override {
282     std::lock_guard<std::mutex> lk(*mtx_);
283     *dead_ = true;
284     cv_->notify_one();
285   }
286 
287   using HybridBase::makeCxxInstance;
288 
289  private:
290   friend HybridBase;
DestroyableDestroyable291   Destroyable(
292       std::shared_ptr<std::mutex> mtx,
293       std::shared_ptr<bool> dead,
294       std::shared_ptr<std::condition_variable> cv)
295       : mtx_(mtx), dead_(dead), cv_(cv) {}
296 
297   std::shared_ptr<std::mutex> mtx_;
298   std::shared_ptr<bool> dead_;
299   std::shared_ptr<std::condition_variable> cv_;
300 };
301 
cxxTestHybridDestruction(alias_ref<jclass>)302 static jboolean cxxTestHybridDestruction(alias_ref<jclass>) {
303   auto mtx = std::make_shared<std::mutex>();
304   auto dead = std::make_shared<bool>(false);
305   auto cv = std::make_shared<std::condition_variable>();
306   Destroyable::makeCxxInstance(mtx, dead, cv);
307   std::unique_lock<std::mutex> lk(*mtx);
308   cv->wait(lk, [&] { return dead; });
309   return JNI_TRUE;
310 }
311 
cxxTestDerivedJavaClass(alias_ref<jclass>)312 static jboolean cxxTestDerivedJavaClass(alias_ref<jclass>) {
313   bool ret = true;
314 
315   auto derivedJava = Derived::newObjectCxxArgs();
316 
317   ret &= derivedJava ? true : false;
318   ret &= derivedJava->isInstanceOf(Derived::javaClassLocal());
319   ret &= derivedJava->isInstanceOf(Base::javaClassLocal());
320 
321   auto javaPtr = derivedJava.get();
322 
323   ret &= dynamic_cast<Derived::javaobject>(javaPtr) != nullptr;
324   ret &= dynamic_cast<Base::javaobject>(javaPtr) != nullptr;
325   ret &= dynamic_cast<jobject>(javaPtr) != nullptr;
326 
327   return ret;
328 }
329 
330 class TestHybridClassBase
331     : public facebook::jni::HybridClass<TestHybridClassBase> {
332  public:
333   static constexpr const char* const kJavaDescriptor =
334       "Lcom/facebook/jni/HybridTests$TestHybridClassBase;";
335 
TestHybridClassBase()336   TestHybridClassBase() {}
337 
TestHybridClassBase(int i)338   explicit TestHybridClassBase(int i) : i_(i) {}
339 
initHybrid(alias_ref<jhybridobject> o,jint i)340   static void initHybrid(alias_ref<jhybridobject> o, jint i) {
341     // The arguments will be forwarded to the ctor, and the result
342     // will be saved in mHybridPointer in the java object.
343     setCxxInstance(o, i);
344   }
345 
initHybrid2(alias_ref<jhybridobject> o)346   static void initHybrid2(alias_ref<jhybridobject> o) {
347     setCxxInstance(o);
348   }
349 
getInt()350   virtual jint getInt() {
351     return i_;
352   }
353 
setInt(int i)354   void setInt(int i) {
355     i_ = i;
356   }
357 
makeWithThree(alias_ref<jclass>)358   static local_ref<jhybridobject> makeWithThree(
359       alias_ref<jclass> /* unused */) {
360     // The args are passed to C++ initialization directly.
361     return newObjectCxxArgs(3);
362   }
363 
registerNatives()364   static void registerNatives() {
365     registerHybrid({
366         makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid),
367         makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid2),
368         makeNativeMethod("getInt", TestHybridClassBase::getInt),
369         makeNativeMethod("setInt", TestHybridClassBase::setInt),
370         makeNativeMethod("makeWithThree", TestHybridClassBase::makeWithThree),
371     });
372   }
373 
374  private:
375   int i_ = 0;
376 };
377 
RegisterTestHybridClass()378 void RegisterTestHybridClass() {
379   TestHybridClass::registerNatives();
380   AbstractTestHybrid::registerNatives();
381   ConcreteTestHybrid::registerNatives();
382   TestHybridClassBase::registerNatives();
383 
384   registerNatives(
385       "com/facebook/jni/HybridTests",
386       {
387           makeNativeMethod("cxxTestInheritance", cxxTestInheritance),
388           makeNativeMethod("makeAbstractHybrid", makeAbstractHybrid),
389           makeNativeMethod("cxxTestDerivedJavaClass", cxxTestDerivedJavaClass),
390           makeNativeMethod(
391               "cxxTestHybridDestruction", cxxTestHybridDestruction),
392       });
393 }
394