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