package jnpf.bizcommon.audit.service;
|
|
import com.alibaba.fastjson.JSONException;
|
import jnpf.util.JsonUtil;
|
|
import java.lang.reflect.Method;
|
import java.util.Map;
|
|
/**
|
* 层 0 负缓存的**失败分类**探针(C1 二轮评审 I-3 的证据脚本)。
|
*
|
* <p>为什么必须实测而不能推:{@code AuditFormRegistry.deterministicParseFailure} 决定
|
* 「这次解析失败要不要进负缓存」。判错的代价不对称——把**瞬时**失败判成确定性,该表单
|
* 从此静默不被审计直到重启(缓存是进程级、无过期的);反过来只是多查一次库。
|
* L061 ① 记的就是这个坑,而它当时正是**推**出来的黑名单被实证推翻的。
|
*
|
* <p>本探针喂各种畸形输入给 {@code JsonUtil.getJsonToList}(层 0 反查主表用的正是它),
|
* 打印真实异常链,再让 {@code deterministicParseFailure} 对它做判定,逐条断言。
|
*
|
* <p>退出码:0 = 全绿 / 1 = 有断言失败。**不连库、不起容器**,秒级。
|
*/
|
public final class AuditL0ParseFailureProbe {
|
|
private static int passed;
|
private static int failed;
|
|
public static void main(String[] args) throws Exception {
|
Method decide = AuditFormRegistry.class
|
.getDeclaredMethod("deterministicParseFailure", Throwable.class);
|
decide.setAccessible(true);
|
|
System.out.println("=== 层0 解析失败分类探针(fastjson 实测链形 + 白名单判定)===");
|
|
// ① 各类语法错误:应抛 JSONException → 确定性 → 可缓存
|
expect(decide, "截断的 JSON", "[{\"table\":\"lims_x\"", true);
|
expect(decide, "顶层非数组", "{\"table\":\"lims_x\"}", true);
|
expect(decide, "元素非对象", "[1,2,3]", true);
|
expect(decide, "空串以外的纯垃圾", "not json at all", true);
|
|
// ② 畸形 Unicode 转义:**实测抛裸的 NumberFormatException、不被包装成 JSONException**
|
// (codex 独立复核发现,本探针独立复核其发现)。它同样是文本的固有属性 → 确定性。
|
expect(decide, "畸形 Unicode 转义 \\uZZZZ", "[{\"table\":\"\\uZZZZ\"}]", true);
|
|
// ③ 瞬时故障:**绝不能**判成确定性
|
assertDecide(decide, "StackOverflowError(深层嵌套的真实形态)",
|
new StackOverflowError(), false);
|
assertDecide(decide, "OutOfMemoryError", new OutOfMemoryError(), false);
|
assertDecide(decide, "线程中断", new InterruptedException(), false);
|
assertDecide(decide, "包了一层的 SOE(池线程形态)",
|
new RuntimeException(new StackOverflowError()), false);
|
|
// ④ 自环 cause 链不能把遍历卡死(L061 的写法要求)。
|
// 注意**不能**用 `e.initCause(e)` 构造——JDK 直接抛 IllegalArgumentException:
|
// Self-causation not permitted(本探针第一版就这么写的,当场炸了)。
|
// 真实的自环只可能来自覆盖了 getCause() 的自定义异常,所以照那个形态造。
|
assertDecide(decide, "自环 cause 链不死循环(覆盖 getCause 的形态)",
|
new SelfCausedException(), false);
|
|
System.out.println();
|
System.out.printf("=== 层0 失败分类断言:%d 通过 / %d 失败 ===%n", passed, failed);
|
if (failed > 0) {
|
System.exit(1);
|
}
|
}
|
|
/** 真喂一段畸形 JSON,打印它实际抛的异常链,再断言白名单的判定。 */
|
private static void expect(Method decide, String label, String json, boolean deterministic) {
|
Throwable caught = null;
|
try {
|
JsonUtil.getJsonToList(json, Map.class);
|
} catch (Throwable t) {
|
caught = t;
|
}
|
if (caught == null) {
|
// 没抛异常也是有效信息(说明 fastjson 宽容地接受了它),不算失败,但要打印出来
|
System.out.printf(" [INFO] %s → 未抛异常(fastjson 接受了这段输入)%n", label);
|
return;
|
}
|
System.out.printf(" 链形 %-28s → %s%n", label, chain(caught));
|
assertDecide(decide, label, caught, deterministic);
|
}
|
|
private static void assertDecide(Method decide, String label, Throwable t, boolean expected) {
|
boolean actual;
|
try {
|
actual = (Boolean) decide.invoke(null, t);
|
} catch (Exception e) {
|
System.out.printf(" [FAIL] %s → 判定方法自身抛异常:%s%n", label, e);
|
failed++;
|
return;
|
}
|
if (actual == expected) {
|
System.out.printf(" [PASS] %s → %s%n", label,
|
expected ? "确定性(可负缓存)" : "瞬时(不缓存)");
|
passed++;
|
} else {
|
System.out.printf(" [FAIL] %s → 判为%s,应为%s%s%n", label,
|
actual ? "确定性" : "瞬时", expected ? "确定性" : "瞬时",
|
expected ? "" : " ← 危险方向:瞬时失败被永久负缓存 = 该表单静默不再被审计");
|
failed++;
|
}
|
}
|
|
private static String chain(Throwable t) {
|
StringBuilder sb = new StringBuilder();
|
for (Throwable c = t; c != null; c = (c.getCause() == c ? null : c.getCause())) {
|
if (sb.length() > 0) {
|
sb.append(" ← ");
|
}
|
sb.append(c.getClass().getSimpleName());
|
if (c instanceof JSONException || c instanceof NumberFormatException) {
|
sb.append("*"); // * = 命中白名单的那一环
|
}
|
}
|
return sb.toString();
|
}
|
|
/** 唯一能造出自环 cause 的形态:覆盖 getCause() 返回自己。 */
|
private static final class SelfCausedException extends RuntimeException {
|
@Override
|
public synchronized Throwable getCause() {
|
return this;
|
}
|
}
|
|
private AuditL0ParseFailureProbe() {
|
}
|
}
|