Skip to content
File

Blob: src/rust/jsg-test/ffi.c++

5.7 KB
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "ffi.h"
6 
7#include <workerd/io/compatibility-date.capnp.h>
8#include <workerd/jsg/setup.h>
9#include <workerd/rust/jsg-test/lib.rs.h>
10#include <workerd/rust/jsg/ffi-inl.h>
11#include <workerd/rust/jsg/ffi.h>
12#include <workerd/rust/jsg/lib.rs.h>
13#include <workerd/rust/jsg/v8.rs.h>
14 
15#include <cppgc/common.h>
16#include <v8-cppgc.h>
17#include <v8.h>
18 
19#include <capnp/message.h>
20#include <kj/common.h>
21 
22using namespace kj_rs;
23 
24namespace {
25// Enable predictable mode so RequestGarbageCollectionForTesting actually triggers GC.
26// Without this, V8 may defer or skip the requested collection.
27bool initPredictableMode = (workerd::setPredictableModeForTest(), true);
28} // namespace
29 
30namespace workerd {
31 
32struct TestContext: public jsg::Object, public jsg::ContextGlobal {
33 JSG_RESOURCE_TYPE(TestContext) {}
34};
35JSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext);
36 
37namespace rust::jsg_test {
38 
39// Lazy initialization of V8System to avoid static initialization order issues
40static ::workerd::jsg::V8System& getV8System() {
41 static ::workerd::jsg::V8System v8System;
42 return v8System;
43}
44 
45TestHarness::TestHarness(::workerd::jsg::V8StackScope&)
46 : isolate(kj::heap<TestIsolate>(getV8System(), kj::heap<::workerd::jsg::IsolateObserver>())),
47 locker(isolate->getIsolate()),
48 isolateScope(isolate->getIsolate()),
49 realm([&] {
50 // Build default (all-false) feature flags for the test realm.
51 capnp::MallocMessageBuilder flagsMessage;
52 flagsMessage.initRoot<CompatibilityFlags>();
53 auto words = capnp::canonicalize(flagsMessage.getRoot<CompatibilityFlags>().asReader());
54 return ::workerd::rust::jsg::realm_create(
55 isolate->getIsolate(), words.asBytes().as<Rust>());
56 }()) {
57 isolate->getIsolate()->SetData(::workerd::jsg::SetDataIndex::SET_DATA_RUST_REALM, &*realm);
58}
59 
60kj::Own<TestHarness> create_test_harness() {
61 return ::workerd::jsg::runInV8Stack(
62 [](::workerd::jsg::V8StackScope& stackScope) { return kj::heap<TestHarness>(stackScope); });
63}
64 
65EvalContext::EvalContext(v8::Isolate* isolate, v8::Local<v8::Context> context)
66 : v8Isolate(isolate),
67 v8Context(isolate, context) {}
68 
69void EvalContext::set_global(::rust::Str name, ::workerd::rust::jsg::Local value) const {
70 auto ctx = v8Context.Get(v8Isolate);
71 auto key = ::workerd::jsg::check(v8::String::NewFromUtf8(
72 v8Isolate, name.data(), v8::NewStringType::kNormal, static_cast<int>(name.size())));
73 auto v8Value = ::workerd::rust::jsg::local_from_ffi<v8::Value>(kj::mv(value));
74 ::workerd::jsg::check(ctx->Global()->Set(ctx, key, v8Value));
75}
76 
77EvalResult EvalContext::eval(::rust::Str code) const {
78 EvalResult result;
79 result.success = false;
80 
81 v8::Local<v8::Context> ctx = v8Context.Get(v8Isolate);
82 
83 v8::Local<v8::String> source = ::workerd::jsg::check(v8::String::NewFromUtf8(
84 v8Isolate, code.data(), v8::NewStringType::kNormal, static_cast<int>(code.size())));
85 
86 v8::Local<v8::Script> script;
87 KJ_ASSERT(v8::Script::Compile(ctx, source).ToLocal(&script), "Failed to compile script");
88 v8::TryCatch catcher(v8Isolate);
89 
90 v8::Local<v8::Value> value;
91 if (script->Run(ctx).ToLocal(&value)) {
92 result.success = true;
93 result.value = ::workerd::rust::jsg::to_ffi(kj::mv(value));
94 } else if (catcher.HasCaught()) {
95 result.success = false;
96 auto exception = catcher.Exception();
97 result.value = ::workerd::rust::jsg::to_ffi(kj::mv(exception));
98 } else {
99 result.success = false;
100 }
101 
102 return result;
103}
104 
105void TestHarness::run_in_context(
106 size_t data, ::rust::Fn<void(size_t, Isolate*, EvalContext&)> callback) const {
107 isolate->runInLockScope([&](TestIsolate::Lock& lock) {
108 auto context = lock.newContext<TestContext>();
109 v8::Local<v8::Context> v8Context = context.getHandle(lock.v8Isolate);
110 v8::Context::Scope contextScope(v8Context);
111 
112 EvalContext evalContext(lock.v8Isolate, v8Context);
113 callback(data, lock.v8Isolate, evalContext);
114 });
115}
116 
117void request_gc(Isolate* isolate, GcType gc_type) {
118 switch (gc_type) {
119 case GcType::Full:
120 isolate->RequestGarbageCollectionForTesting(
121 v8::Isolate::GarbageCollectionType::kFullGarbageCollection);
122 if (auto* cppHeap = isolate->GetCppHeap()) {
123 cppHeap->CollectGarbageForTesting(cppgc::EmbedderStackState::kNoHeapPointers);
124 }
125 break;
126 case GcType::Minor:
127 isolate->RequestGarbageCollectionForTesting(
128 v8::Isolate::GarbageCollectionType::kMinorGarbageCollection);
129 // V8's minor GC (scavenge) only collects V8's young generation heap.
130 // CppgcShim objects live on the cppgc heap, so we must also trigger
131 // cppgc's young generation collection to actually reclaim them.
132 if (auto* cppHeap = isolate->GetCppHeap()) {
133 cppHeap->CollectGarbageInYoungGenerationForTesting(
134 cppgc::EmbedderStackState::kNoHeapPointers);
135 }
136 break;
137 }
138}
139 
140::workerd::rust::jsg::Local create_cpp_tagged_object(Isolate* isolate) {
141 auto tmpl = v8::ObjectTemplate::New(isolate);
142 tmpl->SetInternalFieldCount(::workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT);
143 auto obj = ::workerd::jsg::check(tmpl->NewInstance(isolate->GetCurrentContext()));
144 
145 // Set the C++ wrappable tag (NOT the Rust tag) to simulate a C++ JSG object
146 auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_WRAPPABLE_TAG);
147 obj->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,
148 tagAddress,
149 static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));
150 
151 return ::workerd::rust::jsg::to_ffi(v8::Local<v8::Value>::Cast(obj));
152}
153 
154} // namespace rust::jsg_test
155 
156} // namespace workerd