001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.ast; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.lang.reflect.Proxy; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026import java.util.StringJoiner; 027 028import org.springframework.asm.Label; 029import org.springframework.asm.MethodVisitor; 030import org.springframework.core.convert.TypeDescriptor; 031import org.springframework.expression.AccessException; 032import org.springframework.expression.EvaluationContext; 033import org.springframework.expression.EvaluationException; 034import org.springframework.expression.ExpressionInvocationTargetException; 035import org.springframework.expression.MethodExecutor; 036import org.springframework.expression.MethodResolver; 037import org.springframework.expression.TypedValue; 038import org.springframework.expression.spel.CodeFlow; 039import org.springframework.expression.spel.ExpressionState; 040import org.springframework.expression.spel.SpelEvaluationException; 041import org.springframework.expression.spel.SpelMessage; 042import org.springframework.expression.spel.support.ReflectiveMethodExecutor; 043import org.springframework.expression.spel.support.ReflectiveMethodResolver; 044import org.springframework.lang.Nullable; 045import org.springframework.util.Assert; 046import org.springframework.util.ObjectUtils; 047 048/** 049 * Expression language AST node that represents a method reference. 050 * 051 * @author Andy Clement 052 * @author Juergen Hoeller 053 * @since 3.0 054 */ 055public class MethodReference extends SpelNodeImpl { 056 057 private final String name; 058 059 private final boolean nullSafe; 060 061 @Nullable 062 private String originalPrimitiveExitTypeDescriptor; 063 064 @Nullable 065 private volatile CachedMethodExecutor cachedExecutor; 066 067 068 public MethodReference(boolean nullSafe, String methodName, int startPos, int endPos, SpelNodeImpl... arguments) { 069 super(startPos, endPos, arguments); 070 this.name = methodName; 071 this.nullSafe = nullSafe; 072 } 073 074 075 public final String getName() { 076 return this.name; 077 } 078 079 @Override 080 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 081 Object[] arguments = getArguments(state); 082 if (state.getActiveContextObject().getValue() == null) { 083 throwIfNotNullSafe(getArgumentTypes(arguments)); 084 return ValueRef.NullValueRef.INSTANCE; 085 } 086 return new MethodValueRef(state, arguments); 087 } 088 089 @Override 090 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 091 EvaluationContext evaluationContext = state.getEvaluationContext(); 092 Object value = state.getActiveContextObject().getValue(); 093 TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); 094 Object[] arguments = getArguments(state); 095 TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments); 096 updateExitTypeDescriptor(); 097 return result; 098 } 099 100 private TypedValue getValueInternal(EvaluationContext evaluationContext, 101 @Nullable Object value, @Nullable TypeDescriptor targetType, Object[] arguments) { 102 103 List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments); 104 if (value == null) { 105 throwIfNotNullSafe(argumentTypes); 106 return TypedValue.NULL; 107 } 108 109 MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes); 110 if (executorToUse != null) { 111 try { 112 return executorToUse.execute(evaluationContext, value, arguments); 113 } 114 catch (AccessException ex) { 115 // Two reasons this can occur: 116 // 1. the method invoked actually threw a real exception 117 // 2. the method invoked was not passed the arguments it expected and 118 // has become 'stale' 119 120 // In the first case we should not retry, in the second case we should see 121 // if there is a better suited method. 122 123 // To determine the situation, the AccessException will contain a cause. 124 // If the cause is an InvocationTargetException, a user exception was 125 // thrown inside the method. Otherwise the method could not be invoked. 126 throwSimpleExceptionIfPossible(value, ex); 127 128 // At this point we know it wasn't a user problem so worth a retry if a 129 // better candidate can be found. 130 this.cachedExecutor = null; 131 } 132 } 133 134 // either there was no accessor or it no longer existed 135 executorToUse = findAccessorForMethod(argumentTypes, value, evaluationContext); 136 this.cachedExecutor = new CachedMethodExecutor( 137 executorToUse, (value instanceof Class ? (Class<?>) value : null), targetType, argumentTypes); 138 try { 139 return executorToUse.execute(evaluationContext, value, arguments); 140 } 141 catch (AccessException ex) { 142 // Same unwrapping exception handling as above in above catch block 143 throwSimpleExceptionIfPossible(value, ex); 144 throw new SpelEvaluationException(getStartPosition(), ex, 145 SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, 146 value.getClass().getName(), ex.getMessage()); 147 } 148 } 149 150 private void throwIfNotNullSafe(List<TypeDescriptor> argumentTypes) { 151 if (!this.nullSafe) { 152 throw new SpelEvaluationException(getStartPosition(), 153 SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, 154 FormatHelper.formatMethodForMessage(this.name, argumentTypes)); 155 } 156 } 157 158 private Object[] getArguments(ExpressionState state) { 159 Object[] arguments = new Object[getChildCount()]; 160 for (int i = 0; i < arguments.length; i++) { 161 // Make the root object the active context again for evaluating the parameter expressions 162 try { 163 state.pushActiveContextObject(state.getScopeRootContextObject()); 164 arguments[i] = this.children[i].getValueInternal(state).getValue(); 165 } 166 finally { 167 state.popActiveContextObject(); 168 } 169 } 170 return arguments; 171 } 172 173 private List<TypeDescriptor> getArgumentTypes(Object... arguments) { 174 List<TypeDescriptor> descriptors = new ArrayList<>(arguments.length); 175 for (Object argument : arguments) { 176 descriptors.add(TypeDescriptor.forObject(argument)); 177 } 178 return Collections.unmodifiableList(descriptors); 179 } 180 181 @Nullable 182 private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, 183 @Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 184 185 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers(); 186 if (methodResolvers.size() != 1 || !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) { 187 // Not a default ReflectiveMethodResolver - don't know whether caching is valid 188 return null; 189 } 190 191 CachedMethodExecutor executorToCheck = this.cachedExecutor; 192 if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) { 193 return executorToCheck.get(); 194 } 195 this.cachedExecutor = null; 196 return null; 197 } 198 199 private MethodExecutor findAccessorForMethod(List<TypeDescriptor> argumentTypes, Object targetObject, 200 EvaluationContext evaluationContext) throws SpelEvaluationException { 201 202 AccessException accessException = null; 203 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers(); 204 for (MethodResolver methodResolver : methodResolvers) { 205 try { 206 MethodExecutor methodExecutor = methodResolver.resolve( 207 evaluationContext, targetObject, this.name, argumentTypes); 208 if (methodExecutor != null) { 209 return methodExecutor; 210 } 211 } 212 catch (AccessException ex) { 213 accessException = ex; 214 break; 215 } 216 } 217 218 String method = FormatHelper.formatMethodForMessage(this.name, argumentTypes); 219 String className = FormatHelper.formatClassNameForMessage( 220 targetObject instanceof Class ? ((Class<?>) targetObject) : targetObject.getClass()); 221 if (accessException != null) { 222 throw new SpelEvaluationException( 223 getStartPosition(), accessException, SpelMessage.PROBLEM_LOCATING_METHOD, method, className); 224 } 225 else { 226 throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, method, className); 227 } 228 } 229 230 /** 231 * Decode the AccessException, throwing a lightweight evaluation exception or, 232 * if the cause was a RuntimeException, throw the RuntimeException directly. 233 */ 234 private void throwSimpleExceptionIfPossible(Object value, AccessException ex) { 235 if (ex.getCause() instanceof InvocationTargetException) { 236 Throwable rootCause = ex.getCause().getCause(); 237 if (rootCause instanceof RuntimeException) { 238 throw (RuntimeException) rootCause; 239 } 240 throw new ExpressionInvocationTargetException(getStartPosition(), 241 "A problem occurred when trying to execute method '" + this.name + 242 "' on object of type [" + value.getClass().getName() + "]", rootCause); 243 } 244 } 245 246 private void updateExitTypeDescriptor() { 247 CachedMethodExecutor executorToCheck = this.cachedExecutor; 248 if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) { 249 Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod(); 250 String descriptor = CodeFlow.toDescriptor(method.getReturnType()); 251 if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) { 252 this.originalPrimitiveExitTypeDescriptor = descriptor; 253 this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor); 254 } 255 else { 256 this.exitTypeDescriptor = descriptor; 257 } 258 } 259 } 260 261 @Override 262 public String toStringAST() { 263 StringJoiner sj = new StringJoiner(",", "(", ")"); 264 for (int i = 0; i < getChildCount(); i++) { 265 sj.add(getChild(i).toStringAST()); 266 } 267 return this.name + sj.toString(); 268 } 269 270 /** 271 * A method reference is compilable if it has been resolved to a reflectively accessible method 272 * and the child nodes (arguments to the method) are also compilable. 273 */ 274 @Override 275 public boolean isCompilable() { 276 CachedMethodExecutor executorToCheck = this.cachedExecutor; 277 if (executorToCheck == null || executorToCheck.hasProxyTarget() || 278 !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) { 279 return false; 280 } 281 282 for (SpelNodeImpl child : this.children) { 283 if (!child.isCompilable()) { 284 return false; 285 } 286 } 287 288 ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) executorToCheck.get(); 289 if (executor.didArgumentConversionOccur()) { 290 return false; 291 } 292 Class<?> clazz = executor.getMethod().getDeclaringClass(); 293 if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) { 294 return false; 295 } 296 297 return true; 298 } 299 300 @Override 301 public void generateCode(MethodVisitor mv, CodeFlow cf) { 302 CachedMethodExecutor executorToCheck = this.cachedExecutor; 303 if (executorToCheck == null || !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) { 304 throw new IllegalStateException("No applicable cached executor found: " + executorToCheck); 305 } 306 307 ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor) executorToCheck.get(); 308 Method method = methodExecutor.getMethod(); 309 boolean isStaticMethod = Modifier.isStatic(method.getModifiers()); 310 String descriptor = cf.lastDescriptor(); 311 312 Label skipIfNull = null; 313 if (descriptor == null && !isStaticMethod) { 314 // Nothing on the stack but something is needed 315 cf.loadTarget(mv); 316 } 317 if ((descriptor != null || !isStaticMethod) && this.nullSafe) { 318 mv.visitInsn(DUP); 319 skipIfNull = new Label(); 320 Label continueLabel = new Label(); 321 mv.visitJumpInsn(IFNONNULL, continueLabel); 322 CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor); 323 mv.visitJumpInsn(GOTO, skipIfNull); 324 mv.visitLabel(continueLabel); 325 } 326 if (descriptor != null && isStaticMethod) { 327 // Something on the stack when nothing is needed 328 mv.visitInsn(POP); 329 } 330 331 if (CodeFlow.isPrimitive(descriptor)) { 332 CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0)); 333 } 334 335 String classDesc; 336 if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 337 classDesc = method.getDeclaringClass().getName().replace('.', '/'); 338 } 339 else { 340 Class<?> publicDeclaringClass = methodExecutor.getPublicDeclaringClass(); 341 Assert.state(publicDeclaringClass != null, "No public declaring class"); 342 classDesc = publicDeclaringClass.getName().replace('.', '/'); 343 } 344 345 if (!isStaticMethod && (descriptor == null || !descriptor.substring(1).equals(classDesc))) { 346 CodeFlow.insertCheckCast(mv, "L" + classDesc); 347 } 348 349 generateCodeForArguments(mv, cf, method, this.children); 350 mv.visitMethodInsn((isStaticMethod ? INVOKESTATIC : (method.isDefault() ? INVOKEINTERFACE : INVOKEVIRTUAL)), 351 classDesc, method.getName(), CodeFlow.createSignatureDescriptor(method), 352 method.getDeclaringClass().isInterface()); 353 cf.pushDescriptor(this.exitTypeDescriptor); 354 355 if (this.originalPrimitiveExitTypeDescriptor != null) { 356 // The output of the accessor will be a primitive but from the block above it might be null, 357 // so to have a 'common stack' element at skipIfNull target we need to box the primitive 358 CodeFlow.insertBoxIfNecessary(mv, this.originalPrimitiveExitTypeDescriptor); 359 } 360 if (skipIfNull != null) { 361 mv.visitLabel(skipIfNull); 362 } 363 } 364 365 366 private class MethodValueRef implements ValueRef { 367 368 private final EvaluationContext evaluationContext; 369 370 @Nullable 371 private final Object value; 372 373 @Nullable 374 private final TypeDescriptor targetType; 375 376 private final Object[] arguments; 377 378 public MethodValueRef(ExpressionState state, Object[] arguments) { 379 this.evaluationContext = state.getEvaluationContext(); 380 this.value = state.getActiveContextObject().getValue(); 381 this.targetType = state.getActiveContextObject().getTypeDescriptor(); 382 this.arguments = arguments; 383 } 384 385 @Override 386 public TypedValue getValue() { 387 TypedValue result = MethodReference.this.getValueInternal( 388 this.evaluationContext, this.value, this.targetType, this.arguments); 389 updateExitTypeDescriptor(); 390 return result; 391 } 392 393 @Override 394 public void setValue(@Nullable Object newValue) { 395 throw new IllegalAccessError(); 396 } 397 398 @Override 399 public boolean isWritable() { 400 return false; 401 } 402 } 403 404 405 private static class CachedMethodExecutor { 406 407 private final MethodExecutor methodExecutor; 408 409 @Nullable 410 private final Class<?> staticClass; 411 412 @Nullable 413 private final TypeDescriptor target; 414 415 private final List<TypeDescriptor> argumentTypes; 416 417 public CachedMethodExecutor(MethodExecutor methodExecutor, @Nullable Class<?> staticClass, 418 @Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 419 420 this.methodExecutor = methodExecutor; 421 this.staticClass = staticClass; 422 this.target = target; 423 this.argumentTypes = argumentTypes; 424 } 425 426 public boolean isSuitable(Object value, @Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) { 427 return ((this.staticClass == null || this.staticClass == value) && 428 ObjectUtils.nullSafeEquals(this.target, target) && this.argumentTypes.equals(argumentTypes)); 429 } 430 431 public boolean hasProxyTarget() { 432 return (this.target != null && Proxy.isProxyClass(this.target.getType())); 433 } 434 435 public MethodExecutor get() { 436 return this.methodExecutor; 437 } 438 } 439 440}