1 /*
  2  * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import java.lang.invoke.MethodHandles;
 25 import jdk.incubator.code.*;
 26 import jdk.incubator.code.dialect.core.CoreOp;
 27 import jdk.incubator.code.dialect.java.JavaOp;
 28 import jdk.incubator.code.dialect.java.JavaType;
 29 import jdk.incubator.code.dialect.java.MethodRef;
 30 import jdk.incubator.code.extern.OpWriter;
 31 
 32 import java.util.ArrayList;
 33 import java.util.Collections;
 34 import java.util.HashMap;
 35 import java.util.List;
 36 import java.util.Map;
 37 
 38 public final class Verifier {
 39 
 40     public final class VerifyError {
 41 
 42         private final String message;
 43 
 44         public VerifyError(String message) {
 45             this.message = message;
 46         }
 47 
 48         public String getMessage() {
 49             return message;
 50         }
 51 
 52         public String getPrintedContext() {
 53             return toText(rootOp);
 54         }
 55 
 56         @Override
 57         public String toString() {
 58             return getMessage() + " in " + getPrintedContext();
 59         }
 60     }
 61 
 62     public static List<Verifier.VerifyError> verify(Op op) {
 63         return verify(MethodHandles.publicLookup(), op);
 64     }
 65 
 66     public static List<Verifier.VerifyError> verify(MethodHandles.Lookup l, Op op) {
 67         var verifier = new Verifier(l, op);
 68         verifier.verifyOps();
 69         verifier.verifyExceptionRegions();
 70         return verifier.errors == null ? List.of() : Collections.unmodifiableList(verifier.errors);
 71     }
 72 
 73 
 74     private final MethodHandles.Lookup lookup;
 75     private final Op rootOp;
 76     private OpWriter.CodeItemNamerOption namerOption;
 77     private List<Verifier.VerifyError> errors;
 78 
 79     private Verifier(MethodHandles.Lookup lookup, Op rootOp) {
 80         this.lookup = lookup;
 81         this.rootOp = rootOp;
 82     }
 83 
 84     private OpWriter.CodeItemNamerOption getNamer() {
 85         if (namerOption == null) {
 86             namerOption = OpWriter.CodeItemNamerOption.of(OpWriter.computeGlobalNames(rootOp));
 87         }
 88         return namerOption;
 89     }
 90 
 91     private String toText(Op op) {
 92         return OpWriter.toText(op, getNamer());
 93     }
 94 
 95     private String getName(CodeItem codeItem) {
 96         return getNamer().namer().apply(codeItem);
 97     }
 98 
 99     private void error(String message, Object... args) {
100         if (errors == null) {
101             errors = new ArrayList<>();
102         }
103         for (int i = 0; i < args.length; i++) {
104             args[i] = toText(args[i]);
105         }
106         errors.add(new VerifyError(message.formatted(args)));
107     }
108 
109     private String toText(Object arg) {
110         return switch (arg) {
111             case Op op -> toText(op);
112             case Block b -> getName(b);
113             case Value v -> getName(v);
114             case List<?> l -> l.stream().map(this::toText).toList().toString();
115             default -> arg.toString();
116         };
117     }
118 
119     private void verifyOps() {
120         rootOp.elements().forEach(e -> {
121             if (!(e instanceof Op op)) {
122                 return;
123             }
124 
125             // Verify operands declaration dominance
126             for (var v : op.operands()) {
127                 if (!op.result().isDominatedBy(v)) {
128                     error("%s %s operand %s is not dominated by its declaration in %s", op.ancestorBlock(), op, v, v.declaringBlock());
129                 }
130             }
131 
132             // Verify individual Ops
133             switch (op) {
134                 case CoreOp.BranchOp br ->
135                         verifyBlockReferences(op, br.successors());
136                 case CoreOp.ConditionalBranchOp cbr ->
137                         verifyBlockReferences(op, cbr.successors());
138                 case JavaOp.ArithmeticOperation _, JavaOp.TestOperation _ ->
139                         verifyOpHandleExists(op, op.externalizeOpName());
140                 case JavaOp.ConvOp _ -> {
141                     verifyOpHandleExists(op, op.externalizeOpName() + "_" + op.opType().returnType());
142                 }
143                 default -> {}
144 
145             }
146         });
147     }
148 
149     private void verifyBlockReferences(Op op, List<Block.Reference> references) {
150         for (Block.Reference r : references) {
151             Block b = r.targetBlock();
152             List<Value> args = r.arguments();
153             List<Block.Parameter> params = r.targetBlock().parameters();
154             if (args.size() != params.size()) {
155                 error("%s %s block reference arguments size to target block parameters size mismatch", b, op);
156             } else {
157                 Block tb = r.targetBlock();
158                 for (int i = 0; i < args.size(); i++) {
159                     if (!isAssignable(params.get(i).type(), args.get(i), tb, b)) {
160                         error("%s %s %s is not assignable from %s", op.ancestorBlock(), op, params.get(i).type(), args.get(i).type());
161                     }
162                 }
163             }
164         }
165     }
166 
167     private boolean isAssignable(TypeElement toType, Value fromValue,  Object toContext, Object fromContext) {
168         if (toType.equals(fromValue.type())) return true;
169         var to = resolveToClass(toType, toContext);
170         var from = resolveToClass(fromValue.type(), fromContext);
171         if (from.isPrimitive()) {
172             // Primitive types assignability
173             return to == int.class && (from == byte.class || from == short.class || from == char.class);
174         } else {
175             // Objects assignability
176             return to.isAssignableFrom(from);
177         }
178     }
179 
180     public Class<?> resolveToClass(TypeElement d, Object context) {
181         try {
182             if (d instanceof JavaType jt) {
183                 return (Class<?>)jt.erasure().resolve(lookup);
184             } else {
185                 error("%s %s is not a Java type", context, d);
186             }
187         } catch (ReflectiveOperationException e) {
188             error("%s %s", context, e.getMessage());
189         }
190         return Object.class;
191     }
192 
193     static final Class<?> CLASS_INVOKABLE_LEAF_OPS;
194     static {
195         try {
196             CLASS_INVOKABLE_LEAF_OPS = Class.forName("jdk.incubator.code.interpreter.InvokableLeafOps");
197         } catch (ReflectiveOperationException roe) {
198             throw new InternalError(roe);
199         }
200     }
201 
202     private void verifyOpHandleExists(Op op, String opName) {
203         try {
204             var mt = MethodRef.toNominalDescriptor(op.opType()).resolveConstantDesc(lookup).erase();
205             CLASS_INVOKABLE_LEAF_OPS.getDeclaredMethod(opName, mt.parameterArray());
206         } catch (NoSuchMethodException nsme) {
207             error("%s %s of type %s is not supported", op.ancestorBlock(), op, op.opType());
208         } catch (ReflectiveOperationException roe) {
209             error("%s %s %s",  op.ancestorBlock(), op, roe.getMessage());
210         }
211     }
212 
213     private void verifyExceptionRegions() {
214         Map<Block, List<Block>> map = new HashMap<>();
215         rootOp.elements().forEach(e -> {
216             if (!(e instanceof Block b)) {
217                 return;
218             }
219             List<Block> catchBlocks = map.computeIfAbsent(b, _ -> List.of());
220             switch (b.terminatingOp()) {
221                 case CoreOp.BranchOp br ->
222                         verifyCatchStack(b, br, br.branch(), catchBlocks, map);
223                 case CoreOp.ConditionalBranchOp cbr -> {
224                     verifyCatchStack(b, cbr, cbr.trueBranch(), catchBlocks, map);
225                     verifyCatchStack(b, cbr, cbr.falseBranch(), catchBlocks, map);
226                 }
227                 case JavaOp.ExceptionRegionEnter ere -> {
228                     List<Block> newCatchBlocks = new ArrayList<>();
229                     newCatchBlocks.addAll(catchBlocks);
230                     for (Block.Reference cb : ere.catchBlocks()) {
231                         newCatchBlocks.add(cb.targetBlock());
232                         verifyCatchStack(b, ere, cb, catchBlocks, map);
233                     }
234                     verifyCatchStack(b, ere, ere.start(), newCatchBlocks, map);
235                 }
236                 case JavaOp.ExceptionRegionExit ere -> {
237                     List<Block> exitedCatchBlocks = ere.catchBlocks().stream().map(Block.Reference::targetBlock).toList();
238                     if (exitedCatchBlocks.size() > catchBlocks.size() || !catchBlocks.reversed().subList(0, exitedCatchBlocks.size()).equals(exitedCatchBlocks)) {
239                         error("%s %s exited catch blocks %s does not match actual stack %s", b, ere, exitedCatchBlocks, catchBlocks);
240                     } else {
241                         verifyCatchStack(b, ere, ere.end(), catchBlocks.subList(0, catchBlocks.size() - exitedCatchBlocks.size()), map);
242                     }
243                 }
244                 default -> {}
245             }
246         });
247     }
248 
249     private void verifyCatchStack(Block b, Op op, Block.Reference target, List<Block> catchBlocks, Map<Block, List<Block>> blockMap) {
250         blockMap.compute(target.targetBlock(), (tb, stored) -> {
251             if (stored != null && !stored.equals(catchBlocks)) {
252                 error("%s %s catch stack mismatch at target %s %s vs %s", b, op, tb, stored, catchBlocks);
253             }
254             return catchBlocks;
255         });
256     }
257 }