Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.izzel.incision.bridge;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Incision 字节码桥 — 所有被 incision 织入的 INVOKESTATIC 调用都指向这里。
Expand All @@ -17,8 +20,8 @@
*
* 解析顺序:
* <ol>
* <li>按被织入类的 defining ClassLoader 精确查找本地 TheatreDispatcher;</li>
* <li>目标是服务端类且当前只有一个本地 lease 时,允许唯一回退;</li>
* <li>按目标签名查找声明该切术的 TheatreDispatcher;</li>
* <li>旧调用方未登记目标时,才按 defining ClassLoader 或唯一 lease 回退;</li>
* <li>本地没有可用路由时再交给系统 ClassLoader 上的 Gate。</li>
* </ol>
*
Expand All @@ -39,6 +42,19 @@ private IncisionBridge() {}
/** ClassLoader → 本地 TheatreDispatcher.dispatch Method 缓存(单插件 fallback 路径) */
private static final ConcurrentHashMap<ClassLoader, Method> localCache = new ConcurrentHashMap<ClassLoader, Method>();

/**
* 运行时目标签名 → 声明该目标的 dispatcher。
*
* 被织入类可能属于 Leaf 的服务端 URLClassLoader,也可能属于 AuraSkills 等第三方插件;
* 它的 defining loader 与切术声明方没有必然关系,因此 loader 绝不能作为正常路由依据。
*/
private static final ConcurrentHashMap<String, CopyOnWriteArrayList<Method>> targetRoutes =
new ConcurrentHashMap<String, CopyOnWriteArrayList<Method>>();

/** 多插件声明同一目标的诊断去重;真正的跨插件优先级聚合必须由 Gate 完成。 */
private static final ConcurrentHashMap<String, Boolean> routeConflictWarnings =
new ConcurrentHashMap<String, Boolean>();

