001/* 002 * Copyright 2002-2019 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.math.BigDecimal; 020import java.math.BigInteger; 021 022import org.springframework.asm.Label; 023import org.springframework.asm.MethodVisitor; 024import org.springframework.expression.EvaluationContext; 025import org.springframework.expression.spel.CodeFlow; 026import org.springframework.lang.Nullable; 027import org.springframework.util.ClassUtils; 028import org.springframework.util.NumberUtils; 029import org.springframework.util.ObjectUtils; 030 031/** 032 * Common supertype for operators that operate on either one or two operands. 033 * In the case of multiply or divide there would be two operands, but for 034 * unary plus or minus, there is only one. 035 * 036 * @author Andy Clement 037 * @author Juergen Hoeller 038 * @author Giovanni Dall'Oglio Risso 039 * @since 3.0 040 */ 041public abstract class Operator extends SpelNodeImpl { 042 043 private final String operatorName; 044 045 // The descriptors of the runtime operand values are used if the discovered declared 046 // descriptors are not providing enough information (for example a generic type 047 // whose accessors seem to only be returning 'Object' - the actual descriptors may 048 // indicate 'int') 049 050 @Nullable 051 protected String leftActualDescriptor; 052 053 @Nullable 054 protected String rightActualDescriptor; 055 056 057 public Operator(String payload, int startPos, int endPos, SpelNodeImpl... operands) { 058 super(startPos, endPos, operands); 059 this.operatorName = payload; 060 } 061 062 063 public SpelNodeImpl getLeftOperand() { 064 return this.children[0]; 065 } 066 067 public SpelNodeImpl getRightOperand() { 068 return this.children[1]; 069 } 070 071 public final String getOperatorName() { 072 return this.operatorName; 073 } 074 075 /** 076 * String format for all operators is the same 077 * {@code '(' [operand] [operator] [operand] ')'}. 078 */ 079 @Override 080 public String toStringAST() { 081 StringBuilder sb = new StringBuilder("("); 082 sb.append(getChild(0).toStringAST()); 083 for (int i = 1; i < getChildCount(); i++) { 084 sb.append(" ").append(getOperatorName()).append(" "); 085 sb.append(getChild(i).toStringAST()); 086 } 087 sb.append(")"); 088 return sb.toString(); 089 } 090 091 092 protected boolean isCompilableOperatorUsingNumerics() { 093 SpelNodeImpl left = getLeftOperand(); 094 SpelNodeImpl right = getRightOperand(); 095 if (!left.isCompilable() || !right.isCompilable()) { 096 return false; 097 } 098 099 // Supported operand types for equals (at the moment) 100 String leftDesc = left.exitTypeDescriptor; 101 String rightDesc = right.exitTypeDescriptor; 102 DescriptorComparison dc = DescriptorComparison.checkNumericCompatibility( 103 leftDesc, rightDesc, this.leftActualDescriptor, this.rightActualDescriptor); 104 return (dc.areNumbers && dc.areCompatible); 105 } 106 107 /** 108 * Numeric comparison operators share very similar generated code, only differing in 109 * two comparison instructions. 110 */ 111 protected void generateComparisonCode(MethodVisitor mv, CodeFlow cf, int compInstruction1, int compInstruction2) { 112 SpelNodeImpl left = getLeftOperand(); 113 SpelNodeImpl right = getRightOperand(); 114 String leftDesc = left.exitTypeDescriptor; 115 String rightDesc = right.exitTypeDescriptor; 116 Label elseTarget = new Label(); 117 Label endOfIf = new Label(); 118 boolean unboxLeft = !CodeFlow.isPrimitive(leftDesc); 119 boolean unboxRight = !CodeFlow.isPrimitive(rightDesc); 120 DescriptorComparison dc = DescriptorComparison.checkNumericCompatibility( 121 leftDesc, rightDesc, this.leftActualDescriptor, this.rightActualDescriptor); 122 char targetType = dc.compatibleType; // CodeFlow.toPrimitiveTargetDesc(leftDesc); 123 124 cf.enterCompilationScope(); 125 left.generateCode(mv, cf); 126 cf.exitCompilationScope(); 127 if (CodeFlow.isPrimitive(leftDesc)) { 128 CodeFlow.insertBoxIfNecessary(mv, leftDesc); 129 unboxLeft = true; 130 } 131 132 cf.enterCompilationScope(); 133 right.generateCode(mv, cf); 134 cf.exitCompilationScope(); 135 if (CodeFlow.isPrimitive(rightDesc)) { 136 CodeFlow.insertBoxIfNecessary(mv, rightDesc); 137 unboxRight = true; 138 } 139 140 // This code block checks whether the left or right operand is null and handles 141 // those cases before letting the original code (that only handled actual numbers) run 142 Label rightIsNonNull = new Label(); 143 mv.visitInsn(DUP); // stack: left/right/right 144 mv.visitJumpInsn(IFNONNULL, rightIsNonNull); // stack: left/right 145 // here: RIGHT==null LEFT==unknown 146 mv.visitInsn(SWAP); // right/left 147 Label leftNotNullRightIsNull = new Label(); 148 mv.visitJumpInsn(IFNONNULL, leftNotNullRightIsNull); // stack: right 149 // here: RIGHT==null LEFT==null 150 mv.visitInsn(POP); // stack: <nothing> 151 // load 0 or 1 depending on comparison instruction 152 switch (compInstruction1) { 153 case IFGE: // OpLT 154 case IFLE: // OpGT 155 mv.visitInsn(ICONST_0); // false - null is not < or > null 156 break; 157 case IFGT: // OpLE 158 case IFLT: // OpGE 159 mv.visitInsn(ICONST_1); // true - null is <= or >= null 160 break; 161 default: 162 throw new IllegalStateException("Unsupported: " + compInstruction1); 163 } 164 mv.visitJumpInsn(GOTO, endOfIf); 165 mv.visitLabel(leftNotNullRightIsNull); // stack: right 166 // RIGHT==null LEFT!=null 167 mv.visitInsn(POP); // stack: <nothing> 168 // load 0 or 1 depending on comparison instruction 169 switch (compInstruction1) { 170 case IFGE: // OpLT 171 case IFGT: // OpLE 172 mv.visitInsn(ICONST_0); // false - something is not < or <= null 173 break; 174 case IFLE: // OpGT 175 case IFLT: // OpGE 176 mv.visitInsn(ICONST_1); // true - something is > or >= null 177 break; 178 default: 179 throw new IllegalStateException("Unsupported: " + compInstruction1); 180 } 181 mv.visitJumpInsn(GOTO, endOfIf); 182 183 mv.visitLabel(rightIsNonNull); // stack: left/right 184 // here: RIGHT!=null LEFT==unknown 185 mv.visitInsn(SWAP); // stack: right/left 186 mv.visitInsn(DUP); // stack: right/left/left 187 Label neitherRightNorLeftAreNull = new Label(); 188 mv.visitJumpInsn(IFNONNULL, neitherRightNorLeftAreNull); // stack: right/left 189 // here: RIGHT!=null LEFT==null 190 mv.visitInsn(POP2); // stack: <nothing> 191 switch (compInstruction1) { 192 case IFGE: // OpLT 193 case IFGT: // OpLE 194 mv.visitInsn(ICONST_1); // true - null is < or <= something 195 break; 196 case IFLE: // OpGT 197 case IFLT: // OpGE 198 mv.visitInsn(ICONST_0); // false - null is not > or >= something 199 break; 200 default: 201 throw new IllegalStateException("Unsupported: " + compInstruction1); 202 } 203 mv.visitJumpInsn(GOTO, endOfIf); 204 mv.visitLabel(neitherRightNorLeftAreNull); // stack: right/left 205 // neither were null so unbox and proceed with numeric comparison 206 if (unboxLeft) { 207 CodeFlow.insertUnboxInsns(mv, targetType, leftDesc); 208 } 209 // What we just unboxed might be a double slot item (long/double) 210 // so can't just use SWAP 211 // stack: right/left(1or2slots) 212 if (targetType == 'D' || targetType == 'J') { 213 mv.visitInsn(DUP2_X1); 214 mv.visitInsn(POP2); 215 } 216 else { 217 mv.visitInsn(SWAP); 218 } 219 // stack: left(1or2)/right 220 if (unboxRight) { 221 CodeFlow.insertUnboxInsns(mv, targetType, rightDesc); 222 } 223 224 // assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc) 225 if (targetType == 'D') { 226 mv.visitInsn(DCMPG); 227 mv.visitJumpInsn(compInstruction1, elseTarget); 228 } 229 else if (targetType == 'F') { 230 mv.visitInsn(FCMPG); 231 mv.visitJumpInsn(compInstruction1, elseTarget); 232 } 233 else if (targetType == 'J') { 234 mv.visitInsn(LCMP); 235 mv.visitJumpInsn(compInstruction1, elseTarget); 236 } 237 else if (targetType == 'I') { 238 mv.visitJumpInsn(compInstruction2, elseTarget); 239 } 240 else { 241 throw new IllegalStateException("Unexpected descriptor " + leftDesc); 242 } 243 244 // Other numbers are not yet supported (isCompilable will not have returned true) 245 mv.visitInsn(ICONST_1); 246 mv.visitJumpInsn(GOTO,endOfIf); 247 mv.visitLabel(elseTarget); 248 mv.visitInsn(ICONST_0); 249 mv.visitLabel(endOfIf); 250 cf.pushDescriptor("Z"); 251 } 252 253 254 /** 255 * Perform an equality check for the given operand values. 256 * <p>This method is not just used for reflective comparisons in subclasses 257 * but also from compiled expression code, which is why it needs to be 258 * declared as {@code public static} here. 259 * @param context the current evaluation context 260 * @param left the left-hand operand value 261 * @param right the right-hand operand value 262 */ 263 public static boolean equalityCheck(EvaluationContext context, @Nullable Object left, @Nullable Object right) { 264 if (left instanceof Number && right instanceof Number) { 265 Number leftNumber = (Number) left; 266 Number rightNumber = (Number) right; 267 268 if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { 269 BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); 270 BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); 271 return (leftBigDecimal.compareTo(rightBigDecimal) == 0); 272 } 273 else if (leftNumber instanceof Double || rightNumber instanceof Double) { 274 return (leftNumber.doubleValue() == rightNumber.doubleValue()); 275 } 276 else if (leftNumber instanceof Float || rightNumber instanceof Float) { 277 return (leftNumber.floatValue() == rightNumber.floatValue()); 278 } 279 else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) { 280 BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class); 281 BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class); 282 return (leftBigInteger.compareTo(rightBigInteger) == 0); 283 } 284 else if (leftNumber instanceof Long || rightNumber instanceof Long) { 285 return (leftNumber.longValue() == rightNumber.longValue()); 286 } 287 else if (leftNumber instanceof Integer || rightNumber instanceof Integer) { 288 return (leftNumber.intValue() == rightNumber.intValue()); 289 } 290 else if (leftNumber instanceof Short || rightNumber instanceof Short) { 291 return (leftNumber.shortValue() == rightNumber.shortValue()); 292 } 293 else if (leftNumber instanceof Byte || rightNumber instanceof Byte) { 294 return (leftNumber.byteValue() == rightNumber.byteValue()); 295 } 296 else { 297 // Unknown Number subtypes -> best guess is double comparison 298 return (leftNumber.doubleValue() == rightNumber.doubleValue()); 299 } 300 } 301 302 if (left instanceof CharSequence && right instanceof CharSequence) { 303 return left.toString().equals(right.toString()); 304 } 305 306 if (left instanceof Boolean && right instanceof Boolean) { 307 return left.equals(right); 308 } 309 310 if (ObjectUtils.nullSafeEquals(left, right)) { 311 return true; 312 } 313 314 if (left instanceof Comparable && right instanceof Comparable) { 315 Class<?> ancestor = ClassUtils.determineCommonAncestor(left.getClass(), right.getClass()); 316 if (ancestor != null && Comparable.class.isAssignableFrom(ancestor)) { 317 return (context.getTypeComparator().compare(left, right) == 0); 318 } 319 } 320 321 return false; 322 } 323 324 325 /** 326 * A descriptor comparison encapsulates the result of comparing descriptor 327 * for two operands and describes at what level they are compatible. 328 */ 329 protected static final class DescriptorComparison { 330 331 static final DescriptorComparison NOT_NUMBERS = new DescriptorComparison(false, false, ' '); 332 333 static final DescriptorComparison INCOMPATIBLE_NUMBERS = new DescriptorComparison(true, false, ' '); 334 335 final boolean areNumbers; // Were the two compared descriptor both for numbers? 336 337 final boolean areCompatible; // If they were numbers, were they compatible? 338 339 final char compatibleType; // When compatible, what is the descriptor of the common type 340 341 private DescriptorComparison(boolean areNumbers, boolean areCompatible, char compatibleType) { 342 this.areNumbers = areNumbers; 343 this.areCompatible = areCompatible; 344 this.compatibleType = compatibleType; 345 } 346 347 /** 348 * Return an object that indicates whether the input descriptors are compatible. 349 * <p>A declared descriptor is what could statically be determined (e.g. from looking 350 * at the return value of a property accessor method) whilst an actual descriptor 351 * is the type of an actual object that was returned, which may differ. 352 * <p>For generic types with unbound type variables, the declared descriptor 353 * discovered may be 'Object' but from the actual descriptor it is possible to 354 * observe that the objects are really numeric values (e.g. ints). 355 * @param leftDeclaredDescriptor the statically determinable left descriptor 356 * @param rightDeclaredDescriptor the statically determinable right descriptor 357 * @param leftActualDescriptor the dynamic/runtime left object descriptor 358 * @param rightActualDescriptor the dynamic/runtime right object descriptor 359 * @return a DescriptorComparison object indicating the type of compatibility, if any 360 */ 361 public static DescriptorComparison checkNumericCompatibility( 362 @Nullable String leftDeclaredDescriptor, @Nullable String rightDeclaredDescriptor, 363 @Nullable String leftActualDescriptor, @Nullable String rightActualDescriptor) { 364 365 String ld = leftDeclaredDescriptor; 366 String rd = rightDeclaredDescriptor; 367 368 boolean leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld); 369 boolean rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd); 370 371 // If the declared descriptors aren't providing the information, try the actual descriptors 372 if (!leftNumeric && !ObjectUtils.nullSafeEquals(ld, leftActualDescriptor)) { 373 ld = leftActualDescriptor; 374 leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld); 375 } 376 if (!rightNumeric && !ObjectUtils.nullSafeEquals(rd, rightActualDescriptor)) { 377 rd = rightActualDescriptor; 378 rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd); 379 } 380 381 if (leftNumeric && rightNumeric) { 382 if (CodeFlow.areBoxingCompatible(ld, rd)) { 383 return new DescriptorComparison(true, true, CodeFlow.toPrimitiveTargetDesc(ld)); 384 } 385 else { 386 return DescriptorComparison.INCOMPATIBLE_NUMBERS; 387 } 388 } 389 else { 390 return DescriptorComparison.NOT_NUMBERS; 391 } 392 } 393 } 394 395}