1 package jdk.incubator.code.dialect.java;
  2 
  3 import jdk.incubator.code.Body;
  4 import jdk.incubator.code.CodeType;
  5 import jdk.incubator.code.Op;
  6 import jdk.incubator.code.Value;
  7 import jdk.incubator.code.dialect.core.CoreOp;
  8 import jdk.incubator.code.internal.ArithmeticAndConvOpImpls;
  9 
 10 import java.lang.invoke.MethodHandles;
 11 import java.lang.invoke.VarHandle;
 12 import java.lang.reflect.Field;
 13 import java.lang.reflect.Modifier;
 14 import java.util.HashMap;
 15 import java.util.List;
 16 import java.util.Map;
 17 import java.util.Optional;
 18 
 19 import static jdk.incubator.code.dialect.java.JavaType.J_L_STRING;
 20 import static jdk.incubator.code.dialect.java.JavaType.VOID;
 21 
 22 final class ConstantExpressionEvaluator {
 23     private final MethodHandles.Lookup l;
 24     private final Map<Value, Object> m = new HashMap<>();
 25 
 26     ConstantExpressionEvaluator(MethodHandles.Lookup l) {
 27         this.l = l;
 28     }
 29 
 30     <T extends Op & JavaOp.JavaExpression> Optional<Object> evaluate(T op) {
 31         try {
 32             Object v = this.eval(op);
 33             return Optional.ofNullable(v);
 34         } catch (ArithmeticAndConvOpImpls.NonConstantExpression e) {
 35             return Optional.empty();
 36         }
 37     }
 38 
 39     Optional<Object> evaluate(Value v) {
 40         try {
 41             Object o = this.eval(v);
 42             return Optional.ofNullable(o);
 43         } catch (ArithmeticAndConvOpImpls.NonConstantExpression e) {
 44             return Optional.empty();
 45         }
 46     }
 47 
 48     private Object eval(Op op) {
 49         if (m.containsKey(op.result())) {
 50             return m.get(op.result());
 51         }
 52         Object r = switch (op) {
 53             case CoreOp.ConstantOp cop when isConstant(cop) -> {
 54                 Object v = cop.value();
 55                 yield v instanceof String s ? s.intern() : v;
 56             }
 57             case CoreOp.VarAccessOp.VarLoadOp varLoadOp when varLoadOp.operands().getFirst() instanceof Op.Result &&
 58                     isConstant(varLoadOp.varOp()) -> eval(varLoadOp.varOp().initOperand());
 59             case JavaOp.ConvOp _ -> {
 60                 // we expect cast to primitive type
 61                 var v = eval(op.operands().getFirst());
 62                 yield ArithmeticAndConvOpImpls.evaluate(op, List.of(v));
 63             }
 64             case JavaOp.CastOp castOp -> {
 65                 // we expect cast to String
 66                 Value operand = castOp.operands().getFirst();
 67                 if (!castOp.resultType().equals(J_L_STRING) || !operand.type().equals(J_L_STRING)) {
 68                     throw new ArithmeticAndConvOpImpls.NonConstantExpression();
 69                 }
 70                 Object v = eval(operand);
 71                 if (!(v instanceof String s)) {
 72                     throw new ArithmeticAndConvOpImpls.NonConstantExpression();
 73                 }
 74                 yield s;
 75             }
 76             case JavaOp.ConcatOp concatOp -> {
 77                 Object first = eval(concatOp.operands().getFirst());
 78                 Object second = eval(concatOp.operands().getLast());
 79                 yield (first.toString() + second).intern();
 80             }
 81             case JavaOp.FieldAccessOp.FieldLoadOp fieldLoadOp -> {
 82                 Field field;
 83                 VarHandle vh;
 84                 try {
 85                     field = fieldLoadOp.fieldReference().resolveToField(l);
 86                     vh = fieldLoadOp.fieldReference().resolveToHandle(l);
 87                 } catch (ReflectiveOperationException | IllegalArgumentException _) {
 88                     // we cann't reflectivelly get the field
 89                     throw new ArithmeticAndConvOpImpls.NonConstantExpression();
 90                 }
 91                 // Requirement: the field must be a constant variable.
 92                 // Current checks:
 93                 // 1) The field is declared final.
 94                 // 2) The field type is a primitive or String.
 95                 // Missing check:
 96                 // 3) Verify the field is initialized and the initializer is a constant expression.
 97                 if ((field.getModifiers() & Modifier.FINAL) == 0 ||
 98                         !isConstantType(fieldLoadOp.fieldReference().type())) {
 99                     throw new ArithmeticAndConvOpImpls.NonConstantExpression();
100                 }
101                 if ((field.getModifiers() & Modifier.STATIC) != 0) {
102                     Object v;
103                     try {
104                         v = vh.get();
105                     } catch (Throwable t) {
106                         throw new ArithmeticAndConvOpImpls.NonConstantExpression();
107                     }
108                     if (!isConstantValue(v)) {
109                         throw new ArithmeticAndConvOpImpls.NonConstantExpression();
110                     }
111                     yield v instanceof String s ? s.intern() : v;
112                 } else {
113                     // we can't get the value of an instance field from the model
114                     // we need the value of the receiver
115                     throw new ArithmeticAndConvOpImpls.NonConstantExpression();
116                 }
117             }
118             case JavaOp.ArithmeticOperation _ -> {
119                 List<Object> values = op.operands().stream().map(this::eval).toList();
120                 yield ArithmeticAndConvOpImpls.evaluate(op, values);
121             }
122             case JavaOp.ConditionalExpressionOp _ -> {
123                 boolean p = evalBoolean(op.bodies().get(0));
124                 Object t = eval(op.bodies().get(1));
125                 Object f = eval(op.bodies().get(2));
126                 yield p ? t : f;
127             }
128             case JavaOp.ConditionalAndOp _ -> {
129                 boolean left = evalBoolean(op.bodies().get(0));
130                 boolean right = evalBoolean(op.bodies().get(1));
131                 yield left && right;
132             }
133             case JavaOp.ConditionalOrOp _ -> {
134                 boolean left = evalBoolean(op.bodies().get(0));
135                 boolean right = evalBoolean(op.bodies().get(1));
136                 yield left || right;
137             }
138             default -> throw new ArithmeticAndConvOpImpls.NonConstantExpression();
139         };
140         m.put(op.result(), r);
141         return r;
142     }
143 
144     private Object eval(Value v) {
145         if (v.declaringElement() instanceof JavaOp.JavaExpression e) {
146             return eval((Op & JavaOp.JavaExpression) e);
147         }
148         throw new ArithmeticAndConvOpImpls.NonConstantExpression();
149     }
150 
151     private Object eval(Body body) throws ArithmeticAndConvOpImpls.NonConstantExpression {
152         if (body.blocks().size() != 1 ||
153                 !(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
154                 yop.yieldValue() == null ||
155                 !isConstantType(yop.yieldValue().type())) {
156             throw new ArithmeticAndConvOpImpls.NonConstantExpression();
157         }
158         return eval(yop.yieldValue());
159     }
160 
161     private boolean evalBoolean(Body body) throws ArithmeticAndConvOpImpls.NonConstantExpression {
162         Object eval = eval(body);
163         if (!(eval instanceof Boolean b)) {
164             throw new ArithmeticAndConvOpImpls.NonConstantExpression();
165         }
166         return b;
167     }
168 
169     private static boolean isConstant(CoreOp.ConstantOp op) {
170         return isConstantType(op.resultType()) && isConstantValue(op.value());
171     }
172 
173     private static boolean isConstant(CoreOp.VarOp op) {
174         // Requirement: the local variable must be a constant variable.
175         // Current checks:
176         // 1) The variable is initialized, and the initializer is a constant expression.
177         // 2) The variable type is a primitive or String.
178         // Missing check:
179         // 3) Ensure the variable is declared final
180         return isConstantType(op.varValueType()) &&
181                 !op.isUninitialized() &&
182                 // @@@ Add to VarOp
183                 op.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
184     }
185 
186     private static boolean isConstantValue(Object o) {
187         return switch (o) {
188             case String _ -> true;
189             case Boolean _, Byte _, Short _, Character _, Integer _, Long _, Float _, Double _ -> true;
190             case null, default -> false;
191         };
192     }
193 
194     private static boolean isConstantType(CodeType e) {
195         return (e instanceof PrimitiveType && !VOID.equals(e)) || J_L_STRING.equals(e);
196     }
197 }