/**
* 供 weaver 注入的 INVOKESTATIC 目标。
*
Expand All @@ -54,16 +70,26 @@ private IncisionBridge() {}
*/
public static Object dispatch(Class<?> ownerClass, String targetSignature, Object self, Object[] args) {
ClassLoader definingLoader = ownerClass == null ? null : ownerClass.getClassLoader();
Method local = resolveLocalDispatch(definingLoader);
if (local != null) {
try {
if (local.getParameterCount() == 4) {
return local.invoke(null, targetSignature, self, args, null);
List<Method> locals = resolveLocalDispatches(definingLoader, targetSignature);
if (!locals.isEmpty()) {
Object result = null;
boolean invoked = false;
for (Method local : locals) {
try {
Object localResult;
if (local.getParameterCount() == 4) {
localResult = local.invoke(null, targetSignature, self, args, null);
} else {
localResult = local.invoke(null, targetSignature, self, args);
}
// 未持有该 target 的 dispatcher 以 null 表示未命中,不能覆盖前一个插件的有效结果。
if (localResult != null) result = localResult;
invoked = true;
} catch (Throwable t) {
System.err.println("[Incision][Bridge] local dispatch failed: " + t);
}
return local.invoke(null, targetSignature, self, args);
} catch (Throwable t) {
System.err.println("[Incision][Bridge] local dispatch failed: " + t);
}
if (invoked) return result;
}
Method m = systemDispatch;
Object host = systemHost;
Expand All @@ -77,15 +103,21 @@ public static Object dispatch(Class<?> ownerClass, String targetSignature, Objec
// 精确 loader 路由失败属于生命周期错误,不能静默伪装成 advice 未命中。
System.err.println("[Incision][Bridge] dispatch unavailable: owner=" +
(ownerClass == null ? "null" : ownerClass.getName()) + " loader=" + definingLoader +
" localLeases=" + localCache.size() + " target=" + targetSignature);
" localLeases=" + localCache.size() + " targetRoutes=" + targetRoutes.size() +
" target=" + targetSignature);
return null;
}

public static Object dispatchBypass(Class<?> ownerClass, String targetSignature, Object self, Object[] args) {
Method local = resolveLocalSibling(ownerClass == null ? null : ownerClass.getClassLoader(), "dispatchBypass");
if (local != null) {
List<Method> dispatches = resolveLocalDispatches(
ownerClass == null ? null : ownerClass.getClassLoader(), targetSignature
);
for (Method dispatch : dispatches) {
Method local = resolveLocalSibling(dispatch, "dispatchBypass");
if (local == null) continue;
try {
return local.invoke(null, targetSignature, self, args);
Object result = local.invoke(null, targetSignature, self, args);
if (!isBypassMiss(result)) return result;
} catch (Throwable t) {
System.err.println("[Incision][Bridge] local bypass dispatch failed: " + t);
}
Expand Down Expand Up @@ -116,6 +148,11 @@ public static boolean hasSystemHost() {
return systemHost != null && systemDispatch != null;
}

/** JVM 级 lease 数量;Gate holder 只能在该值归零后释放共享 delegate。 */
public static int localLeaseCount() {
return localCache.size();
}

// -----------------------------------------------------------------

private static Object findSystemHost() {
Expand All @@ -141,14 +178,63 @@ private static Method resolveDispatch(Object host) {
}

private static Method resolveLocalDispatch(ClassLoader definingLoader) {
Method cached = localCache.get(definingLoader);
Method cached = definingLoader == null ? null : localCache.get(definingLoader);
if (cached != null) return cached;
// 服务端类通常由 bootstrap/system loader 定义。只有一个插件持有 lease 时路由没有歧义
// 多插件场景必须交给 Gate,禁止退化为“最后注册 dispatcher”。
// 兼容未登记 target 的旧调用方:只有一个插件持有 lease 时路由没有歧义
// 多 lease 的广播由 resolveLocalDispatches 生成快照,禁止退化为“最后注册 dispatcher”。
if (localCache.size() == 1) return localCache.values().iterator().next();
return null;
}

private static List<Method> resolveLocalDispatches(ClassLoader definingLoader, String targetSignature) {
CopyOnWriteArrayList<Method> routed = targetRoutes.get(baseSignature(targetSignature));
if (routed != null && !routed.isEmpty()) return new ArrayList<Method>(routed);
Method legacy = resolveLocalDispatch(definingLoader);
if (legacy != null) return java.util.Collections.singletonList(legacy);
// 旧版调用方不会登记 target。owner loader 又可能属于 Leaf 或第三方插件,
// 此时广播给快照中的 dispatcher,由各自的 chain 表自行判定是否命中,禁止再因多 lease 直接断链。
return new ArrayList<Method>(localCache.values());
}

/**
* 登记目标的真实声明方。相位与 Site advice id 属于调用后缀,不参与路由键。
*/
public static void registerLocalTarget(Class<?> dispatcherClass, String targetSignature) {
if (dispatcherClass == null || targetSignature == null) return;
Method dispatch = pickDispatchMethod(dispatcherClass);
if (dispatch == null) return;
String base = baseSignature(targetSignature);
CopyOnWriteArrayList<Method> routes = targetRoutes.computeIfAbsent(
base, ignored -> new CopyOnWriteArrayList<Method>()
);
for (Method route : routes) {
if (route.getDeclaringClass() == dispatcherClass) return;
}
routes.add(dispatch);
if (routes.size() > 1 && routeConflictWarnings.putIfAbsent(base, Boolean.TRUE) == null) {
System.err.println("[Incision][Bridge] multiple dispatchers registered for target=" + base +
" routes=" + routes.size() + " (cross-plugin priority requires Gate aggregation)");
}
}

/** 仅移除当前插件对指定目标的路由,不影响其他同时安装 Incision 的插件。 */
public static void unregisterLocalTarget(ClassLoader classLoader, String targetSignature) {
if (classLoader == null || targetSignature == null) return;
String base = baseSignature(targetSignature);
CopyOnWriteArrayList<Method> routes = targetRoutes.get(base);
if (routes == null) return;
routes.removeIf(method -> method.getDeclaringClass().getClassLoader() == classLoader);
if (routes.isEmpty()) targetRoutes.remove(base, routes);
if (routes.size() <= 1) routeConflictWarnings.remove(base);
}

private static String baseSignature(String targetSignature) {
int hash = targetSignature.indexOf('#');
String withoutAdvice = hash < 0 ? targetSignature : targetSignature.substring(0, hash);
int phase = withoutAdvice.lastIndexOf('@');
return phase < 0 ? withoutAdvice : withoutAdvice.substring(0, phase);
}

/** 由 IncisionBootstrap 在 CONST 阶段调用,显式注册经过重定向后的 dispatcher 类 */
public static void registerLocalDispatcher(Class<?> dispatcherClass) {
if (dispatcherClass == null) return;
Expand Down Expand Up @@ -177,8 +263,7 @@ private static Method pickDispatchMethod(Class<?> cls) {
return anyShape4 != null ? anyShape4 : anyShape3;
}

private static Method resolveLocalSibling(ClassLoader cl, String methodName) {
Method local = resolveLocalDispatch(cl);
private static Method resolveLocalSibling(Method local, String methodName) {
if (local == null) return null;
try {
return local.getDeclaringClass().getMethod(methodName, String.class, Object.class, Object[].class);
Expand All @@ -191,5 +276,10 @@ private static Method resolveLocalSibling(ClassLoader cl, String methodName) {
public static void unregisterLocalDispatcher(ClassLoader cl) {
if (cl == null) return;
localCache.remove(cl);
for (String target : new ArrayList<String>(targetRoutes.keySet())) {
unregisterLocalTarget(cl, target);
}
// 最后一个插件退出后必须断开 Gate 对首个插件 ClassLoader 的强引用;更早解绑会破坏其他 lease。
if (localCache.isEmpty()) unbindSystemHost();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package taboolib.module.incision

import io.izzel.incision.bridge.IncisionBridge
import taboolib.common.Inject
import taboolib.common.LifeCycle
import taboolib.common.env.RuntimeDependencies
Expand All @@ -19,6 +18,7 @@ import taboolib.module.incision.loader.PipelineBackend
import taboolib.module.incision.reflex.IncisionReflex
import taboolib.module.incision.remap.RemapRouter
import taboolib.module.incision.remap.TabooLibNmsResolver
import taboolib.module.incision.runtime.CanonicalBridge
import taboolib.module.incision.runtime.SurgeryRegistry
import taboolib.module.incision.runtime.TheatreDispatcher

Expand Down Expand Up @@ -50,13 +50,7 @@ object IncisionBootstrap {
TabooLibNmsResolver.installIfAvailable()
// 2. 安装反射穿透适配器,让 TabooLib reflex 在 invoke 时自动 withoutIncision
IncisionReflex.installReflexAdapter()
// 3. 把经 gradle 重定位后真实的 TheatreDispatcher 类名注册进桥(插件 CL 版本)
try {
IncisionBridge.registerLocalDispatcher(TheatreDispatcher::class.java)
} catch (t: Throwable) {
Forensics.warn("Incision bridge registerLocalDispatcher failed: ${t.message}")
}
// 4. 注入 IncisionBridge 到 bootstrap ClassLoader,使跨 CL 目标(NMS、Bukkit API)能解析桥类
// 3. 注入 IncisionBridge 到 bootstrap ClassLoader,使跨 CL 目标(NMS、Bukkit API)能解析桥类
// 必须在任何 installWeaver/retransform 之前完成
injectBridgeIntoSystemClassLoader()
Forensics.info("Incision CONST: api=$API_VERSION resolver=${RemapRouter.name()}")
Expand All @@ -70,7 +64,7 @@ object IncisionBootstrap {
// 1. 接入 / 创建 IncisionGate
try {
val gate = IncisionGateLocator.locateOrCreate(API_VERSION)
IncisionBridge.bindSystemHost(gate)
CanonicalBridge.bindSystemHost(gate)
Forensics.info("Incision Gate online: api=${gate.apiVersion()}")
} catch (t: Throwable) {
Forensics.warn("Incision Gate 接入失败(将使用本地 dispatcher 兜底):${t.javaClass.name}: ${t.message}")
Expand Down Expand Up @@ -104,10 +98,7 @@ object IncisionBootstrap {
PipelineBackend.clear()
InstrumentationBackend.clearTransformers()
// 解绑本插件在桥上的本地 dispatcher 缓存
runCatching {
IncisionBridge.unregisterLocalDispatcher(IncisionBootstrap::class.java.classLoader)
}
IncisionBridge.unbindSystemHost()
CanonicalBridge.unregisterDispatcher(IncisionBootstrap::class.java.classLoader)
GateBootstrapper.release(IncisionBootstrap::class.java.classLoader)
JvmtiBackend.dispose()
}
Expand Down Expand Up @@ -165,8 +156,8 @@ object IncisionBootstrap {
} catch (_: Throwable) {
null
}
if (existing != null && existing.classLoader == null) {
Forensics.info("IncisionBridge 已存在于 bootstrap ClassLoader")
if (existing != null && (existing.classLoader == null || existing.classLoader === sysCL)) {
Forensics.info("IncisionBridge 已存在于 ${existing.classLoader ?: "bootstrap"} ClassLoader")
registerDispatcherOn(existing)
return
}
Expand Down Expand Up @@ -208,13 +199,17 @@ object IncisionBootstrap {
}
}
// 路径 3: fallback — 仅在插件 CL 中,跨 CL 目标无法使用
runCatching {
Class.forName(bridgeClassName, true, IncisionBootstrap::class.java.classLoader)
}.getOrNull()?.let(::registerDispatcherOn)
Forensics.warn("IncisionBridge 未能注入 bootstrap/system CL — 跨 ClassLoader 目标(NMS、Bukkit API)将不可用")
}

private fun registerDispatcherOn(bridgeClass: Class<*>) {
try {
val regMethod = bridgeClass.getMethod("registerLocalDispatcher", Class::class.java)
regMethod.invoke(null, TheatreDispatcher::class.java)
// 后续 target 注册、host 绑定与卸载必须复用同一个类句柄,不能再直接链接插件内同名 Bridge。
CanonicalBridge.bind(bridgeClass)
CanonicalBridge.registerDispatcher(TheatreDispatcher::class.java)
Forensics.info("IncisionBridge dispatcher 已注册 (CL=${bridgeClass.classLoader ?: "bootstrap"})")
} catch (t: Throwable) {
Forensics.warn("IncisionBridge registerLocalDispatcher 失败: ${t.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ object Scalpel {
continue
}
activeTokens[resolvedOwner] = token
syncRuntimeAliases(resolvedOwner, ownerEntries.values.toList())
Forensics.debug("installWeaver status=${installation.status} owner=$owner resolved=$resolvedOwner advices=${group.size} total=${ownerEntries.size} backend=${backend.name}")
}
}
Expand Down Expand Up @@ -256,14 +257,14 @@ object Scalpel {
val token = installation.token ?: return false
if (installation.status !in setOf(Backend.InstallStatus.INSTALLED, Backend.InstallStatus.PENDING_LOAD)) return false
activeTokens[owner] = token
syncRuntimeAliases(owner, entries)
return true
}

/** 把逻辑声明统一翻译为运行时坐标;宿主与 Site 必须经过同一条映射链。 */
private fun buildRuntimeTargets(resolvedOwner: String, entries: List<AdviceEntry>): List<ScalpelWeaver.AdviceTargetSpec> {
return entries.groupBy { it.target }.map { (target, targetEntries) ->
val (resolvedName, resolvedDescriptor) = RemapRouter.resolveMethod(target.owner, target.name, target.descriptor)
val runtimeTarget = target.copy(owner = resolvedOwner, name = resolvedName, descriptor = resolvedDescriptor)
val runtimeTarget = resolveRuntimeTarget(resolvedOwner, target)
ScalpelWeaver.AdviceTargetSpec(
target = runtimeTarget,
kinds = targetEntries.map { it.kind }.toSet(),
Expand Down Expand Up @@ -294,6 +295,20 @@ object Scalpel {
}
}

/** 后端确认接受计划后才发布别名,失败安装不得留下一个永远不会被字节码调用的幽灵 chain。 */
private fun syncRuntimeAliases(resolvedOwner: String, entries: List<AdviceEntry>) {
entries.groupBy { it.target }.forEach { (logicalTarget, targetEntries) ->
val runtimeTarget = resolveRuntimeTarget(resolvedOwner, logicalTarget)
TheatreDispatcher.registerRuntimeAlias(runtimeTarget, targetEntries)
}
}

/** 宿主坐标只允许经过这一条解析链,保证 weave key、Bridge route 与 dispatcher alias 完全一致。 */
private fun resolveRuntimeTarget(resolvedOwner: String, target: taboolib.module.incision.api.MethodCoordinate): taboolib.module.incision.api.MethodCoordinate {
val (resolvedName, resolvedDescriptor) = RemapRouter.resolveMethod(target.owner, target.name, target.descriptor)
return target.copy(owner = resolvedOwner, name = resolvedName, descriptor = resolvedDescriptor)
}

/**
* 插件卸载边界:移除全部 transformer 与累计计划,避免下一 ClassLoader 再次叠加旧织入。
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package taboolib.module.incision.gate
import taboolib.module.incision.diagnostic.Forensics
import taboolib.module.incision.diagnostic.Trauma
import taboolib.module.incision.loader.InstrumentationBackend
import taboolib.module.incision.runtime.CanonicalBridge
import taboolib.platform.bukkit.Exchanges
import java.io.File
import java.io.FileOutputStream
Expand Down Expand Up @@ -41,10 +42,14 @@ object GateBootstrapper {
val gate = bound ?: return
runCatching { gate.healByClassLoader(classLoader) }
bound = null
val holder = runCatching {
Class.forName("taboolib.incision.gate.IncisionGate\$V${gate.apiVersion()}", false, ClassLoader.getSystemClassLoader())
}.getOrNull()
runCatching { holder?.getMethod("setDelegate", Object::class.java)?.invoke(null, null) }
// holder 是 JVM 共享对象。任一插件单独 disable 都不能清掉其他 lease 正在使用的 delegate;
// canonical Bridge 在最后一个 dispatcher 注销时同步断开 systemHost,二者必须一起归零。
if (CanonicalBridge.localLeaseCount() == 0) {
val holder = runCatching {
Class.forName("taboolib.incision.gate.IncisionGate\$V${gate.apiVersion()}", false, ClassLoader.getSystemClassLoader())
}.getOrNull()
runCatching { holder?.getMethod("setDelegate", Object::class.java)?.invoke(null, null) }
}
}

fun bootstrap(apiVersion: Int): IncisionGateApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class AdviceChain(val target: MethodCoordinate) {
private val entries = java.util.concurrent.CopyOnWriteArrayList<AdviceEntry>()

fun add(entry: AdviceEntry) {
// 聚合计划重装会再次同步逻辑/运行时别名;同一 id 必须替换而不是重复执行。
entries.removeIf { it.id == entry.id }
entries.add(entry)
entries.sortByDescending { it.priority }
}
Expand Down
Loading
Loading