package com.codechecker.cache.key; import com.codechecker.cache.schema.SourceIndex; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.BinaryExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.ReturnStmt; import java.util.Optional; /** * 尽力将 Redis key 表达式静态推断为一个「key 模式」,动态部分以 {@code *} 表示。 */ public class RedisKeyResolver { private static final int MAX_DEPTH = 8; private final SourceIndex index; public RedisKeyResolver(SourceIndex index) { this.index = index; } public String resolve(Expression keyExpr, ClassOrInterfaceDeclaration enclosingClass, SourceIndex.IndexedType context) { String raw = resolveExpr(keyExpr, enclosingClass, context, 0); return normalize(raw); } private String resolveExpr(Expression expr, ClassOrInterfaceDeclaration enclosingClass, SourceIndex.IndexedType context, int depth) { if (expr == null || depth > MAX_DEPTH) { return "*"; } if (expr instanceof StringLiteralExpr) { return ((StringLiteralExpr) expr).asString(); } if (expr instanceof BinaryExpr) { BinaryExpr be = (BinaryExpr) expr; if (be.getOperator() == BinaryExpr.Operator.PLUS) { return resolveExpr(be.getLeft(), enclosingClass, context, depth + 1) + resolveExpr(be.getRight(), enclosingClass, context, depth + 1); } return "*"; } if (expr instanceof NameExpr) { String name = ((NameExpr) expr).getNameAsString(); String constVal = lookupConstant(enclosingClass, name, context, depth); if (constVal != null) { return constVal; } String methodVal = lookupMethodReturn(enclosingClass, name, context, depth); return methodVal != null ? methodVal : "*"; } if (expr instanceof FieldAccessExpr) { FieldAccessExpr fae = (FieldAccessExpr) expr; String fieldName = fae.getNameAsString(); if (fae.getScope() instanceof NameExpr) { String scope = ((NameExpr) fae.getScope()).getNameAsString(); String local = lookupConstant(enclosingClass, fieldName, context, depth); if (local != null && scope.equals(enclosingClass == null ? "" : enclosingClass.getNameAsString())) { return local; } String external = lookupExternalConstant(scope, fieldName, context, depth); if (external != null) { return external; } } String scope = fae.getScope().toString(); String external = lookupExternalConstant(scope, fieldName, context, depth); if (external != null) { return external; } String local = lookupConstant(enclosingClass, fieldName, context, depth); return local != null ? local : "*"; } if (expr instanceof MethodCallExpr) { MethodCallExpr call = (MethodCallExpr) expr; String name = call.getNameAsString(); if ("format".equals(name) && !call.getArguments().isEmpty()) { String fmt = resolveExpr(call.getArgument(0), enclosingClass, context, depth + 1); return fmt.replaceAll("%[-0-9.]*[sdxDX]", "*"); } String methodVal = lookupMethodReturn(enclosingClass, name, context, depth); return methodVal != null ? methodVal : "*"; } return "*"; } private String lookupConstant(ClassOrInterfaceDeclaration clazz, String name, SourceIndex.IndexedType context, int depth) { if (clazz == null) { return null; } for (FieldDeclaration field : clazz.getFields()) { if (!field.isStatic()) { continue; } for (VariableDeclarator var : field.getVariables()) { if (var.getNameAsString().equals(name)) { Optional init = var.getInitializer(); if (init.isPresent()) { return resolveExpr(init.get(), clazz, context, depth + 1); } } } } return null; } private String lookupExternalConstant(String scopeName, String fieldName, SourceIndex.IndexedType context, int depth) { String fqn = index.resolveFqn(scopeName, context); if (fqn == null) { return null; } SourceIndex.IndexedType type = index.get(fqn); if (type == null) { return null; } return lookupConstant(type.getDeclaration(), fieldName, type, depth); } private String lookupMethodReturn(ClassOrInterfaceDeclaration clazz, String methodName, SourceIndex.IndexedType context, int depth) { if (clazz == null || depth > MAX_DEPTH) { return null; } for (MethodDeclaration method : clazz.getMethods()) { if (method.getNameAsString().equals(methodName) && method.getBody().isPresent()) { for (ReturnStmt ret : method.getBody().get().findAll(ReturnStmt.class)) { if (ret.getExpression().isPresent()) { return resolveExpr(ret.getExpression().get(), clazz, context, depth + 1); } } } } return null; } private String normalize(String raw) { if (raw == null || raw.isEmpty()) { return "unknown-key"; } String collapsed = raw.replaceAll("\\*+", "*"); if (collapsed.equals("*")) { return "unknown-key"; } return collapsed; } }