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 package com.facebook.jni; 18 19 import static org.assertj.core.api.Assertions.assertThat; 20 21 import com.facebook.jni.annotations.DoNotStrip; 22 import org.junit.Test; 23 24 public class HybridTests extends BaseFBJniTests { 25 static class TestHybridClass { 26 // Hybrid classes must include a member which manages the C++ object. It 27 // will be initialized from C++. It must be declared exactly with this 28 // type and name, so JNI can find it, and initialized once in the ctor. 29 // The annotation is necessary to keep proguard from renaming it, or else JNI 30 // won't be able to find it. 31 @DoNotStrip private final HybridData mHybridData; 32 33 // This is the method which creates the C++ instance and initializes 34 // mHybridData. Conventionally, it should be named initHybrid, and invoked 35 // from the constructor. This must be called only once. If the C++ 36 // instance is referenced before this is called, a NullPointerException 37 // will be thrown. initHybrid(int i, String s, boolean b)38 private native HybridData initHybrid(int i, String s, boolean b); 39 40 // You can have more than one, which may be useful if the ctor is 41 // overloaded. This will call the default C++ ctor. initHybrid()42 private native HybridData initHybrid(); 43 44 // Implements factory-style initialization. You shouldn't usually 45 // need both styles in one class. Here we do it for testing and 46 // demo purposes. initHybrid(String s, int i, boolean b)47 private native HybridData initHybrid(String s, int i, boolean b); 48 49 // Java ctor must invoke initHybrid(). This just passes arguments through, 50 // but the ctor can do whatever work it wants, as long as it calls 51 // initHybrid() before any native methods. TestHybridClass(int i, String s, boolean b)52 public TestHybridClass(int i, String s, boolean b) { 53 mHybridData = initHybrid(i, s, b); 54 } 55 56 // This behaves the same as the ctor above, I just wanted a different 57 // signature to demonstrate factory-style initialization. TestHybridClass(String s, int i, boolean b)58 public TestHybridClass(String s, int i, boolean b) { 59 mHybridData = initHybrid(s, i, b); 60 } 61 62 // This is the simplest case. Even if everything is default, initHybrid() 63 // must still be called. TestHybridClass()64 public TestHybridClass() { 65 mHybridData = initHybrid(); 66 } 67 68 // Java ctor used by C++ newObjectCxxArgs. Note this is private. TestHybridClass(HybridData hd)69 private TestHybridClass(HybridData hd) { 70 mHybridData = hd; 71 } 72 doneUsingIt()73 public void doneUsingIt() { 74 mHybridData.resetNative(); 75 } 76 77 // Some C++ methods. setBoth(int i, String s)78 public native void setBoth(int i, String s); 79 getInt()80 public native int getInt(); 81 getString()82 public native String getString(); 83 getCharString()84 public native String getCharString(); 85 copy1(TestHybridClass other)86 public native boolean copy1(TestHybridClass other); 87 copy2(TestHybridClass other)88 public native boolean copy2(TestHybridClass other); 89 oops()90 public native void oops(); 91 setGlobal(String s)92 public native void setGlobal(String s); 93 getGlobal1()94 public native String getGlobal1(); 95 getGlobal2()96 public native String getGlobal2(); 97 makeWithTwo()98 public static native TestHybridClass makeWithTwo(); 99 makeWithThree()100 public static native TestHybridClass makeWithThree(); 101 autoconvertMany()102 public static native void autoconvertMany(); 103 } 104 105 @Test testHybridClass()106 public void testHybridClass() { 107 TestHybridClass thc1 = new TestHybridClass(); 108 assertThat(thc1.getInt()).isEqualTo(0); 109 assertThat(thc1.getString()).isEqualTo(""); 110 111 thc1.setBoth(1, "one"); 112 assertThat(thc1.getInt()).isEqualTo(1); 113 assertThat(thc1.getString()).isEqualTo("one"); 114 115 TestHybridClass thc2 = TestHybridClass.makeWithTwo(); 116 assertThat(thc2.getInt()).isEqualTo(2); 117 assertThat(thc2.getString()).isEqualTo("two"); 118 119 thc2.doneUsingIt(); 120 121 thrown.expect(NullPointerException.class); 122 thc2.getInt(); 123 } 124 125 @Test testHybridAutoconversion()126 public void testHybridAutoconversion() { 127 TestHybridClass thc3 = TestHybridClass.makeWithThree(); 128 assertThat(thc3.copy1(new TestHybridClass(3, "three", false))).isTrue(); 129 assertThat(thc3.getInt()).isEqualTo(3); 130 assertThat(thc3.getString()).isEqualTo("three"); 131 132 TestHybridClass thc4 = new TestHybridClass(); 133 thc4.copy1(new TestHybridClass("four", 4, false)); 134 assertThat(thc4.getInt()).isEqualTo(4); 135 assertThat(thc4.getString()).isEqualTo("four"); 136 assertThat(thc4.getCharString()).isEqualTo("four"); 137 138 TestHybridClass thc5 = new TestHybridClass(); 139 assertThat(thc5.copy2(new TestHybridClass(5, "five", false))).isTrue(); 140 assertThat(thc5.getInt()).isEqualTo(5); 141 assertThat(thc5.getString()).isEqualTo("five"); 142 } 143 144 @Test testReturnGlobalRef()145 public void testReturnGlobalRef() { 146 TestHybridClass thc = new TestHybridClass(); 147 thc.setGlobal("global_ref"); 148 assertThat(thc.getGlobal1()).isEqualTo("global_ref"); 149 assertThat(thc.getGlobal2()).isEqualTo("global_ref"); 150 } 151 152 @Test testLocalLeak()153 public void testLocalLeak() { 154 TestHybridClass.autoconvertMany(); 155 } 156 157 @Test testExceptionMapping()158 public void testExceptionMapping() { 159 TestHybridClass thc1 = new TestHybridClass(); 160 thrown.expect(ArrayStoreException.class); 161 thc1.oops(); 162 } 163 164 abstract static class AbstractTestHybrid { 165 @DoNotStrip private final HybridData mHybridData; 166 167 private int mAbstractNum; 168 AbstractTestHybrid(HybridData hybridData, int an)169 protected AbstractTestHybrid(HybridData hybridData, int an) { 170 mHybridData = hybridData; 171 mAbstractNum = an; 172 } 173 abstractNum()174 public int abstractNum() { 175 return mAbstractNum; 176 } 177 nativeNum()178 public native int nativeNum(); 179 concreteNum()180 public abstract int concreteNum(); 181 sum()182 public abstract int sum(); 183 } 184 185 static class ConcreteTestHybrid extends AbstractTestHybrid { ConcreteTestHybrid(int an, int nn, int cn)186 public ConcreteTestHybrid(int an, int nn, int cn) { 187 super(initHybrid(nn, cn), an); 188 } 189 initHybrid(int nn, int cn)190 private static native HybridData initHybrid(int nn, int cn); 191 192 // overrides can be native 193 @Override concreteNum()194 public native int concreteNum(); 195 196 // overrides can be java 197 @Override sum()198 public int sum() { 199 return nativeNum() + abstractNum() + concreteNum(); 200 } 201 } 202 203 @Test testHybridInheritance()204 public void testHybridInheritance() { 205 AbstractTestHybrid ath = new ConcreteTestHybrid(1, 2, 3); 206 assertThat(ath.abstractNum()).isEqualTo(1); 207 assertThat(ath.nativeNum()).isEqualTo(2); 208 assertThat(ath.concreteNum()).isEqualTo(3); 209 assertThat(ath.sum()).isEqualTo(6); 210 } 211 cxxTestInheritance(AbstractTestHybrid ath)212 public static native boolean cxxTestInheritance(AbstractTestHybrid ath); 213 makeAbstractHybrid()214 public static native AbstractTestHybrid makeAbstractHybrid(); 215 216 @Test testHybridCxx()217 public void testHybridCxx() { 218 AbstractTestHybrid ath = new ConcreteTestHybrid(4, 5, 6); 219 assertThat(cxxTestInheritance(ath)).isTrue(); 220 221 AbstractTestHybrid ath2 = makeAbstractHybrid(); 222 assertThat(ath2 instanceof ConcreteTestHybrid).isTrue(); 223 assertThat(ath2.abstractNum()).isEqualTo(7); 224 assertThat(ath2.nativeNum()).isEqualTo(8); 225 assertThat(ath2.concreteNum()).isEqualTo(9); 226 assertThat(ath2.sum()).isEqualTo(24); 227 } 228 229 static class Base {} 230 231 static class Derived extends Base { 232 @DoNotStrip private final HybridData mHybridData; 233 Derived(HybridData hybridData)234 private Derived(HybridData hybridData) { 235 mHybridData = hybridData; 236 } 237 } 238 cxxTestDerivedJavaClass()239 public static native boolean cxxTestDerivedJavaClass(); 240 241 @Test testDerivedJavaClassCxx()242 public void testDerivedJavaClassCxx() { 243 assertThat(cxxTestDerivedJavaClass()).isTrue(); 244 } 245 246 static class TestHybridClassBase extends HybridClassBase { initHybrid()247 protected native void initHybrid(); 248 initHybrid(int i)249 private native void initHybrid(int i); 250 TestHybridClassBase()251 protected TestHybridClassBase() { 252 // No initHybrid() here! 253 // Otherwise factory construction will set native pointer twice and process will crash. 254 } 255 TestHybridClassBase(int i)256 public TestHybridClassBase(int i) { 257 initHybrid(i); 258 } 259 260 // Some C++ methods. setInt(int i)261 public native void setInt(int i); 262 getInt()263 public native int getInt(); 264 makeWithThree()265 public static native TestHybridClassBase makeWithThree(); 266 } 267 268 static class TestHybridClassBaseDefaultCtor extends TestHybridClassBase { TestHybridClassBaseDefaultCtor()269 public TestHybridClassBaseDefaultCtor() { 270 initHybrid(); 271 } 272 } 273 274 @Test testHybridBaseDefaultCtor()275 public void testHybridBaseDefaultCtor() { 276 TestHybridClassBaseDefaultCtor base = new TestHybridClassBaseDefaultCtor(); 277 assertThat(base.getInt()).isZero(); 278 279 base.setInt(58); 280 assertThat(base.getInt()).isEqualTo(58); 281 } 282 283 @Test testHybridBaseConstructorArgs()284 public void testHybridBaseConstructorArgs() { 285 TestHybridClassBase base = new TestHybridClassBase(42); 286 assertThat(base.getInt()).isEqualTo(42); 287 } 288 289 @Test testHybridBaseFactoryConstruction()290 public void testHybridBaseFactoryConstruction() { 291 TestHybridClassBase base = TestHybridClassBase.makeWithThree(); 292 assertThat(base.getInt()).isEqualTo(3); 293 } 294 295 static class Destroyable { 296 @DoNotStrip private final HybridData mHybridData; 297 Destroyable(HybridData hybridData)298 private Destroyable(HybridData hybridData) { 299 mHybridData = hybridData; 300 } 301 } 302 cxxTestHybridDestruction()303 public static native boolean cxxTestHybridDestruction(); 304 305 @Test testHybridDestuction()306 public void testHybridDestuction() { 307 assertThat(cxxTestHybridDestruction()).isTrue(); 308 } 309 } 310