if (UNLIKELY(!art::ArtMethod::GetQuickToInterpreterBridge())) { // This is a workaround for art_quick_to_interpreter_bridge not found. // This case is almost impossible to enter // because its symbols are found almost always on all devices. // But if it happened... Try to get it with an abstract method (it is not compilable // and its entry is art_quick_to_interpreter_bridge) // Note: We DO NOT use platform's abstract methods // because their entry may not be interpreter entry.
LOGE("art_quick_to_interpreter_bridge not found, try workaround");
voidThread::Init(const ElfImage* handle){ if (Android::version == Android::kL || Android::version == Android::kLMr1) { // This function is needed to create the backup method on Lollipop. // Below M, an ArtMethod is actually a instance of java.lang.reflect.ArtMethod, can't use malloc() // It should be immovable. On Kitkat, moving gc is unimplemented in art, so it can't be moved // but on Lollipop, this object may be moved by gc, so we need to ensure it is non-movable. alloc_non_movable = reinterpret_cast<void* (*)(void*, Thread*)>(handle->GetSymbolAddress( // art::mirror::Class::AllocNonMovableObject(art::Thread*) "_ZN3art6mirror5Class21AllocNonMovableObjectEPNS_6ThreadE")); } // 获取当前的线程对象
current = reinterpret_cast<Thread* (*)()>(handle->GetSymbolAddress( "_ZN3art6Thread14CurrentFromGdbEv")); // art::Thread::CurrentFromGdb()
if (UNLIKELY(!current && Android::version < Android::kN)) { current = reinterpret_cast<Thread* (*)()>(handle->GetSymbolAddress( "_ZN3art6Thread7CurrentEv")); // art::Thread::Current() if (UNLIKELY(!current)) { key_self = static_cast<pthread_key_t*>(handle->GetSymbolAddress( "_ZN3art6Thread17pthread_key_self_E")); // art::Thread::pthread_key_self_ } }
// jit_load方法 auto jit_load = reinterpret_cast<JitCompiler* (*)(bool*)>(jit_lib_handle->GetSymbolAddress( "jit_load"));
// 创建jitCompiler if (LIKELY(jit_load)) { bool generate_debug_info = false; self_compiler = jit_load(&generate_debug_info); } else { LOGW("Failed to create new JitCompiler: jit_load not found"); }
// FIXME: jit_compile_method doesn't exist in Android R void* jit_compile_method = jit_lib_handle->GetSymbolAddress("jit_compile_method");
if (Android::version >= Android::kQ) { Jit::jit_compile_method_q = reinterpret_cast<bool (*)(void*, void*, void*, bool, bool)>(jit_compile_method); // Android Q, ART may update CompilerOptions and the value we set will be overwritten. // the function pointer saved in art::jit::Jit::jit_update_options_ . Jit::jit_update_options_ptr = static_cast<void**>(art_lib_handle->GetSymbolAddress( "_ZN3art3jit3Jit19jit_update_options_E")); } else { Jit::jit_compile_method = reinterpret_cast<bool (*)(void*, void*, void*, bool)>(jit_compile_method); }
// fields count from compiler_filter_ (not included) to inline_max_code_units_ (not included) // FIXME Offset for inline_max_code_units_ seems to be incorrect on my Pixel 3 (Android 10)... // FIXME Structure of CompilerOptions has changed in Android R. unsigned thresholds_count = Android::version >= Android::kO ? 5 : 6;
voidAndroid::InitMembersFromRuntime(JavaVM* jvm, const ElfImage* handle){ if (version < kQ) { // ClassLinker is unnecessary before R. // JIT was added in Android N but MoveObsoleteMethod was added in Android O // and I didn't find a stable way to retrieve jit code cache until Q // from Runtime object, so try to retrieve from ProfileSaver. // TODO: Still clearing jit info on Android N but only for jit-compiled methods. if (version >= kO) { InitJitCodeCache(nullptr, 0, handle); } return; } void** instance_ptr = static_cast<void**>(handle->GetSymbolAddress("_ZN3art7Runtime9instance_E")); void* runtime; if (UNLIKELY(!instance_ptr || !(runtime = *instance_ptr))) { LOGE("Unable to retrieve Runtime."); return; } // If SmallIrtAllocator symbols can be found, then the ROM has merged commit "Initially allocate smaller local IRT" // This commit added a pointer member between `class_linker_` and `java_vm_`. Need to calibrate offset here. // https://android.googlesource.com/platform/art/+/4dcac3629ea5925e47b522073f3c49420e998911 // https://github.com/crdroidandroid/android_art/commit/aa7999027fa830d0419c9518ab56ceb7fcf6f7f1 // https://android.googlesource.com/platform/art/+/849d09a81907f16d8ccc6019b8baf86a304b730c bool has_smaller_irt = version >= kT || handle->HasSymbol("_ZN3art17SmallIrtAllocator10DeallocateEPNS_8IrtEntryE") || handle->HasSymbol("_ZN3art3jni17SmallLrtAllocatorC2Ev"); std::vector<size_t> known_offsets = OffsetOfJavaVm(has_smaller_irt); size_t jvm_offset = 0; for (size_t offset : known_offsets) { auto val = reinterpret_cast<std::unique_ptr<JavaVM>*>( reinterpret_cast<uintptr_t>(runtime) + offset)->get(); if (val == jvm) { jvm_offset = offset; break; } } if (UNLIKELY(!jvm_offset)) { LOGW("JavaVM offset mismatches default offsets, trying a linear search"); int offset = Memory::FindOffset(runtime, jvm, 1024, 4); if (UNLIKELY(offset == -1)) { LOGE("Failed to find java vm from Runtime"); return; } jvm_offset = offset; LOGW("Found JavaVM in Runtime at %zu", jvm_offset); } InitClassLinker(runtime, jvm_offset, handle, has_smaller_irt); InitJitCodeCache(runtime, jvm_offset, handle); }
LOGW("Method.getAccessFlags threw exception unexpectedly, use default access flags."); env->ExceptionDescribe(); env->ExceptionClear(); } else { LOGW("Method.getAccessFlags not found, use default access flags."); } expected_access_flags = AccessFlags::kPrivate | AccessFlags::kStatic | AccessFlags::kNative; } while (false);
if (androidVersion >= Android::kQ) { expected_access_flags |= AccessFlags::kPublicApi; }
ScopedLocalClassRef I(env, "top/canyie/pine/Ruler$I"); auto abstract_method = art::ArtMethod::Require(env, I.Get(), "m", "()V", false); art::ArtMethod::InitMembers(env, m1, m2, abstract_method, expected_access_flags); // 没找到artMethod的art_quick_to_interpreter_bridge函数地址 // 通过获取abstract方法的entry_point作为其值。 if (UNLIKELY(!art::ArtMethod::GetQuickToInterpreterBridge())) { // This is a workaround for art_quick_to_interpreter_bridge not found. // This case is almost impossible to enter // because its symbols are found almost always on all devices. // But if it happened... Try to get it with an abstract method (it is not compilable // and its entry is art_quick_to_interpreter_bridge) // Note: We DO NOT use platform's abstract methods // because their entry may not be interpreter entry.
LOGE("art_quick_to_interpreter_bridge not found, try workaround");
privatestaticHookHandlersHookHandler=newHookHandler() { @Override public MethodHook.Unhook handleHook(HookRecord hookRecord, MethodHook hook, int modifiers, boolean newMethod, boolean canInitDeclaringClass) { // 只有首次hook的时候需要调用方法执行hook if (newMethod) hookNewMethod(hookRecord, modifiers, canInitDeclaringClass); // 如果之前hook过直接将方法添加到hook record中
// 过滤异常case,如果hook callback为直接返回 if (hook == null) { // This can only happen when the up handler pass null manually, // just return null and let the up to do remaining everything returnnull; } hookRecord.addCallback(hook); // 创建unhook包装给外部。 // 另外提一嘴:逻辑比较简单,Unhook会调用HookHandler.handleUnhook // 而handleUnhook只是做了一个callback的移除。 return hook.newUnhook(hookRecord); }
longthread= currentArtThread0(); // 如果hook方法是Static方法,调用以下static方法以便其能被正常解析。 if ((hookRecord.isStatic = Modifier.isStatic(modifiers)) && canInitDeclaringClass) { resolve((Method) method); if (PineConfig.sdkLevel >= Build.VERSION_CODES.Q) { // Android R has a new class state called "visibly initialized", // and FixupStaticTrampolines will be called after class was initialized. // The entry point will be reset. Make this class be visibly initialized before hook // Note: this feature does not exist on official Android Q, // but some weird ROMs cherry-pick this commit to these Android Q ROMs // https://github.com/crdroidandroid/android_art/commit/ef76ced9d2856ac988377ad99288a357697c4fa2 makeClassesVisiblyInitialized(thread); } }
// Only try compile target method when trying inline hook. // 处理需要inline hook的case if (isInlineHook) { // Cannot compile native or proxy methods. // 如果是INLINE 模式在hook强制进行一次jit编译 if (!(jni || proxy)) { if (mode == HookMode.INLINE) { booleancompiled= compile0(thread, method); if (!compiled) { Log.w(TAG, "Cannot compile the target method, force replacement mode."); isInlineHook = false; } } } else { isInlineHook = false; } }
String bridgeMethodName; // FIXME: WARNING: The following code will cause parameter types and return type to be initialized!!! // 根据入参和返回值获取桥接方法 if (method instanceof Method) { hookRecord.paramTypes = ((Method) method).getParameterTypes(); Class<?> returnType = ((Method) method).getReturnType(); bridgeMethodName = returnType.isPrimitive() ? returnType.getName() + "Bridge" : "objectBridge"; } else { hookRecord.paramTypes = ((Constructor<?>) method).getParameterTypes(); // Constructor is actually a method named <init> and its return type is void. bridgeMethodName = "voidBridge"; } // 设置参数 hookRecord.paramNumber = hookRecord.paramTypes.length;
boolJit::CompileMethod(Thread* thread, void* method){ // sdk >= 30 直接返回 if (LIKELY(Android::version >= Android::kR)) { LOGW("JIT compilation is not supported in Android R yet"); returnfalse; } // 获取jit compiler void* compiler = GetCompiler(); if (UNLIKELY(!compiler)) { LOGE("No JitCompiler available for JIT compilation!"); returnfalse; } bool result; // JIT compilation will modify the state of the thread, so we backup and restore it after compilation. // jit会修改线程的flag,调用前备份下 int32_t origin_state_and_flags = thread->GetStateAndFlags(); // 调用jit方法 if (jit_compile_method) { result = jit_compile_method(compiler, method, thread, false/*osr*/); } elseif (jit_compile_method_q) { result = jit_compile_method_q(compiler, method, thread, false/*baseline*/, false/*osr*/); } else { LOGE("Compile method failed: jit_compile_method not found"); returnfalse; } // reset jit thread->SetStateAndFlags(origin_state_and_flags); return result; }
if (PineConfig::jit_compilation_allowed && PineConfig::auto_compile_bridge) { // The bridge method entry will be hardcoded in the trampoline, subsequent optimization // operations that require modification of the bridge method entry will not take effect. // Try to do JIT compilation first to get the best performance. bridge->Compile(thread); }
// 创建backup方法 art::ArtMethod* backup; if (WellKnownClasses::java_lang_reflect_ArtMethod) { // If ArtMethod has mirror class in java, we cannot use malloc to direct // allocate an instance because it must has a record in Runtime.
backup = static_cast<art::ArtMethod*>(thread->AllocNonMovable( WellKnownClasses::java_lang_reflect_ArtMethod)); if (UNLIKELY(!backup)) { #if __ANDROID_API__ < __ANDROID_API_L__ // On Android kitkat, moving gc is not supported in art. All objects are immovable. if (UNLIKELY(Android::version >= Android::kL)) { #endif LOGE("Failed to allocate an immovable object for creating backup method."); env->ExceptionClear(); #if __ANDROID_API__ < __ANDROID_API_L__ } #endif
jobject javaBackup = env->AllocObject(WellKnownClasses::java_lang_reflect_ArtMethod); if (UNLIKELY(env->ExceptionCheck())) { LOGE("Can't create the backup method!"); returnnullptr; } backup = static_cast<art::ArtMethod*>(thread->DecodeJObject(javaBackup)); } } else { backup = art::ArtMethod::New(); if (UNLIKELY(!backup)) { int local_errno = errno; LOGE("Cannot allocate backup ArtMethod, errno %d(%s)", errno, strerror(errno)); if (local_errno == ENOMEM) { JNIHelper::Throw(env, "java/lang/OutOfMemoryError", "No memory for allocate backup method"); } else { JNIHelper::Throw(env, "java/lang/RuntimeException", "hook failed: cannot allocate backup method"); } returnnullptr; } }
void* new_entrypoint; char error_msg[288]; // 安装trampoline { // ArtMethod objects are very important. Many threads depend on their values, // so we need to suspend other threads to avoid errors. ScopedSuspendVM suspend_vm(thread);
总结一下就是: 1.如果有java.lang.reflect.ArtMethod类型,则使用它创建ArtMethod对象 a. 5.0、5.1的系统版本通过AllocNonMovableObject创建ArtMethod对象(版本差异) b. 其他版本使用env->AllocObject创建ArtMethod对象 2.若无则使用malloc分配特定大小的内存。
art::ArtMethod* backup; if (WellKnownClasses::java_lang_reflect_ArtMethod) { // 如果ArtMethod的java mirror类型能够获取到
// If ArtMethod has mirror class in java, we cannot use malloc to direct // allocate an instance because it must has a record in Runtime.
// 先尝试调用art::mirror::Class::AllocNonMovableObject new对象。(应该只针对于androd 5.0、5.1的版本) // 如果失败再尝试使用env->AllocObject开启新的内存空间 backup = static_cast<art::ArtMethod*>(thread->AllocNonMovable( WellKnownClasses::java_lang_reflect_ArtMethod)); if (UNLIKELY(!backup)) { #if __ANDROID_API__ < __ANDROID_API_L__ // On Android kitkat, moving gc is not supported in art. All objects are immovable. if (UNLIKELY(Android::version >= Android::kL)) { #endif LOGE("Failed to allocate an immovable object for creating backup method."); env->ExceptionClear(); #if __ANDROID_API__ < __ANDROID_API_L__ } #endif
void* TrampolineInstaller::InstallReplacementTrampoline(art::ArtMethod* target, art::ArtMethod* bridge){ // #1 创建bridge jump trampoline void* origin_code_entry = target->GetEntryPointFromCompiledCode(); void* bridge_jump_trampoline = CreateBridgeJumpTrampoline(target, bridge, origin_code_entry); if (UNLIKELY(!bridge_jump_trampoline)) returnnullptr; // #2 入口替换 // 这里注释是想说明:如果是不使用入口替换,比如替换原始代码的字节序列,修改x0值后,如果正好触发了堆栈回溯可能会有出现崩溃。 // 最后选择了入口替换的方案 // Unknown bug: // After setting the r0 register to the original method, if the original method needs to be // traced back to the call stack (such as an exception), the thread will become a zombie thread // and there will be no response. Just set origin code entry and don't create call_origin_trampoline // to set r0 register to avoid it. // void *call_origin_trampoline = CreateCallOriginTrampoline(target, origin_code_entry); // if (UNLIKELY(!call_origin_trampoline)) return nullptr; target->SetEntryPointFromCompiledCode(bridge_jump_trampoline); // return call_origin_trampoline; if (PineConfig::debug) LOGD("InstallReplacementTrampoline: origin %p origin_entry %p bridge_jump %p", target, origin_code_entry, bridge_jump_trampoline); // #3 返回原始的代码地址 return origin_code_entry; }
// 2. hook static方法前进行方法初始化(可选) if ((hookRecord.isStatic = Modifier.isStatic(modifiers)) && canInitDeclaringClass) { resolve((Method) method); if (PineConfig.sdkLevel >= Build.VERSION_CODES.Q) { // Android R has a new class state called "visibly initialized", // and FixupStaticTrampolines will be called after class was initialized. // The entry point will be reset. Make this class be visibly initialized before hook // Note: this feature does not exist on official Android Q, // but some weird ROMs cherry-pick this commit to these Android Q ROMs // https://github.com/crdroidandroid/android_art/commit/ef76ced9d2856ac988377ad99288a357697c4fa2 makeClassesVisiblyInitialized(thread); } }
// 方法参数为0,1,2 staticvoidvoidBridge(long artMethod, long extras)throws Throwable { voidBridge(artMethod, extras, 0); } // 方法参数为3 staticvoidvoidBridge(long artMethod, long extras, long sp)throws Throwable { voidBridge(artMethod, extras, sp, 0); } // 方法参数为4 staticvoidvoidBridge(long artMethod, long extras, long sp, long x4)throws Throwable { voidBridge(artMethod, extras, sp, x4, 0); }
// 方法参数为5 staticvoidvoidBridge(long artMethod, long extras, long sp, long x4, long x5)throws Throwable { voidBridge(artMethod, extras, sp, x4, x5, 0); } // 方法参数为6 staticvoidvoidBridge(long artMethod, long extras, long sp, long x4, long x5, long x6)throws Throwable { voidBridge(artMethod, extras, sp, x4, x5, x6, 0); } // 方法参数为7 staticvoidvoidBridge(long artMethod, long extras, long sp, long x4, long x5, long x6, long x7)throws Throwable { Arm64Entry.voidBridge(artMethod, extras, sp, x4, x5, x6, x7); }
// 方案执行的第一步 staticvoidvoidBridge(long artMethod, long extras, long sp, long x4, long x5, long x6, long x7)throws Throwable { handleBridge(artMethod, extras, sp, x4, x5, x6, x7); }
privatestatic Object handleBridge(long artMethod, long originExtras, long sp, long x4, long x5, long x6, long x7)throws Throwable { // #1 克隆extra参数 // Clone the extras and unlock to minimize the time we hold the lock longextras= Pine.cloneExtras(originExtras); Pine.log("handleBridge: artMethod=%#x originExtras=%#x extras=%#x sp=%#x", artMethod, originExtras, extras, sp); // #2 解析寄存器 & 堆栈参数 Pine.HookRecordhookRecord= Pine.getHookRecord(artMethod); ThreeTuple<long[], long[], double[]> threeTuple = getArgs(hookRecord, extras, sp, x4, x5, x6, x7); long[] coreRegisters = threeTuple.a; long[] stack = threeTuple.b; double[] fpRegisters = threeTuple.c; Object receiver; Object[] args;
intcrIndex=0, stackIndex = 0, fprIndex = 0; longthread= Pine.currentArtThread0(); // #3 解析this参数 if (hookRecord.isStatic) { receiver = null; } else { receiver = Pine.getObject(thread, coreRegisters[0]); crIndex = 1; stackIndex = 1; } // #4 将参数转为object数组(方便后续反射调用) if (hookRecord.paramNumber > 0) { args = newObject[hookRecord.paramNumber]; for (inti=0; i < hookRecord.paramNumber; i++) { Class<?> paramType = hookRecord.paramTypes[i]; Object value; if (paramType == double.class) { if (fprIndex < fpRegisters.length) value = fpRegisters[fprIndex++]; else value = Double.longBitsToDouble(stack[stackIndex]); } elseif (paramType == float.class) { long asLong; if (fprIndex < fpRegisters.length) asLong = Double.doubleToLongBits(fpRegisters[fprIndex++]); else asLong = stack[stackIndex]; value = Float.intBitsToFloat((int) (asLong & INT_BITS)); } else { long asLong; if (crIndex < coreRegisters.length) asLong = coreRegisters[crIndex++]; else asLong = stack[stackIndex];
// 释放锁 voidReleaseLock(){ #if defined(__aarch64__) || defined(__arm__) // Not supported spinlock on x86 platform CHECK(lock_flag == 0, "Unexpected lock_flag %d", lock_flag); dmb(); // Ensure all previous accesses are observed before the lock is released. lock_flag = 1; dsb(); // Ensure completion of the store that cleared the lock before sending the event. sev(); // Wake up the thread that is waiting for the lock. #endif }
static Object callBackupMethod(HookRecord hookRecord, Object thisObject, Object[] args)throws InvocationTargetException, IllegalAccessException { // java.lang.Class object is movable and may cause crash when invoke backup method, // native entry of JNI method may be changed by RegisterNatives and UnregisterNatives, // so we need to update them when invoke backup method. Member origin = hookRecord.target; Methodbackup= hookRecord.backup; Class<?> declaring = origin.getDeclaringClass(); // 在调用方法前需要更新下back method的一些信息 syncMethodInfo(origin, backup, hookRecord.skipUpdateDeclaringClass); // 直接反射调用 // FIXME: GC happens here (you can add Runtime.getRuntime().gc() to test) will crash backup calling Objectresult= backup.invoke(thisObject, args); // Explicit use declaring_class object to ensure it has reference on stack // and avoid being moved by gc. (invalid for now) declaring.getClass(); return result; }
同步更新逻辑如下,就是把原始方法和backup方法的declaringClass取出来对比如果不一致就执行更新,除了declaringClass还会对EntryPointFromJni做类似的对比操作。 (可能是担心gc or 其他原因更新了原始方法?)
voidPine_syncMethodInfo(JNIEnv* env, jclass, jobject javaOrigin, jobject javaBackup, jboolean skipDeclaringClass){ auto origin = art::ArtMethod::FromReflectedMethod(env, javaOrigin); auto backup = art::ArtMethod::FromReflectedMethod(env, javaBackup); // An ArtMethod is actually an instance of java class "java.lang.reflect.ArtMethod" on pre M // declaring_class is a reference field so the runtime itself will update it if moved by GC if (skipDeclaringClass == JNI_FALSE && Android::version >= Android::kM) { uint32_t declaring_class = origin->GetDeclaringClass(); if (declaring_class != backup->GetDeclaringClass()) { LOGI("GC moved declaring class of method %p, also update in backup %p", origin, backup); backup->SetDeclaringClass(declaring_class); } } // JNI method entry might be changed by RegisterNatives or UnregisterNatives // Use backup to check native as we may add kNative to access flags of origin (Android 8.0+ with debuggable mode) if (backup->IsNative()) { void* previous = backup->GetEntryPointFromJni(); void* current = origin->GetEntryPointFromJni(); if (current != previous) { LOGI("Native entry of method %p was changed, also update in backup %p", origin, backup); backup->SetEntryPointFromJni(current); } } }
// ...... public MethodHook.Unhook handleHook(Pine.HookRecord hookRecord, MethodHook hook, int modifiers, boolean newMethod, boolean canInitDeclaringClass) { // 判断是否启用延后执行(核心点就是看类是否已经初始化完全) booleanskipInit= hook != null && shouldDelay(hookRecord.target, newMethod, modifiers); // 将hook保存到JNI中 if (newMethod) recordMethodHooked(hookRecord.artMethod, PREVENT_ENTRY_UPDATE, PREVENT_ENTRY_UPDATE); // replaceMent Mode if (Pine.getHookMode() == Pine.HookMode.REPLACEMENT) { // Here we always need to record hooked methods even if they don't need to be delayed // because we manually have shut the debug switch down, we need to skip ShouldUseInterpreterEntrypoint // WARNING: Do not log the target method here, as it may trigger // initialization of parameters and return type // 如果是skipInit为true此次不会执行hook MethodHook.Unhooku= realHandler.handleHook(hookRecord, hook, modifiers, newMethod, !skipInit && canInitDeclaringClass); // 更新下记录 if (newMethod) recordMethodHooked(hookRecord.artMethod, hookRecord.trampoline, Pine.getArtMethod(hookRecord.backup)); // 返回 return u; } // 其他模式(INLINE Mode) // 处理skipInit的case if (skipInit) { // 添加一条hook记录 Class<?> declaring = hookRecord.target.getDeclaringClass(); synchronized (pendingMap) { Set<Pine.HookRecord> pendingHooks = pendingMap.get(declaring); if (pendingHooks == null) { pendingHooks = newHashSet<>(1, 1f); pendingMap.put(declaring, pendingHooks); // 调用ClassInitMonitor监控类完成初始化完成 ClassInitMonitor.care(declaring); } pendingHooks.add(hookRecord); } hookRecord.addCallback(hook); return hook.newUnhook(hookRecord); } // not skip模式下先直接进行初始化。 MethodHook.Unhooku= realHandler.handleHook(hookRecord, hook, modifiers, newMethod, canInitDeclaringClass); if (newMethod) recordMethodHooked(hookRecord.artMethod, hookRecord.trampoline, Pine.getArtMethod(hookRecord.backup)); return u; } // ......
voidPineEnhances_careClassInit(JNIEnv*, jclass, jlong address){ void* ptr = reinterpret_cast<void*>(address); auto class_def = GetClassDef(ptr); if (class_def == nullptr) { // This class have no class def. That's mostly impossible, these classes (like proxy classes) // should be initialized before. But if it happens... LOGW("Class %p have no class def, this should not happen, please check the root cause", ptr); care_no_class_def_ = true; return; } ScopedLock lk(cared_classes_mutex_); cared_classes_.insert(class_def); }