Skip to content

Commit 7bb684d

Browse files
generatedunixname1563563004708334meta-codesync[bot]
authored andcommitted
xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/jni/corefunctions.cpp (#57828)
Summary: Pull Request resolved: #57828 X-link: react/yoga#2005 Reviewed By: cortinico Differential Revision: D114858875 fbshipit-source-id: d3482b3578737688130c3dce3d5b7b2ddb3755de
1 parent d89c432 commit 7bb684d

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "corefunctions.h"
9+
10+
#include <gtest/gtest.h>
11+
#include <jni.h>
12+
#include "YogaJniException.h"
13+
14+
namespace facebook::yoga::vanillajni {
15+
namespace {
16+
17+
// A single byte handed out as opaque, never-dereferenced JNI handles (jclass,
18+
// jobject, jthrowable, jmethodID). The functions under test only compare these
19+
// against nullptr; they never look inside them.
20+
char gFakeHandleStorage = 0;
21+
22+
template <typename T>
23+
T fakeHandle() {
24+
return reinterpret_cast<T>(&gFakeHandleStorage);
25+
}
26+
27+
// Overlay for JNIEnv. The real _JNIEnv is standard-layout with a single leading
28+
// functions pointer, so a FakeEnv* can be passed anywhere a JNIEnv* is expected
29+
// and cast back inside the interface callbacks to reach the per-instance,
30+
// test-controlled behavior below.
31+
struct FakeEnv {
32+
const JNINativeInterface* functions;
33+
34+
jboolean exceptionCheckResult;
35+
jthrowable exceptionOccurredResult;
36+
jobject newGlobalRefResult;
37+
jclass findClassResult;
38+
jmethodID getMethodIdResult;
39+
jobject newObjectResult;
40+
41+
int exceptionCheckCalls;
42+
int exceptionClearCalls;
43+
};
44+
45+
struct FakeVm {
46+
const JNIInvokeInterface* functions;
47+
JNIEnv* envToReturn;
48+
jint getEnvResult;
49+
};
50+
51+
FakeEnv* asFake(JNIEnv* env) {
52+
return reinterpret_cast<FakeEnv*>(env);
53+
}
54+
55+
jboolean fakeExceptionCheck(JNIEnv* env) {
56+
auto* self = asFake(env);
57+
++self->exceptionCheckCalls;
58+
return self->exceptionCheckResult;
59+
}
60+
61+
jthrowable fakeExceptionOccurred(JNIEnv* env) {
62+
return asFake(env)->exceptionOccurredResult;
63+
}
64+
65+
void fakeExceptionClear(JNIEnv* env) {
66+
++asFake(env)->exceptionClearCalls;
67+
}
68+
69+
jobject fakeNewGlobalRef(JNIEnv* env, jobject /*obj*/) {
70+
return asFake(env)->newGlobalRefResult;
71+
}
72+
73+
void fakeDeleteGlobalRef(JNIEnv* /*env*/, jobject /*obj*/) {}
74+
75+
jclass fakeFindClass(JNIEnv* env, const char* /*name*/) {
76+
return asFake(env)->findClassResult;
77+
}
78+
79+
jmethodID fakeGetMethodID(
80+
JNIEnv* env,
81+
jclass /*clazz*/,
82+
const char* /*name*/,
83+
const char* /*sig*/) {
84+
return asFake(env)->getMethodIdResult;
85+
}
86+
87+
jobject fakeNewObjectV(
88+
JNIEnv* env,
89+
jclass /*clazz*/,
90+
jmethodID /*methodId*/,
91+
va_list /*args*/) {
92+
return asFake(env)->newObjectResult;
93+
}
94+
95+
const JNINativeInterface& fakeNativeInterface() {
96+
static const JNINativeInterface table = [] {
97+
JNINativeInterface t{};
98+
t.ExceptionCheck = &fakeExceptionCheck;
99+
t.ExceptionOccurred = &fakeExceptionOccurred;
100+
t.ExceptionClear = &fakeExceptionClear;
101+
t.NewGlobalRef = &fakeNewGlobalRef;
102+
t.DeleteGlobalRef = &fakeDeleteGlobalRef;
103+
t.FindClass = &fakeFindClass;
104+
t.GetMethodID = &fakeGetMethodID;
105+
t.NewObjectV = &fakeNewObjectV;
106+
return t;
107+
}();
108+
return table;
109+
}
110+
111+
jint fakeGetEnv(JavaVM* vm, void** out, jint /*version*/) {
112+
auto* self = reinterpret_cast<FakeVm*>(vm);
113+
*out = self->envToReturn;
114+
return self->getEnvResult;
115+
}
116+
117+
const JNIInvokeInterface& fakeInvokeInterface() {
118+
static const JNIInvokeInterface table = [] {
119+
JNIInvokeInterface t{};
120+
t.GetEnv = &fakeGetEnv;
121+
return t;
122+
}();
123+
return table;
124+
}
125+
126+
// getCurrentEnv() (invoked while wrapping a Java exception into a
127+
// YogaJniException) reads a process-global JavaVM that is latched exactly once
128+
// by the first ensureInitialized() call, so it must stay valid for the entire
129+
// process. This plumbing env/VM exists solely to satisfy that constraint: it
130+
// returns constant, non-null JNI handles and is never mutated by any test.
131+
// All per-test, mutable state lives on each fixture's own FakeEnv, so tests
132+
// stay fully isolated from one another.
133+
FakeEnv& plumbingEnv() {
134+
static FakeEnv env{
135+
.functions = &fakeNativeInterface(),
136+
.exceptionCheckResult = JNI_FALSE,
137+
.exceptionOccurredResult = fakeHandle<jthrowable>(),
138+
.newGlobalRefResult = fakeHandle<jobject>(),
139+
.findClassResult = fakeHandle<jclass>(),
140+
.getMethodIdResult = fakeHandle<jmethodID>(),
141+
.newObjectResult = fakeHandle<jobject>(),
142+
.exceptionCheckCalls = 0,
143+
.exceptionClearCalls = 0};
144+
return env;
145+
}
146+
147+
JNIEnv* plumbingJniEnv() {
148+
return reinterpret_cast<JNIEnv*>(&plumbingEnv());
149+
}
150+
151+
FakeVm& plumbingVm() {
152+
static FakeVm vm{
153+
.functions = &fakeInvokeInterface(),
154+
.envToReturn = plumbingJniEnv(),
155+
.getEnvResult = JNI_OK};
156+
return vm;
157+
}
158+
159+
JavaVM* plumbingJavaVm() {
160+
return reinterpret_cast<JavaVM*>(&plumbingVm());
161+
}
162+
163+
class CoreFunctionsTest : public ::testing::Test {
164+
protected:
165+
// Latch the process-global JavaVM exactly once for the whole suite so that
166+
// getCurrentEnv() (used when wrapping exceptions) has a valid, stable env.
167+
static void SetUpTestSuite() {
168+
JNIEnv* out = nullptr;
169+
ensureInitialized(&out, plumbingJavaVm());
170+
}
171+
172+
void SetUp() override {
173+
env_ = FakeEnv{
174+
.functions = &fakeNativeInterface(),
175+
.exceptionCheckResult = JNI_FALSE,
176+
.exceptionOccurredResult = fakeHandle<jthrowable>(),
177+
.newGlobalRefResult = fakeHandle<jobject>(),
178+
.findClassResult = fakeHandle<jclass>(),
179+
.getMethodIdResult = fakeHandle<jmethodID>(),
180+
.newObjectResult = fakeHandle<jobject>(),
181+
.exceptionCheckCalls = 0,
182+
.exceptionClearCalls = 0};
183+
}
184+
185+
JNIEnv* env() {
186+
return reinterpret_cast<JNIEnv*>(&env_);
187+
}
188+
189+
// Owned per test instance; each test observes and mutates only its own copy.
190+
FakeEnv env_{};
191+
};
192+
193+
TEST_F(CoreFunctionsTest, ensureInitializedReturnsVersionAndPopulatesEnv) {
194+
JNIEnv* out = nullptr;
195+
196+
jint result = ensureInitialized(&out, plumbingJavaVm());
197+
198+
EXPECT_EQ(JNI_VERSION_1_6, result);
199+
// The out-param must be populated with the JNIEnv that the VM GetEnv hook
200+
// wrote, not left untouched.
201+
EXPECT_EQ(plumbingJniEnv(), out);
202+
}
203+
204+
TEST_F(CoreFunctionsTest, assertNoPendingJniExceptionReturnsWhenNoException) {
205+
env_.exceptionCheckResult = JNI_FALSE;
206+
207+
EXPECT_NO_THROW(assertNoPendingJniException(env()));
208+
// A clean env must be left alone: no attempt to clear a (non-existent)
209+
// pending exception.
210+
EXPECT_EQ(0, env_.exceptionClearCalls);
211+
}
212+
213+
TEST_F(CoreFunctionsTest, assertNoPendingJniExceptionClearsAndWrapsPending) {
214+
env_.exceptionCheckResult = JNI_TRUE;
215+
env_.exceptionOccurredResult = fakeHandle<jthrowable>();
216+
217+
EXPECT_THROW(assertNoPendingJniException(env()), YogaJniException);
218+
// The pending Java exception must be cleared exactly once before it is
219+
// rethrown as a C++ exception, otherwise it would leak into the next JNI
220+
// call.
221+
EXPECT_EQ(1, env_.exceptionClearCalls);
222+
}
223+
224+
TEST_F(
225+
CoreFunctionsTest,
226+
assertNoPendingJniExceptionIfSkipsCheckWhenConditionFalse) {
227+
// Arrange a pending exception that would throw if it were inspected.
228+
env_.exceptionCheckResult = JNI_TRUE;
229+
env_.exceptionOccurredResult = fakeHandle<jthrowable>();
230+
231+
EXPECT_NO_THROW(assertNoPendingJniExceptionIf(env(), false));
232+
// With a false condition the env must not be probed at all.
233+
EXPECT_EQ(0, env_.exceptionCheckCalls);
234+
}
235+
236+
TEST_F(
237+
CoreFunctionsTest,
238+
assertNoPendingJniExceptionIfThrowsWhenConditionTrueWithoutPending) {
239+
// Condition is true but there is no pending JNI exception, so the function
240+
// must still surface a failure by synthesizing a YogaJniException.
241+
env_.exceptionCheckResult = JNI_FALSE;
242+
243+
EXPECT_THROW(assertNoPendingJniExceptionIf(env(), true), YogaJniException);
244+
}
245+
246+
} // namespace
247+
} // namespace facebook::yoga::vanillajni

0 commit comments

Comments
 (0)