1 /*
2 * Copyright (c) 2024, 2026, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code.internal;
27
28 import com.sun.source.tree.LambdaExpressionTree;
29 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
30 import com.sun.tools.javac.code.Kinds.Kind;
31 import com.sun.tools.javac.code.Symbol;
32 import com.sun.tools.javac.code.Symbol.ClassSymbol;
33 import com.sun.tools.javac.code.Symbol.MethodSymbol;
34 import com.sun.tools.javac.code.Symbol.VarSymbol;
35 import com.sun.tools.javac.code.Symtab;
36 import com.sun.tools.javac.code.Type;
37 import com.sun.tools.javac.code.Type.ArrayType;
38 import com.sun.tools.javac.code.Type.CapturedType;
39 import com.sun.tools.javac.code.Type.IntersectionClassType;
40 import com.sun.tools.javac.code.Type.MethodType;
41 import com.sun.tools.javac.code.Type.StructuralTypeMapping;
42 import com.sun.tools.javac.code.Type.UnionClassType;
43 import com.sun.tools.javac.code.TypeTag;
44 import com.sun.tools.javac.code.Types;
45 import com.sun.tools.javac.comp.AttrContext;
46 import com.sun.tools.javac.comp.CaptureScanner;
47 import com.sun.tools.javac.comp.DeferredAttr.FilterScanner;
48 import com.sun.tools.javac.comp.Env;
49 import com.sun.tools.javac.comp.Flow;
50 import com.sun.tools.javac.comp.Lower;
51 import com.sun.tools.javac.comp.CodeReflectionTransformer;
52 import com.sun.tools.javac.comp.TypeEnvs;
53 import com.sun.tools.javac.file.PathFileObject;
54 import com.sun.tools.javac.jvm.ByteCodes;
55 import com.sun.tools.javac.jvm.Gen;
56 import com.sun.tools.javac.resources.CompilerProperties.*;
57 import com.sun.tools.javac.tree.JCTree;
58 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
59 import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
60 import com.sun.tools.javac.tree.JCTree.JCAssign;
61 import com.sun.tools.javac.tree.JCTree.JCBinary;
62 import com.sun.tools.javac.tree.JCTree.JCBlock;
63 import com.sun.tools.javac.tree.JCTree.JCCaseLabel;
64 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
65 import com.sun.tools.javac.tree.JCTree.JCConstantCaseLabel;
66 import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel;
67 import com.sun.tools.javac.tree.JCTree.JCExpression;
68 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
69 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
70 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression.CodeReflectionInfo;
71 import com.sun.tools.javac.tree.JCTree.JCIdent;
72 import com.sun.tools.javac.tree.JCTree.JCLambda;
73 import com.sun.tools.javac.tree.JCTree.JCLiteral;
74 import com.sun.tools.javac.tree.JCTree.JCMemberReference;
75 import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
76 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
77 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
78 import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
79 import com.sun.tools.javac.tree.JCTree.JCNewArray;
80 import com.sun.tools.javac.tree.JCTree.JCNewClass;
81 import com.sun.tools.javac.tree.JCTree.JCReturn;
82 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
83 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
84 import com.sun.tools.javac.tree.JCTree.JCAssert;
85 import com.sun.tools.javac.tree.JCTree.Tag;
86 import com.sun.tools.javac.tree.TreeInfo;
87 import com.sun.tools.javac.tree.TreeMaker;
88 import com.sun.tools.javac.util.Assert;
89 import com.sun.tools.javac.util.Context;
90 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
91 import com.sun.tools.javac.util.ListBuffer;
92 import com.sun.tools.javac.util.Log;
93 import com.sun.tools.javac.util.Name;
94 import com.sun.tools.javac.util.Names;
95 import com.sun.tools.javac.util.Options;
96 import jdk.incubator.code.*;
97 import jdk.incubator.code.extern.DialectFactory;
98 import jdk.incubator.code.dialect.core.*;
99 import jdk.incubator.code.dialect.java.*;
100 import jdk.incubator.code.dialect.java.WildcardType.BoundKind;
101
102 import javax.lang.model.element.Modifier;
103 import javax.tools.JavaFileObject;
104 import java.lang.constant.ClassDesc;
105 import java.util.*;
106 import java.util.List;
107 import java.util.function.Function;
108 import java.util.function.Supplier;
109
110 import static com.sun.tools.javac.code.Flags.*;
111 import static com.sun.tools.javac.code.Kinds.Kind.MTH;
112 import static com.sun.tools.javac.code.Kinds.Kind.TYP;
113 import static com.sun.tools.javac.code.Kinds.Kind.VAR;
114 import static com.sun.tools.javac.code.TypeTag.BOT;
115 import static com.sun.tools.javac.code.TypeTag.CLASS;
116 import static com.sun.tools.javac.code.TypeTag.INT;
117 import static com.sun.tools.javac.code.TypeTag.METHOD;
118 import static com.sun.tools.javac.code.TypeTag.NONE;
119 import static com.sun.tools.javac.main.Option.G_CUSTOM;
120
121 import java.io.IOException;
122 import java.io.OutputStream;
123 import java.lang.classfile.ClassFile;
124 import java.lang.classfile.ClassTransform;
125 import java.lang.classfile.attribute.InnerClassInfo;
126 import java.lang.classfile.attribute.InnerClassesAttribute;
127 import java.lang.classfile.attribute.NestHostAttribute;
128 import java.lang.invoke.MethodHandles;
129 import javax.tools.JavaFileManager;
130 import javax.tools.StandardLocation;
131 import jdk.incubator.code.bytecode.BytecodeGenerator;
132
133 /**
134 * This a tree translator that adds the code model to all method declaration marked
135 * with the {@code Reflect} annotation. The model is expressed using the code
136 * reflection API (see {@code jdk.incubator.code}).
137 */
138 public class ReflectMethods extends TreeTranslatorPrev {
139 protected static final Context.Key<ReflectMethods> reflectMethodsKey = new Context.Key<>();
140
141 public static ReflectMethods instance(Context context) {
142 ReflectMethods instance = context.get(reflectMethodsKey);
143 if (instance == null)
144 instance = new ReflectMethods(context);
145 return instance;
146 }
147
148 private final Types types;
149 private final Names names;
150 private final Symtab syms;
151 private final Gen gen;
152 private final Log log;
153 private final Lower lower;
154 private final TypeEnvs typeEnvs;
155 private final Flow flow;
156 private final CodeReflectionSymbols crSyms;
157 private final boolean dumpIR;
158 private final boolean lineDebugInfo;
159 private final boolean reflectAll;
160
161 private TreeMaker make;
162 private ListBuffer<JCTree> opMethodDecls;
163 private SequencedMap<String, Op> ops;
164 private Symbol.ClassSymbol currentClassSym;
165 private Symbol.ClassSymbol codeModelsClassSym;
166 private int lambdaCount;
167 private boolean codeReflectionEnabled = false;
168 private final Map<Symbol, List<Symbol>> localCaptures = new HashMap<>();
169
170 @SuppressWarnings("this-escape")
171 protected ReflectMethods(Context context) {
172 context.put(reflectMethodsKey, this);
173 Options options = Options.instance(context);
174 dumpIR = options.isSet("dumpIR");
175 lineDebugInfo =
176 options.isUnset(G_CUSTOM) ||
177 options.isSet(G_CUSTOM, "lines");
178 reflectAll = options.isSet("reflectAll");
179 names = Names.instance(context);
180 syms = Symtab.instance(context);
181 types = Types.instance(context);
182 gen = Gen.instance(context);
183 log = Log.instance(context);
184 lower = Lower.instance(context);
185 typeEnvs = TypeEnvs.instance(context);
186 flow = Flow.instance(context);
187 crSyms = new CodeReflectionSymbols(context);
188 }
189
190 @Override
191 public void visitVarDef(JCVariableDecl tree) {
192 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
193 try {
194 codeReflectionEnabled = codeReflectionEnabled ||
195 tree.sym.attribute(crSyms.codeReflectionType.tsym) != null;
196 super.visitVarDef(tree);
197 } finally {
198 codeReflectionEnabled = prevCodeReflectionEnabled;
199 }
200 }
201
202 boolean isInsideInnerOrLocalClass() {
203 return currentClassSym.type.getEnclosingType().hasTag(CLASS) ||
204 currentClassSym.isDirectlyOrIndirectlyLocal();
205 }
206
207 @Override
208 public void visitMethodDef(JCMethodDecl tree) {
209 boolean isReflectable = !tree.sym.isConstructor() && isReflectable(tree);
210 if (isReflectable) {
211 if (isInsideInnerOrLocalClass()) {
212 // Reflectable methods in local classes are not supported
213 log.warning(tree, Warnings.ReflectableMethodInnerClass(currentClassSym.enclClass()));
214 super.visitMethodDef(tree);
215 return;
216 } else {
217 // if the method is annotated, scan it
218 BodyScanner bodyScanner = new BodyScanner(tree);
219 CoreOp.FuncOp funcOp = bodyScanner.scanMethod();
220 if (dumpIR) {
221 // dump the method IR if requested
222 log.note(Notes.ReflectableMethodIrDump(tree.sym.enclClass(), tree.sym, funcOp.toText()));
223 }
224 // create a static method that returns the op
225 Name methodName = methodName(symbolToErasedMethodRef(tree.sym));
226 opMethodDecls.add(opMethodDecl(methodName));
227 ops.put(methodName.toString(), funcOp);
228 }
229 }
230 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
231 try {
232 codeReflectionEnabled = isReflectable;
233 super.visitMethodDef(tree);
234 } finally {
235 codeReflectionEnabled = prevCodeReflectionEnabled;
236 }
237 }
238
239 @Override
240 public void visitModuleDef(JCModuleDecl that) {
241 // do nothing
242 }
243
244 @Override
245 public void visitClassDef(JCClassDecl tree) {
246 ListBuffer<JCTree> prevOpMethodDecls = opMethodDecls;
247 SequencedMap<String, Op> prevOps = ops;
248 Symbol.ClassSymbol prevClassSym = currentClassSym;
249 Symbol.ClassSymbol prevCodeModelsClassSym = codeModelsClassSym;
250 int prevLambdaCount = lambdaCount;
251 JavaFileObject prev = log.useSource(tree.sym.sourcefile);
252 computeCapturesIfNeeded(tree);
253 try {
254 lambdaCount = 0;
255 currentClassSym = tree.sym;
256 opMethodDecls = new ListBuffer<>();
257 // The flags are usually filled by Check::checkFlags, which we don't run
258 codeModelsClassSym = new ClassSymbol(IDENTITY_TYPE | STATIC, names.fromString("$CM"), currentClassSym);
259 ops = new LinkedHashMap<>();
260 super.visitClassDef(tree);
261 if (!ops.isEmpty()) {
262 tree.defs = tree.defs.prependList(opMethodDecls.toList());
263 tree = new JCReflectMethodsClassDecl(tree, ops);
264 // store the tree for later phases
265 Env<AttrContext> classEnv = typeEnvs.get(tree.sym);
266 classEnv.tree = tree;
267 classEnv.enclClass = tree;
268 currentClassSym.members().enter(codeModelsClassSym);
269 }
270 } finally {
271 lambdaCount = prevLambdaCount;
272 opMethodDecls = prevOpMethodDecls;
273 ops = prevOps;
274 currentClassSym = prevClassSym;
275 codeModelsClassSym = prevCodeModelsClassSym;
276 result = tree;
277 log.useSource(prev);
278 }
279 }
280
281 void computeCapturesIfNeeded(JCClassDecl tree) {
282 if (tree.sym.isDirectlyOrIndirectlyLocal() && !localCaptures.containsKey(tree.sym)) {
283 // we need to keep track of captured locals using same strategy as Lower
284 class FreeVarScanner extends Lower.FreeVarCollector {
285 FreeVarScanner() {
286 lower.super(tree);
287 }
288
289 @Override
290 protected void addFreeVars(ClassSymbol c) {
291 localCaptures.getOrDefault(c, List.of())
292 .forEach(s -> addFreeVar((VarSymbol)s));
293 }
294 }
295 FreeVarScanner fvs = new FreeVarScanner();
296 localCaptures.put(tree.sym, List.copyOf(fvs.analyzeCaptures()));
297 }
298 }
299
300 @Override
301 public void visitLambda(JCLambda tree) {
302 boolean isReflectable = isReflectable(tree);
303 if (isReflectable) {
304 if (isInsideInnerOrLocalClass()) {
305 // Reflectable lambdas in local classes are not supported
306 log.warning(tree, Warnings.ReflectableLambdaInnerClass(currentClassSym.enclClass()));
307 super.visitLambda(tree);
308 return;
309 }
310
311 // quoted lambda - scan it
312 BodyScanner bodyScanner = new BodyScanner(tree);
313 CoreOp.FuncOp funcOp = bodyScanner.scanLambda();
314 if (dumpIR) {
315 // dump the method IR if requested
316 log.note(Notes.ReflectableLambdaIrDump(funcOp.toText()));
317 }
318 // create a static method that returns the FuncOp representing the lambda
319 Name lambdaName = lambdaName();
320 JCMethodDecl opMethod = opMethodDecl(lambdaName);
321 opMethodDecls.add(opMethod);
322 ops.put(lambdaName.toString(), funcOp);
323
324 // leave the lambda in place, but also leave a trail for LambdaToMethod
325 tree.codeReflectionInfo = new CodeReflectionInfo(opMethod.sym, crSyms.reflectableLambdaMetafactory);
326 }
327 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
328 try {
329 codeReflectionEnabled = isReflectable;
330 super.visitLambda(tree);
331 } finally {
332 codeReflectionEnabled = prevCodeReflectionEnabled;
333 }
334 }
335
336 @Override
337 public void visitReference(JCMemberReference tree) {
338 MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
339 JCLambda lambdaTree = memberReferenceToLambda.lambda();
340
341 if (isReflectable(tree)) {
342 if (isInsideInnerOrLocalClass()) {
343 // Reflectable method references in local classes are not supported
344 log.warning(tree, Warnings.ReflectableMrefInnerClass(currentClassSym.enclClass()));
345 super.visitReference(tree);
346 return;
347 }
348
349 // quoted lambda - scan it
350 BodyScanner bodyScanner = new BodyScanner(lambdaTree);
351 CoreOp.FuncOp funcOp = bodyScanner.scanLambda();
352 if (dumpIR) {
353 // dump the method IR if requested
354 log.note(Notes.ReflectableMrefIrDump(funcOp.toText()));
355 }
356 // create a method that returns the FuncOp representing the lambda
357 Name lambdaName = lambdaName();
358 ops.put(lambdaName.toString(), funcOp);
359 JCMethodDecl opMethod = opMethodDecl(lambdaName);
360 opMethodDecls.add(opMethod);
361 tree.codeReflectionInfo = new CodeReflectionInfo(opMethod.sym, crSyms.reflectableLambdaMetafactory);
362 }
363 super.visitReference(tree);
364 }
365
366 Name lambdaName() {
367 return names.fromString("lambda").append('$', names.fromString(String.valueOf(lambdaCount++)));
368 }
369
370 Name methodName(MethodRef method) {
371 char[] sigCh = method.toString().toCharArray();
372 for (int i = 0; i < sigCh.length; i++) {
373 switch (sigCh[i]) {
374 case '.', ';', '[', '/' -> sigCh[i] = '$';
375 }
376 }
377 return names.fromChars(sigCh, 0, sigCh.length);
378 }
379
380 // @@@ Retain enum for when we might add another storage to test
381 // and compare
382 private enum CodeModelStorageOption {
383 CODE_BUILDER;
384
385 public static CodeModelStorageOption parse(String s) {
386 if (s == null) {
387 return CodeModelStorageOption.CODE_BUILDER;
388 }
389 return CodeModelStorageOption.valueOf(s);
390 }
391 }
392
393 private JCMethodDecl opMethodDecl(Name methodName) {
394 var mt = new MethodType(com.sun.tools.javac.util.List.nil(), crSyms.opType,
395 com.sun.tools.javac.util.List.nil(), syms.methodClass);
396 var ms = new MethodSymbol(PRIVATE | STATIC | SYNTHETIC, methodName, mt, currentClassSym);
397 currentClassSym.members().enter(ms);
398
399 // Create the method body calling the synthetic inner class method of the same name
400 var body = make.Return(make.App(make.Ident(new MethodSymbol(PRIVATE | STATIC | SYNTHETIC, methodName, mt, codeModelsClassSym))));
401 var md = make.MethodDef(ms, make.Block(0, com.sun.tools.javac.util.List.of(body)));
402 return md;
403 }
404
405 public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
406 // note that this method does NOT support recursion.
407 this.make = make;
408 return translate(cdef);
409 }
410
411 public CoreOp.FuncOp getMethodBody(Symbol.ClassSymbol classSym, JCMethodDecl methodDecl, JCBlock attributedBody, TreeMaker make) {
412 // if the method is annotated, scan it
413 // Called from JavacElements::getBody
414 try {
415 this.make = make;
416 currentClassSym = classSym;
417 // same checks as in ReflectMethods::visitMethodDef
418 boolean isReflectable = !methodDecl.sym.isConstructor() && isReflectable(methodDecl);
419 if (isReflectable && !isInsideInnerOrLocalClass()) {
420 BodyScanner bodyScanner = new BodyScanner(methodDecl);
421 return bodyScanner.scanMethod(attributedBody);
422 } else {
423 return null;
424 }
425 } finally {
426 currentClassSym = null;
427 this.make = null;
428 }
429 }
430
431 static class BodyStack {
432 final BodyStack parent;
433
434 // Tree associated with body
435 final JCTree tree;
436
437 // Body to add blocks
438 final Body.Builder body;
439 // Current block to add operations
440 Block.Builder block;
441
442 // Map of symbols (method arguments and local variables) to varOp values
443 final Map<Symbol, Value> localToOp;
444
445 // Label
446 Map.Entry<String, Op.Result> label;
447
448 BodyStack(BodyStack parent, JCTree tree, FunctionType bodySignature) {
449 this.parent = parent;
450
451 this.tree = tree;
452
453 this.body = Body.Builder.of(parent != null ? parent.body : null, bodySignature);
454 this.block = body.entryBlock();
455
456 this.localToOp = new LinkedHashMap<>(); // order is important for captured values
457 }
458
459 public void setLabel(String labelName, Op.Result labelValue) {
460 if (label != null) {
461 throw new IllegalStateException("Label already defined: " + labelName);
462 }
463 label = Map.entry(labelName, labelValue);
464 }
465 }
466
467 class BodyScanner extends TreeScannerPrev {
468 private final JCTree tree;
469 private final Name name;
470 private final BodyStack top;
471 private BodyStack stack;
472 private Op lastOp;
473 private Value result;
474 private Type pt = Type.noType;
475 private final boolean isLambdaReflectable;
476 private Type bodyTarget;
477
478 BodyScanner(JCMethodDecl tree) {
479 this.tree = tree;
480 this.name = tree.name;
481 this.isLambdaReflectable = false;
482
483 List<CodeType> parameters = new ArrayList<>();
484 int blockArgOffset = 0;
485 // Instance methods model "this" as an additional argument occurring
486 // before all other arguments.
487 // @@@ Inner classes.
488 // We need to capture all "this", in nested order, as arguments.
489 if (!tree.getModifiers().getFlags().contains(Modifier.STATIC)) {
490 parameters.add(typeToCodeType(tree.sym.owner.type));
491 blockArgOffset++;
492 }
493 tree.sym.type.getParameterTypes().stream().map(ReflectMethods.this::typeToCodeType).forEach(parameters::add);
494
495 FunctionType bodySignature = CoreType.functionType(
496 typeToCodeType(tree.sym.type.getReturnType()), parameters);
497
498 this.stack = this.top = new BodyStack(null, tree.body, bodySignature);
499
500 // @@@ this as local variable? (it can never be stored to)
501 for (int i = 0 ; i < tree.params.size() ; i++) {
502 Op.Result paramOp = append(CoreOp.var(
503 tree.params.get(i).name.toString(),
504 top.block.parameters().get(blockArgOffset + i)));
505 top.localToOp.put(tree.params.get(i).sym, paramOp);
506 }
507
508 bodyTarget = tree.sym.type.getReturnType();
509 }
510
511 BodyScanner(JCLambda tree) {
512 this.tree = tree;
513 this.name = names.fromString("quotedLambda");
514 this.isLambdaReflectable = true;
515
516 ReflectableLambdaCaptureScanner lambdaCaptureScanner =
517 new ReflectableLambdaCaptureScanner(tree);
518
519 List<VarSymbol> capturedSymbols = lambdaCaptureScanner.analyzeCaptures();
520 int blockParamOffset = 0;
521
522 ListBuffer<Type> capturedTypes = new ListBuffer<>();
523 if (lambdaCaptureScanner.capturesThis) {
524 capturedTypes.add(currentClassSym.type);
525 blockParamOffset++;
526 }
527 for (Symbol s : capturedSymbols) {
528 capturedTypes.add(s.type);
529 }
530
531 FunctionType mtDesc = CoreType.functionType(CoreOp.QuotedOp.QUOTED_OP_TYPE,
532 capturedTypes.toList().map(ReflectMethods.this::typeToCodeType));
533
534 this.stack = this.top = new BodyStack(null, tree.body, mtDesc);
535
536 // add captured variables mappings
537 for (int i = 0 ; i < capturedSymbols.size() ; i++) {
538 Symbol capturedSymbol = capturedSymbols.get(i);
539 var capturedArg = top.block.parameters().get(blockParamOffset + i);
540 top.localToOp.put(capturedSymbol,
541 append(CoreOp.var(capturedSymbol.name.toString(), capturedArg)));
542 }
543
544 // add captured constant mappings
545 for (Map.Entry<Symbol, Object> constantCapture : lambdaCaptureScanner.constantCaptures.entrySet()) {
546 Symbol capturedSymbol = constantCapture.getKey();
547 var capturedArg = append(CoreOp.constant(typeToCodeType(capturedSymbol.type),
548 constantCapture.getValue()));
549 top.localToOp.put(capturedSymbol,
550 append(CoreOp.var(capturedSymbol.name.toString(), capturedArg)));
551 }
552
553 bodyTarget = tree.target.getReturnType();
554 }
555
556 /**
557 * Compute the set of local variables captured by a reflectable lambda expression.
558 * Inspired from LambdaToMethod's LambdaCaptureScanner.
559 */
560 class ReflectableLambdaCaptureScanner extends CaptureScanner {
561 boolean capturesThis;
562 Set<ClassSymbol> seenClasses = new HashSet<>();
563 Map<Symbol, Object> constantCaptures = new HashMap<>();
564
565 ReflectableLambdaCaptureScanner(JCLambda ownerTree) {
566 super(ownerTree);
567 }
568
569 @Override
570 public void visitClassDef(JCClassDecl tree) {
571 computeCapturesIfNeeded(tree);
572 seenClasses.add(tree.sym);
573 super.visitClassDef(tree);
574 }
575
576 @Override
577 public void visitIdent(JCIdent tree) {
578 if (!tree.sym.isStatic() &&
579 tree.sym.owner.kind == TYP &&
580 (tree.sym.kind == VAR || tree.sym.kind == MTH) &&
581 !seenClasses.contains(tree.sym.owner)) {
582 // a reference to an enclosing field or method, we need to capture 'this'
583 capturesThis = true;
584 } else if (tree.sym instanceof VarSymbol vsym &&
585 vsym.getConstValue() != null &&
586 !isVarSeen(vsym)) {
587 // record the constant value associated with this
588 constantCaptures.put(tree.sym, vsym.getConstValue());
589 } else {
590 // might be a local capture
591 super.visitIdent(tree);
592 }
593 }
594
595 @Override
596 public void visitSelect(JCFieldAccess tree) {
597 if (tree.sym.kind == VAR &&
598 (tree.sym.name == names._this ||
599 tree.sym.name == names._super) &&
600 !seenClasses.contains(tree.sym.type.tsym)) {
601 capturesThis = true;
602 }
603 super.visitSelect(tree);
604 }
605
606 @Override
607 public void visitNewClass(JCNewClass tree) {
608 super.visitNewClass(tree); // this might scan an anon class def, so we need to do that first
609 if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
610 for (Symbol c : localCaptures.get(tree.type.tsym)) {
611 addFreeVar((VarSymbol) c);
612 }
613 }
614 if (tree.encl == null && tree.type.tsym.hasOuterInstance()) {
615 capturesThis = true;
616 }
617 }
618
619 @Override
620 public void visitAnnotation(JCAnnotation tree) {
621 // do nothing (annotation values look like captured instance fields)
622 }
623 }
624
625 void pushBody(JCTree tree, FunctionType bodySignature) {
626 stack = new BodyStack(stack, tree, bodySignature);
627 lastOp = null; // reset
628 }
629
630 void popBody() {
631 stack = stack.parent;
632 }
633
634 Value varOpValue(Symbol sym) {
635 BodyStack s = stack;
636 while (s != null) {
637 Value v = s.localToOp.get(sym);
638 if (v != null) {
639 return v;
640 }
641 s = s.parent;
642 }
643 throw new NoSuchElementException(sym.toString());
644 }
645
646 Value thisValue() { // @@@: outer this?
647 return top.block.parameters().get(0);
648 }
649
650 Value getLabel(String labelName) {
651 BodyStack s = stack;
652 while (s != null) {
653 if (s.label != null && s.label.getKey().equals(labelName)) {
654 return s.label.getValue();
655 }
656 s = s.parent;
657 }
658 throw new NoSuchElementException(labelName);
659 }
660
661 private DiagnosticPosition pos() {
662 JCTree current = currentNode();
663 return current != null ? current : tree;
664 }
665
666 private Op.Result append(Op op) {
667 return append(op, generateLocation(pos(), false), stack);
668 }
669
670 private Op.Result append(Op op, Op.Location l) {
671 return append(op, l, stack);
672 }
673
674 private Op.Result append(Op op, Op.Location l, BodyStack stack) {
675 lastOp = op;
676 op.setLocation(l);
677 return stack.block.add(op);
678 }
679
680 Op.Location generateLocation(DiagnosticPosition pos, boolean includeSourceReference) {
681 if (!lineDebugInfo) {
682 return Op.Location.NO_LOCATION;
683 }
684
685 int startPos = pos.getStartPosition();
686 int line = log.currentSource().getLineNumber(startPos);
687 int col = log.currentSource().getColumnNumber(startPos, false);
688 String path;
689 if (includeSourceReference) {
690 path = PathFileObject.getSimpleName(log.currentSourceFile());
691 } else {
692 path = null;
693 }
694 return new Op.Location(path, line, col);
695 }
696
697 private void appendReturnOrUnreachable(JCTree body) {
698 // Append only if an existing terminating operation is not present
699 if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
700 // If control can continue after the body append return.
701 // Otherwise, append unreachable.
702 if (isAliveAfter(body)) {
703 append(CoreOp.return_());
704 } else {
705 append(CoreOp.unreachable());
706 }
707 }
708 }
709
710 private boolean isAliveAfter(JCTree node) {
711 return flow.aliveAfter(typeEnvs.get(currentClassSym), node, make);
712 }
713
714 private void appendTerminating(Supplier<Op.Terminating> sop) {
715 // Append only if an existing terminating operation is not present
716 if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
717 append(sop.get());
718 }
719 }
720
721 public Value toValue(JCExpression expression, Type targetType) {
722 result = null; // reset
723 Type prevPt = pt;
724 try {
725 pt = targetType;
726 scan(expression);
727 return (result == null || targetType.hasTag(TypeTag.VOID) || targetType.hasTag(NONE)) ?
728 result : coerce(result, expression.type, targetType);
729 } finally {
730 pt = prevPt;
731 }
732 }
733
734 public Value toValue(JCExpression expression) {
735 return toValue(expression, Type.noType);
736 }
737
738 public Value toValue(JCTree.JCStatement statement) {
739 result = null; // reset
740 scan(statement);
741 return result;
742 }
743
744 Value coerce(Value sourceValue, Type sourceType, Type targetType) {
745 if (sourceType.isReference() && targetType.isReference() &&
746 !types.isSubtype(types.erasure(sourceType), types.erasure(targetType))) {
747 return append(JavaOp.cast(typeToCodeType(targetType), sourceValue));
748 }
749 return convert(sourceValue, targetType);
750 }
751
752 Value boxIfNeeded(Value exprVal) {
753 Type source = codeTypeToType(exprVal.type());
754 return source.hasTag(NONE) ?
755 exprVal : convert(exprVal, types.boxedTypeOrType(source));
756 }
757
758 Value unboxIfNeeded(Value exprVal) {
759 Type source = codeTypeToType(exprVal.type());
760 return source.hasTag(NONE) ?
761 exprVal : convert(exprVal, types.unboxedTypeOrType(source));
762 }
763
764 Value convert(Value exprVal, Type target) {
765 Type source = codeTypeToType(exprVal.type());
766 boolean sourcePrimitive = source.isPrimitive();
767 boolean targetPrimitive = target.isPrimitive();
768 if (target.hasTag(NONE)) {
769 return exprVal;
770 } else if (sourcePrimitive == targetPrimitive) {
771 if (!sourcePrimitive || types.isSameType(source, target)) {
772 return exprVal;
773 } else {
774 // implicit primitive conversion
775 return append(JavaOp.conv(typeToCodeType(target), exprVal));
776 }
777 } else if (sourcePrimitive) {
778 // we need to box
779 Type unboxedTarget = types.unboxedType(target);
780 if (!unboxedTarget.hasTag(NONE)) {
781 // non-Object target
782 if (!types.isConvertible(source, unboxedTarget)) {
783 exprVal = convert(exprVal, unboxedTarget);
784 }
785 return box(exprVal, target);
786 } else {
787 // Object target
788 return box(exprVal, types.boxedClass(source).type);
789 }
790 } else {
791 // we need to unbox
792 return unbox(exprVal, source, target, types.unboxedType(source));
793 }
794 }
795
796 Value box(Value valueExpr, Type box) {
797 // Boxing is a static method e.g., java.lang.Integer::valueOf(int)java.lang.Integer
798 MethodRef boxMethod = MethodRef.method(typeToCodeType(box), names.valueOf.toString(),
799 CoreType.functionType(typeToCodeType(box), typeToCodeType(types.unboxedType(box))));
800 return append(JavaOp.invoke(boxMethod, valueExpr));
801 }
802
803 Value unbox(Value valueExpr, Type box, Type primitive, Type unboxedType) {
804 if (unboxedType.hasTag(NONE)) {
805 // Object target, first downcast to correct wrapper type
806 unboxedType = primitive;
807 box = types.boxedClass(unboxedType).type;
808 valueExpr = append(JavaOp.cast(typeToCodeType(box), valueExpr));
809 }
810 // Unboxing is a virtual method e.g., java.lang.Integer::intValue()int
811 MethodRef unboxMethod = MethodRef.method(typeToCodeType(box),
812 unboxedType.tsym.name.append(names.Value).toString(),
813 CoreType.functionType(typeToCodeType(unboxedType)));
814 return append(JavaOp.invoke(unboxMethod, valueExpr));
815 }
816
817 @Override
818 public void visitVarDef(JCVariableDecl tree) {
819 JavaType javaType = typeToCodeType(tree.type);
820 if (tree.init != null) {
821 Value initOp = toValue(tree.init, tree.type);
822 result = append(CoreOp.var(tree.name.toString(), javaType, initOp));
823 } else {
824 // Uninitialized
825 result = append(CoreOp.var(tree.name.toString(), javaType));
826 }
827 stack.localToOp.put(tree.sym, result);
828 }
829
830 @Override
831 public void visitAssign(JCAssign tree) {
832 // Consume top node that applies to write access
833 JCTree lhs = TreeInfo.skipParens(tree.lhs);
834 Type target = tree.lhs.type;
835 switch (lhs.getTag()) {
836 case IDENT: {
837 JCIdent assign = (JCIdent) lhs;
838
839 // Scan the rhs, the assign expression result is its input
840 result = toValue(tree.rhs, target);
841
842 Symbol sym = assign.sym;
843 switch (sym.getKind()) {
844 case LOCAL_VARIABLE, BINDING_VARIABLE, PARAMETER, EXCEPTION_PARAMETER -> {
845 Value varOp = varOpValue(sym);
846 append(CoreOp.varStore(varOp, result));
847 }
848 case FIELD -> {
849 FieldRef fd = symbolToErasedFieldRef(sym, symbolSiteType(sym));
850 if (sym.isStatic()) {
851 append(JavaOp.fieldStore(fd, result));
852 } else {
853 append(JavaOp.fieldStore(fd, thisValue(), result));
854 }
855 }
856 default -> throw unreachable();
857 }
858 break;
859 }
860 case SELECT: {
861 JCFieldAccess assign = (JCFieldAccess) lhs;
862
863 Value receiver = toValue(assign.selected);
864
865 // Scan the rhs, the assign expression result is its input
866 result = toValue(tree.rhs, target);
867
868 Symbol sym = assign.sym;
869 FieldRef fr = symbolToErasedFieldRef(sym, assign.selected.type);
870 if (sym.isStatic()) {
871 append(JavaOp.fieldStore(fr, result));
872 } else {
873 append(JavaOp.fieldStore(fr, receiver, result));
874 }
875 break;
876 }
877 case INDEXED: {
878 JCArrayAccess assign = (JCArrayAccess) lhs;
879
880 Value array = toValue(assign.indexed);
881 Value index = toValue(assign.index, syms.intType);
882
883 // Scan the rhs, the assign expression result is its input
884 result = toValue(tree.rhs, target);
885
886 append(JavaOp.arrayStoreOp(array, index, result));
887 break;
888 }
889 default:
890 throw unreachable();
891 }
892 }
893
894 @Override
895 public void visitAssignop(JCTree.JCAssignOp tree) {
896 if (tree.operator.opcode == ByteCodes.string_add) {
897 // string concat
898 applyCompoundAssign(tree.lhs, lhs -> {
899 Type rhsType = tree.rhs.type;
900 Value rhs = toValue(tree.rhs,
901 rhsType.hasTag(BOT) ? syms.stringType : rhsType);
902 // lhs cannot have null type, no target type needed
903 Value assignOpResult = append(JavaOp.concat(lhs, rhs));
904 return result = convert(assignOpResult, tree.type);
905 });
906 } else {
907 // arithmetic op
908 applyCompoundAssign(tree.lhs, lhs -> {
909 Type lhsType = tree.operator.type.getParameterTypes().head;
910 Type rhsType = tree.operator.type.getParameterTypes().tail.head;
911
912 // We need to first convert LHS, then process RHS
913 // as described in JLS 15.26.2
914 lhs = convert(lhs, lhsType);
915 Value rhs = toValue(tree.rhs, rhsType);
916
917 Value assignOpResult = switch (tree.getTag()) {
918
919 // Arithmetic operations
920 case PLUS_ASG -> append(JavaOp.add(lhs, rhs));
921 case MINUS_ASG -> append(JavaOp.sub(lhs, rhs));
922 case MUL_ASG -> append(JavaOp.mul(lhs, rhs));
923 case DIV_ASG -> append(JavaOp.div(lhs, rhs));
924 case MOD_ASG -> append(JavaOp.mod(lhs, rhs));
925
926 // Bitwise operations (including their boolean variants)
927 case BITOR_ASG -> append(JavaOp.or(lhs, rhs));
928 case BITAND_ASG -> append(JavaOp.and(lhs, rhs));
929 case BITXOR_ASG -> append(JavaOp.xor(lhs, rhs));
930
931 // Shift operations
932 case SL_ASG -> append(JavaOp.lshl(lhs, rhs));
933 case SR_ASG -> append(JavaOp.ashr(lhs, rhs));
934 case USR_ASG -> append(JavaOp.lshr(lhs, rhs));
935
936
937 default -> throw unreachable();
938 };
939 return result = convert(assignOpResult, tree.type);
940 });
941 }
942 }
943
944 void applyCompoundAssign(JCTree.JCExpression lhs, Function<Value, Value> scanRhs) {
945 // Consume top node that applies to access
946 lhs = TreeInfo.skipParens(lhs);
947 switch (lhs.getTag()) {
948 case IDENT -> {
949 JCIdent assign = (JCIdent) lhs;
950
951 Symbol sym = assign.sym;
952 switch (sym.getKind()) {
953 case LOCAL_VARIABLE, BINDING_VARIABLE, PARAMETER -> { // exception parameters not valid here!
954 Value varOp = varOpValue(sym);
955
956 Op.Result lhsOpValue = append(CoreOp.varLoad(varOp));
957 // Scan the rhs
958 Value r = scanRhs.apply(lhsOpValue);
959
960 append(CoreOp.varStore(varOp, r));
961 }
962 case FIELD -> {
963 FieldRef fr = symbolToErasedFieldRef(sym, symbolSiteType(sym));
964
965 Op.Result lhsOpValue;
966 CodeType resultType = typeToCodeType(sym.type);
967 if (sym.isStatic()) {
968 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
969 } else {
970 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
971 }
972 // Scan the rhs
973 Value r = scanRhs.apply(lhsOpValue);
974
975 if (sym.isStatic()) {
976 append(JavaOp.fieldStore(fr, r));
977 } else {
978 append(JavaOp.fieldStore(fr, thisValue(), r));
979 }
980 }
981 default -> throw unreachable();
982 }
983 }
984 case SELECT -> {
985 JCFieldAccess assign = (JCFieldAccess) lhs;
986
987 Value receiver = toValue(assign.selected);
988
989 Symbol sym = assign.sym;
990 FieldRef fr = symbolToErasedFieldRef(sym, assign.selected.type);
991
992 Op.Result lhsOpValue;
993 CodeType resultType = typeToCodeType(sym.type);
994 if (sym.isStatic()) {
995 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
996 } else {
997 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, receiver));
998 }
999 // Scan the rhs
1000 Value r = scanRhs.apply(lhsOpValue);
1001
1002 if (sym.isStatic()) {
1003 append(JavaOp.fieldStore(fr, r));
1004 } else {
1005 append(JavaOp.fieldStore(fr, receiver, r));
1006 }
1007 }
1008 case INDEXED -> {
1009 JCArrayAccess assign = (JCArrayAccess) lhs;
1010
1011 Value array = toValue(assign.indexed);
1012 Value index = toValue(assign.index, syms.intType);
1013
1014 Op.Result lhsOpValue = append(JavaOp.arrayLoadOp(array, index));
1015 // Scan the rhs
1016 Value r = scanRhs.apply(lhsOpValue);
1017
1018 append(JavaOp.arrayStoreOp(array, index, r));
1019 }
1020 default -> throw unreachable();
1021 }
1022 }
1023
1024 @Override
1025 public void visitIdent(JCIdent tree) {
1026 // Visited only for read access
1027
1028 Symbol sym = tree.sym;
1029 switch (sym.getKind()) {
1030 case LOCAL_VARIABLE, RESOURCE_VARIABLE, BINDING_VARIABLE, PARAMETER, EXCEPTION_PARAMETER ->
1031 result = loadVar(sym);
1032 case FIELD, ENUM_CONSTANT -> {
1033 if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1034 result = thisValue();
1035 } else if (top.localToOp.containsKey(sym)) {
1036 // if field symbol is a key in top.localToOp
1037 // we expect that we're producing the model of a lambda
1038 // we also expect that the field is a constant capture and sym was mapped to VarOp result
1039 Assert.check(isLambdaReflectable);
1040 Assert.check(sym.isStatic());
1041 Assert.check(sym.isFinal());
1042 result = loadVar(sym);
1043 } else {
1044 FieldRef fr = symbolToErasedFieldRef(sym, symbolSiteType(sym));
1045 CodeType resultType = typeToCodeType(sym.type);
1046 if (sym.isStatic()) {
1047 result = append(JavaOp.fieldLoad(resultType, fr));
1048 } else {
1049 result = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
1050 }
1051 }
1052 }
1053 case PACKAGE, INTERFACE, CLASS, ANNOTATION_TYPE, RECORD, ENUM -> {
1054 result = null;
1055 }
1056 default -> throw unreachable();
1057 }
1058 }
1059
1060 private Value loadVar(Symbol sym) {
1061 Value varOp = varOpValue(sym);
1062 Assert.check(varOp.type() instanceof VarType);
1063 return append(CoreOp.varLoad(varOp));
1064 }
1065
1066 @Override
1067 public void visitTypeIdent(JCTree.JCPrimitiveTypeTree tree) {
1068 result = null;
1069 }
1070
1071 @Override
1072 public void visitTypeArray(JCTree.JCArrayTypeTree tree) {
1073 result = null; // MyType[].class is handled in visitSelect just as MyType.class
1074 }
1075
1076 @Override
1077 public void visitSelect(JCFieldAccess tree) {
1078 // Visited only for read access
1079
1080 Type qualifierTarget = qualifierTarget(tree);
1081 // @@@: might cause redundant load if accessed symbol is static but the qualifier is not a type
1082 Value receiver = toValue(tree.selected);
1083
1084 if (tree.name.equals(names._class)) {
1085 result = append(CoreOp.constant(JavaType.J_L_CLASS, typeToCodeType(tree.selected.type)));
1086 } else if (types.isArray(tree.selected.type)) {
1087 if (tree.sym.equals(syms.lengthVar)) {
1088 result = append(JavaOp.arrayLength(receiver));
1089 } else {
1090 throw unreachable();
1091 }
1092 } else {
1093 Symbol sym = tree.sym;
1094 switch (sym.getKind()) {
1095 case FIELD, ENUM_CONSTANT -> {
1096 if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1097 result = thisValue();
1098 } else {
1099 FieldRef fr = symbolToErasedFieldRef(sym, qualifierTarget.hasTag(NONE) ?
1100 tree.selected.type : qualifierTarget);
1101 CodeType resultType = typeToCodeType(tree.type);
1102 if (sym.isStatic()) {
1103 result = append(JavaOp.fieldLoad(resultType, fr));
1104 } else {
1105 result = append(JavaOp.fieldLoad(resultType, fr, receiver));
1106 }
1107 }
1108 }
1109 case PACKAGE, INTERFACE, CLASS, ANNOTATION_TYPE, RECORD, ENUM -> {
1110 result = null;
1111 }
1112 default -> throw unreachable();
1113 }
1114 }
1115 }
1116
1117 @Override
1118 public void visitIndexed(JCArrayAccess tree) {
1119 // Visited only for read access
1120
1121 Value array = toValue(tree.indexed);
1122
1123 Value index = toValue(tree.index, syms.intType);
1124
1125 result = append(JavaOp.arrayLoadOp(array, index));
1126 }
1127
1128 @Override
1129 public void visitApply(JCTree.JCMethodInvocation tree) {
1130 // @@@ Symbol.externalType, for use with inner classes
1131
1132 // @@@ this.xyz(...) calls in a constructor
1133
1134 JCTree meth = TreeInfo.skipParens(tree.meth);
1135 switch (meth.getTag()) {
1136 case IDENT: {
1137 JCIdent access = (JCIdent) meth;
1138
1139 Symbol sym = access.sym;
1140 List<Value> args = new ArrayList<>();
1141 JavaOp.InvokeOp.InvokeKind ik;
1142 if (!sym.isStatic()) {
1143 ik = JavaOp.InvokeOp.InvokeKind.INSTANCE;
1144 args.add(thisValue());
1145 } else {
1146 ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1147 }
1148
1149 args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1150
1151 MethodRef mr = symbolToErasedMethodRef(sym, symbolSiteType(sym));
1152
1153 JavaType resultType = typeToCodeType(tree.type);
1154 JavaOp.InvokeOp iop = JavaOp.invoke(ik, tree.varargsElement != null,
1155 resultType, mr, args);
1156 Value res = append(iop);
1157 if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1158 result = res;
1159 }
1160 break;
1161 }
1162 case SELECT: {
1163 JCFieldAccess access = (JCFieldAccess) meth;
1164
1165 Type qualifierTarget = qualifierTarget(access);
1166 Value receiver = toValue(access.selected, qualifierTarget);
1167
1168 Symbol sym = access.sym;
1169 List<Value> args = new ArrayList<>();
1170 JavaOp.InvokeOp.InvokeKind ik;
1171 if (!sym.isStatic()) {
1172 args.add(receiver);
1173 // @@@ expr.super(...) for inner class super constructor calls
1174 ik = switch (access.selected) {
1175 case JCIdent i when i.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1176 case JCFieldAccess fa when fa.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1177 default -> JavaOp.InvokeOp.InvokeKind.INSTANCE;
1178 };
1179 } else {
1180 ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1181 }
1182
1183 args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1184
1185 MethodRef mr;
1186 if (sym.owner == syms.arrayClass && sym.name == names.clone) {
1187 // For array.clone use the erased selected type as the reference type,
1188 // which will be an array
1189 mr = MethodRef.method(
1190 typeToCodeType(types.erasure(access.selected.type)),
1191 names.clone.toString(),
1192 JavaType.J_L_OBJECT);
1193 } else {
1194 mr = symbolToErasedMethodRef(sym, qualifierTarget.hasTag(NONE) ?
1195 access.selected.type : qualifierTarget);
1196 }
1197
1198 // Use the actual type of the expression, tree.type, rather than meth.type.getReturnType()
1199 // This ensures invocation expressions to clone on arrays and getClass are modeled
1200 // with the correct result type
1201 JavaType resultType = typeToCodeType(tree.type);
1202 JavaOp.InvokeOp iop = JavaOp.invoke(ik, tree.varargsElement != null,
1203 resultType, mr, args);
1204 Value res = append(iop);
1205 if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1206 result = res;
1207 }
1208 break;
1209 }
1210 default:
1211 throw unreachable();
1212 }
1213 }
1214
1215 List<Value> scanMethodArguments(List<JCExpression> args, Type methodType, Type varargsElement) {
1216 ListBuffer<Value> argValues = new ListBuffer<>();
1217 com.sun.tools.javac.util.List<Type> targetTypes = methodType.getParameterTypes();
1218 if (varargsElement != null) {
1219 targetTypes = targetTypes.reverse().tail;
1220 for (int i = 0 ; i < args.size() - (methodType.getParameterTypes().size() - 1) ; i++) {
1221 targetTypes = targetTypes.prepend(varargsElement);
1222 }
1223 targetTypes = targetTypes.reverse();
1224 }
1225
1226 for (JCTree.JCExpression arg : args) {
1227 argValues.add(toValue(arg, targetTypes.head));
1228 targetTypes = targetTypes.tail;
1229 }
1230 return argValues.toList();
1231 }
1232
1233 @Override
1234 public void visitReference(JCTree.JCMemberReference tree) {
1235 MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
1236 JCVariableDecl recv = memberReferenceToLambda.receiverVar();
1237 if (recv != null) {
1238 scan(recv);
1239 }
1240 scan(memberReferenceToLambda.lambda());
1241 }
1242
1243 Type qualifierTarget(JCFieldAccess tree) {
1244 Type selectedType = types.skipTypeVars(tree.selected.type, true);
1245 return selectedType.isCompound() ?
1246 tree.sym.owner.type :
1247 Type.noType;
1248 }
1249
1250 @Override
1251 public void visitTypeCast(JCTree.JCTypeCast tree) {
1252 Value v = toValue(tree.expr);
1253
1254 Type expressionType = tree.expr.type;
1255 Type type = tree.type;
1256 if (expressionType.isPrimitive() && type.isPrimitive()) {
1257 if (expressionType.equals(type)) {
1258 // Redundant cast
1259 result = v;
1260 } else {
1261 result = append(JavaOp.conv(typeToCodeType(type), v));
1262 }
1263 } else if (expressionType.isPrimitive() || type.isPrimitive()) {
1264 result = convert(v, tree.type);
1265 } else if (!expressionType.hasTag(BOT) &&
1266 types.isAssignable(expressionType, type)) {
1267 // Redundant cast
1268 result = v;
1269 } else {
1270 // Reference cast
1271 JavaType jt = typeToCodeType(types.erasure(type));
1272 result = append(JavaOp.cast(typeToCodeType(type), jt, v));
1273 }
1274 }
1275
1276 @Override
1277 public void visitTypeTest(JCTree.JCInstanceOf tree) {
1278 Value target = toValue(tree.expr);
1279 JCTree.JCPattern pattern = tree.getPattern();
1280 if (pattern != null) {
1281 result = scanPattern(pattern, target);
1282 } else {
1283 result = append(JavaOp.instanceOf(typeToCodeType(tree.pattern.type), target));
1284 }
1285 }
1286
1287 Body.Builder scanPatternAsBody(JCTree.JCPattern pattern, Value target) {
1288 pushBody(pattern, CoreType.functionType(JavaType.BOOLEAN));
1289 Value localTarget = boxIfNeeded(target);
1290 Value patVal = scanPattern(pattern, localTarget);
1291 append(CoreOp.core_yield(patVal));
1292 Body.Builder patternBody = stack.body;
1293 popBody();
1294 return patternBody;
1295 }
1296
1297 Value scanPattern(JCTree.JCPattern pattern, Value target) {
1298 // Type of pattern
1299 JavaType patternType;
1300 if (pattern instanceof JCTree.JCBindingPattern p) {
1301 patternType = JavaOp.Pattern.bindingType(typeToCodeType(p.type));
1302 } else if (pattern instanceof JCTree.JCRecordPattern p) {
1303 patternType = JavaOp.Pattern.recordType(typeToCodeType(p.record.type));
1304 } else {
1305 throw unreachable(); // toplevel patterns are type test/record
1306 }
1307
1308 // Push pattern body
1309 pushBody(pattern, CoreType.functionType(patternType));
1310
1311 // @@@ Assumes just pattern nodes, likely will change when method patterns are supported
1312 // that have expressions for any arguments (which perhaps in turn may have pattern expressions)
1313 List<JCVariableDecl> variables = new ArrayList<>();
1314 class PatternScanner extends FilterScanner {
1315
1316 private Value result;
1317
1318 public PatternScanner() {
1319 super(Set.of(Tag.BINDINGPATTERN, Tag.RECORDPATTERN, Tag.ANYPATTERN));
1320 }
1321
1322 @Override
1323 public void visitBindingPattern(JCTree.JCBindingPattern binding) {
1324 JCVariableDecl var = binding.var;
1325 variables.add(var);
1326 boolean unnamedPatternVariable = var.name.isEmpty();
1327 String bindingName = unnamedPatternVariable ? null : var.name.toString();
1328 result = append(JavaOp.typePattern(typeToCodeType(var.type), bindingName));
1329 }
1330
1331 @Override
1332 public void visitRecordPattern(JCTree.JCRecordPattern record) {
1333 // @@@ Is always Identifier to record?
1334 // scan(record.deconstructor);
1335
1336 List<Value> nestedValues = new ArrayList<>();
1337 for (JCTree.JCPattern jcPattern : record.nested) {
1338 // @@@ when we support ANYPATTERN, we must add result of toValue only if it's non-null
1339 // because passing null to recordPattern methods will cause an error
1340 nestedValues.add(toValue(jcPattern));
1341 }
1342
1343 result = append(JavaOp.recordPattern(symbolToRecordTypeRef(record.record), nestedValues));
1344 }
1345
1346 @Override
1347 public void visitAnyPattern(JCTree.JCAnyPattern anyPattern) {
1348 result = append(JavaOp.matchAllPattern());
1349 }
1350
1351 Value toValue(JCTree tree) {
1352 result = null;
1353 scan(tree);
1354 return result;
1355 }
1356 }
1357 // Scan pattern
1358 Value patternValue = new PatternScanner().toValue(pattern);
1359 append(CoreOp.core_yield(patternValue));
1360 Body.Builder patternBody = stack.body;
1361
1362 // Pop body
1363 popBody();
1364
1365 // Find nearest ancestor body stack element associated with a statement tree
1366 // @@@ Strengthen check of tree?
1367 BodyStack _variablesStack = stack;
1368 while (!(_variablesStack.tree instanceof JCLambda)
1369 && !(_variablesStack.tree instanceof JCTree.JCStatement)) {
1370 _variablesStack = _variablesStack.parent;
1371 }
1372 BodyStack variablesStack = _variablesStack;
1373
1374 // Create pattern var ops for pattern variables using the
1375 // builder associated with the nearest statement tree
1376 BodyStack previous = stack;
1377 // Temporarily position the stack to where the pattern variables are to be declared
1378 stack = variablesStack;
1379 try {
1380 for (JCVariableDecl jcVar : variables) {
1381 // @@@ use uninitialized variable
1382 Value defaultValue = append(defaultValue(jcVar.type));
1383 Value init = convert(defaultValue, jcVar.type);
1384 Op.Result op = append(CoreOp.var(jcVar.name.toString(), typeToCodeType(jcVar.type), init));
1385 stack.localToOp.put(jcVar.sym, op);
1386 }
1387 } finally {
1388 stack = previous;
1389 }
1390
1391 // Create pattern descriptor
1392 List<JavaType> patternDescParams = variables.stream().map(var -> typeToCodeType(var.type)).toList();
1393 FunctionType matchFuncType = CoreType.functionType(JavaType.VOID, patternDescParams);
1394
1395 // Create the match body, assigning pattern values to pattern variables
1396 Body.Builder matchBody = Body.Builder.of(patternBody.connectedAncestorBody(), matchFuncType);
1397 Block.Builder matchBuilder = matchBody.entryBlock();
1398 for (int i = 0; i < variables.size(); i++) {
1399 Value v = matchBuilder.parameters().get(i);
1400 Value var = variablesStack.localToOp.get(variables.get(i).sym);
1401 matchBuilder.add(CoreOp.varStore(var, v));
1402 }
1403 matchBuilder.add(CoreOp.core_yield());
1404
1405 // Create the match operation
1406 return append(JavaOp.match(target, patternBody, matchBody));
1407 }
1408
1409 @Override
1410 public void visitNewClass(JCTree.JCNewClass tree) {
1411 if (tree.def != null) {
1412 scan(tree.def);
1413 }
1414
1415 List<CodeType> argtypes = new ArrayList<>();
1416 Type type = tree.type;
1417 List<Value> args = new ArrayList<>();
1418 if (type.tsym.hasOuterInstance()) {
1419 // Obtain outer value for inner class, and add as first argument
1420 JCTree.JCExpression encl = tree.encl;
1421 Value outerInstance;
1422 if (encl == null) {
1423 outerInstance = thisValue();
1424 } else {
1425 outerInstance = toValue(tree.encl);
1426 }
1427 args.add(outerInstance);
1428 JavaType outerType = typeToCodeType(tree.constructor.innermostAccessibleEnclosingClass().erasure(types));
1429 argtypes.add(outerType);
1430 }
1431
1432 MethodRef methodRef = symbolToErasedMethodRef(tree.constructor);
1433 argtypes.addAll(methodRef.signature().parameterTypes());
1434 args.addAll(scanMethodArguments(tree.args, tree.constructorType, tree.varargsElement));
1435
1436 if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
1437 for (Symbol c : localCaptures.get(tree.type.tsym)) {
1438 args.add(loadVar(c));
1439 argtypes.add(symbolToErasedDesc(c));
1440 }
1441 }
1442
1443 // Create erased method type reference for constructor, where
1444 // the return type declares the class to instantiate
1445 // We need to manually construct the constructor reference,
1446 // as the signature of the constructor symbol is not augmented
1447 // with enclosing this and captured params.
1448 FunctionType constructorSignature = CoreType.functionType(
1449 symbolToErasedDesc(tree.constructor.owner),
1450 argtypes);
1451 MethodRef constructorRef = MethodRef.constructor(constructorSignature);
1452
1453 result = append(JavaOp.new_(tree.varargsElement != null, typeToCodeType(type), constructorRef, args));
1454 }
1455
1456 @Override
1457 public void visitNewArray(JCTree.JCNewArray tree) {
1458 if (tree.elems != null) {
1459 int length = tree.elems.size();
1460 Op.Result a = append(JavaOp.newArray(
1461 typeToCodeType(tree.type),
1462 append(CoreOp.constant(JavaType.INT, length))));
1463 int i = 0;
1464 for (JCExpression elem : tree.elems) {
1465 Value element = toValue(elem, types.elemtype(tree.type));
1466 append(JavaOp.arrayStoreOp(
1467 a,
1468 append(CoreOp.constant(JavaType.INT, i)),
1469 element));
1470 i++;
1471 }
1472
1473 result = a;
1474 } else {
1475 List<Value> indexes = new ArrayList<>();
1476 for (JCTree.JCExpression dim : tree.dims) {
1477 indexes.add(toValue(dim));
1478 }
1479
1480 JavaType arrayType = typeToCodeType(tree.type);
1481 MethodRef constructorRef = MethodRef.constructor(arrayType,
1482 indexes.stream().map(Value::type).toList());
1483 result = append(JavaOp.new_(constructorRef, indexes));
1484 }
1485 }
1486
1487 @Override
1488 public void visitLambda(JCTree.JCLambda tree) {
1489 final FunctionType lambdaType = typeToFunctionType(types.findDescriptorType(tree.target));
1490
1491 // Push quoted body for a reflectable lambda
1492
1493 // A reflectable lambda is going to have its model wrapped in QuotedOp
1494 // only when we are producing the model of the lambda, thus the condition (isReflectable ...)
1495 // also, a lambda contained in a reflectable lambda, will not have its model wrapped in QuotedOp,
1496 // thus the condition (... body == tree)
1497 boolean toQuote = (isLambdaReflectable && this.tree == tree);
1498 if (toQuote) {
1499 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1500 }
1501
1502 // Push lambda body
1503 // for expression lambda, the body stack need to be mapped to the JCLambda tree
1504 // this ensures the logic for computing pattern variable stack, works correctly
1505 pushBody(tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION ? tree : tree.body, lambdaType);
1506
1507 // Map lambda parameters to varOp values
1508 for (int i = 0; i < tree.params.size(); i++) {
1509 JCVariableDecl p = tree.params.get(i);
1510 Op.Result paramOp = append(CoreOp.var(
1511 p.name.toString(),
1512 stack.block.parameters().get(i)));
1513 stack.localToOp.put(p.sym, paramOp);
1514 }
1515
1516 // Scan the lambda body
1517 Type lambdaReturnType = tree.getDescriptorType(types).getReturnType();
1518 if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
1519 Value exprVal = toValue(((JCExpression) tree.body), lambdaReturnType);
1520 if (!lambdaReturnType.hasTag(TypeTag.VOID)) {
1521 append(CoreOp.return_(exprVal));
1522 } else {
1523 appendTerminating(CoreOp::return_);
1524 }
1525 } else {
1526 Type prevBodyTarget = bodyTarget;
1527 try {
1528 bodyTarget = lambdaReturnType;
1529 toValue(((JCTree.JCStatement) tree.body));
1530 appendReturnOrUnreachable(tree.body);
1531 } finally {
1532 bodyTarget = prevBodyTarget;
1533 }
1534 }
1535
1536 // Get the functional interface type
1537 JavaType fiType = typeToCodeType(tree.target);
1538 // build functional lambda
1539 Op lambdaOp = JavaOp.lambda(fiType, stack.body, true);
1540
1541 // Pop lambda body
1542 popBody();
1543
1544 Value lambdaResult;
1545 if (toQuote) {
1546 lambdaResult = append(lambdaOp, generateLocation(tree, true));
1547 } else {
1548 lambdaResult = append(lambdaOp);
1549 }
1550
1551 if (toQuote) {
1552 append(CoreOp.core_yield(lambdaResult));
1553 CoreOp.QuotedOp quotedOp = CoreOp.quoted(stack.body);
1554
1555 // Pop quoted body
1556 popBody();
1557
1558 lambdaResult = append(quotedOp);
1559 }
1560
1561 result = lambdaResult;
1562 }
1563
1564 @Override
1565 public void visitIf(JCTree.JCIf tree) {
1566 List<Body.Builder> bodies = new ArrayList<>();
1567
1568 while (tree != null) {
1569 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1570
1571 // Push if condition
1572 pushBody(cond,
1573 CoreType.functionType(JavaType.BOOLEAN));
1574 Value last = toValue(cond, syms.booleanType);
1575 // Yield the boolean result of the condition
1576 append(CoreOp.core_yield(last));
1577 bodies.add(stack.body);
1578
1579 // Pop if condition
1580 popBody();
1581
1582 // Push if body
1583 pushBody(tree.thenpart, CoreType.FUNCTION_TYPE_VOID);
1584
1585 scan(tree.thenpart);
1586 appendTerminating(CoreOp::core_yield);
1587 bodies.add(stack.body);
1588
1589 // Pop if body
1590 popBody();
1591
1592 JCTree.JCStatement elsepart = tree.elsepart;
1593 if (elsepart == null) {
1594 tree = null;
1595 } else if (elsepart.getTag() == Tag.IF) {
1596 tree = (JCTree.JCIf) elsepart;
1597 } else {
1598 // Push else body
1599 pushBody(elsepart, CoreType.FUNCTION_TYPE_VOID);
1600
1601 scan(elsepart);
1602 appendTerminating(CoreOp::core_yield);
1603 bodies.add(stack.body);
1604
1605 // Pop else body
1606 popBody();
1607
1608 tree = null;
1609 }
1610 }
1611
1612 append(JavaOp.if_(bodies));
1613 result = null;
1614 }
1615
1616 @Override
1617 public void visitSwitchExpression(JCTree.JCSwitchExpression tree) {
1618 Value target = toValue(tree.selector);
1619
1620 Type switchType = adaptBottom(tree.type);
1621 FunctionType caseBodyType = CoreType.functionType(typeToCodeType(switchType));
1622
1623 SwitchBodyInfo bodyInfo = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, caseBodyType,
1624 !tree.hasUnconditionalPattern);
1625
1626 result = append(JavaOp.switchExpression(caseBodyType.returnType(), target, bodyInfo.handlesNull, bodyInfo.bodies));
1627 }
1628
1629 @Override
1630 public void visitSwitch(JCTree.JCSwitch tree) {
1631 Value target = toValue(tree.selector);
1632
1633 FunctionType actionType = CoreType.FUNCTION_TYPE_VOID;
1634
1635 SwitchBodyInfo bodyInfo = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, actionType,
1636 tree.patternSwitch && !tree.hasUnconditionalPattern);
1637
1638 result = append(JavaOp.switchStatement(target, bodyInfo.handlesNull, bodyInfo.bodies));
1639 }
1640
1641 record SwitchBodyInfo(boolean handlesNull, List<Body.Builder> bodies) { }
1642
1643 private SwitchBodyInfo visitSwitchStatAndExpr(JCTree tree, JCExpression selector, Value target,
1644 List<JCTree.JCCase> cases, FunctionType caseBodyType,
1645 boolean isDefaultCaseNeeded) {
1646 List<Body.Builder> bodies = new ArrayList<>();
1647 boolean hasDefaultCase = false;
1648 boolean handlesNull = false;
1649
1650 for (JCTree.JCCase c : cases) {
1651 if (handlesNull(c)) {
1652 handlesNull = true;
1653 }
1654 if (isDefault(c)) {
1655 hasDefaultCase = true;
1656 }
1657 Body.Builder caseLabel = visitCaseLabel(tree, target, c);
1658 Body.Builder caseBody = visitCaseBody(tree, c, caseBodyType, cases.getLast() == c);
1659 bodies.add(caseLabel);
1660 bodies.add(caseBody);
1661 }
1662
1663 if (!hasDefaultCase && isDefaultCaseNeeded) {
1664 // label
1665 pushBody(tree, CoreType.functionType(JavaType.BOOLEAN));
1666 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1667 bodies.add(stack.body);
1668 popBody();
1669
1670 // body
1671 pushBody(tree, caseBodyType);
1672 append(JavaOp.throw_(
1673 append(JavaOp.new_(MethodRef.constructor(MatchException.class)))
1674 ));
1675 bodies.add(stack.body);
1676 popBody();
1677 }
1678
1679 return new SwitchBodyInfo(handlesNull, bodies);
1680 }
1681
1682 boolean handlesNull(JCTree.JCCase caseTree) {
1683 return caseTree.labels.stream().anyMatch(l -> l instanceof JCConstantCaseLabel constLabel &&
1684 TreeInfo.isNull(constLabel.expr));
1685 }
1686
1687 boolean isDefault(JCTree.JCCase caseTree) {
1688 return caseTree.labels.stream().anyMatch(l -> l instanceof JCDefaultCaseLabel);
1689 }
1690
1691 private Value processConstantLabel(Value target, JCTree.JCConstantCaseLabel label) {
1692 if (target.type().equals(JavaType.J_L_STRING)) {
1693 return append(JavaOp.invoke(
1694 MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
1695 target, toValue(label.expr)));
1696 } else {
1697 // target is primitive wrapper, primitive or enum
1698 // if target of type Character, Byte, Short or Integer, unbox it
1699 if (target.type().equals(JavaType.J_L_CHARACTER) || target.type().equals(JavaType.J_L_BYTE) ||
1700 target.type().equals(JavaType.J_L_SHORT) || target.type().equals(JavaType.J_L_INTEGER)) {
1701 PrimitiveType pt = ((ClassType) target.type()).unbox().get();
1702 target = convert(target, codeTypeToType(pt));
1703 }
1704 Value expr = toValue(label.expr);
1705 // conversion may be needed for primitive, e.g. label (byte) 1 and selector of type int
1706 expr = convert(expr, codeTypeToType(target.type()));
1707 return append(JavaOp.eq(target, expr));
1708 }
1709 }
1710
1711 private Body.Builder visitCaseLabel(JCTree tree, Value target, JCTree.JCCase c) {
1712 Body.Builder body;
1713 FunctionType caseLabelType = CoreType.functionType(JavaType.BOOLEAN, target.type());
1714
1715 JCTree.JCCaseLabel headCl = c.labels.head;
1716 if (isDefault(c)) {
1717 // @@@ Do we need to model the default label body?
1718 pushBody(headCl, CoreType.functionType(JavaType.BOOLEAN));
1719
1720 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1721 body = stack.body;
1722
1723 // Pop label
1724 popBody();
1725 } else if (headCl instanceof JCTree.JCPatternCaseLabel pcl) {
1726 boolean isMultiLabel = c.labels.size() > 1;
1727
1728 pushBody(pcl, caseLabelType);
1729
1730 Value localTarget = stack.block.parameters().get(0);
1731 final Value localResult;
1732 if (c.guard != null) {
1733 List<Body.Builder> clBodies = new ArrayList<>();
1734
1735 if (isMultiLabel) {
1736 // push a body for or-ing the patterns
1737 pushBody(pcl, CoreType.functionType(JavaType.BOOLEAN));
1738 }
1739
1740 for (JCCaseLabel l : c.labels) {
1741 JCTree.JCPatternCaseLabel pat = (JCTree.JCPatternCaseLabel)l;
1742 clBodies.add(scanPatternAsBody(pat.pat, localTarget));
1743 }
1744
1745 if (isMultiLabel) {
1746 // or the pattern bodies and replace clBodies with a single body
1747 Value patternOrResult = append(JavaOp.conditionalOr(clBodies));
1748 append(CoreOp.core_yield(patternOrResult));
1749 clBodies.clear();
1750 clBodies.add(stack.body);
1751 popBody();
1752 }
1753
1754 pushBody(c.guard, CoreType.functionType(JavaType.BOOLEAN));
1755 append(CoreOp.core_yield(toValue(c.guard, syms.booleanType)));
1756 clBodies.add(stack.body);
1757 popBody();
1758
1759 localResult = append(JavaOp.conditionalAnd(clBodies));
1760 } else if (isMultiLabel) {
1761 List<Body.Builder> clBodies = new ArrayList<>();
1762 for (JCCaseLabel l : c.labels) {
1763 JCTree.JCPatternCaseLabel pat = (JCTree.JCPatternCaseLabel)l;
1764 clBodies.add(scanPatternAsBody(pat.pat, localTarget));
1765 }
1766 localResult = append(JavaOp.conditionalOr(clBodies));
1767 } else {
1768 localTarget = boxIfNeeded(localTarget);
1769 localResult = scanPattern(pcl.pat, localTarget);
1770 }
1771 // Yield the boolean result of the condition
1772 append(CoreOp.core_yield(localResult));
1773 body = stack.body;
1774
1775 // Pop label
1776 popBody();
1777 } else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) {
1778 pushBody(headCl, caseLabelType);
1779
1780 Value localTarget = stack.block.parameters().get(0);
1781 final Value localResult;
1782 if (c.labels.size() == 1) {
1783 localResult = processConstantLabel(localTarget, ccl);
1784 } else {
1785 List<Body.Builder> clBodies = new ArrayList<>();
1786 for (JCTree.JCCaseLabel cl : c.labels) {
1787 ccl = (JCTree.JCConstantCaseLabel) cl;
1788 pushBody(ccl, CoreType.functionType(JavaType.BOOLEAN));
1789
1790 final Value labelResult = processConstantLabel(localTarget, ccl);
1791
1792 append(CoreOp.core_yield(labelResult));
1793 clBodies.add(stack.body);
1794
1795 // Pop label
1796 popBody();
1797 }
1798
1799 localResult = append(JavaOp.conditionalOr(clBodies));
1800 }
1801
1802 append(CoreOp.core_yield(localResult));
1803 body = stack.body;
1804
1805 // Pop labels
1806 popBody();
1807 } else {
1808 throw unreachable();
1809 }
1810
1811 return body;
1812 }
1813
1814 private Body.Builder visitCaseBody(JCTree tree, JCTree.JCCase c, FunctionType caseBodyType, boolean isLastCase) {
1815 Body.Builder body = null;
1816
1817 switch (c.caseKind) {
1818 case RULE -> {
1819 pushBody(c.body, caseBodyType);
1820
1821 if (c.body instanceof JCTree.JCExpression e) {
1822 Type yieldType = adaptBottom(tree.type);
1823 Value bodyVal = toValue(e, yieldType);
1824 append(CoreOp.core_yield(bodyVal));
1825 } else if (c.body instanceof JCTree.JCStatement s) { // this includes Block
1826 // Otherwise there is a yield statement
1827 toValue(s);
1828 appendTerminating(c.completesNormally ? CoreOp::core_yield : CoreOp::unreachable);
1829 }
1830 body = stack.body;
1831
1832 // Pop block
1833 popBody();
1834 }
1835 case STATEMENT -> {
1836 // @@@ Avoid nesting for a single block? Goes against "say what you see"
1837 // boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock;
1838 pushBody(c, caseBodyType);
1839
1840 scan(c.stats);
1841
1842 appendTerminating(c.completesNormally
1843 ? isLastCase ? CoreOp::core_yield : JavaOp::switchFallthroughOp
1844 : CoreOp::unreachable);
1845
1846 body = stack.body;
1847
1848 // Pop block
1849 popBody();
1850 }
1851 }
1852 return body;
1853 }
1854
1855 @Override
1856 public void visitYield(JCTree.JCYield tree) {
1857 Type yieldType = adaptBottom(tree.target.type);
1858 Value retVal = toValue(tree.value, yieldType);
1859 result = append(JavaOp.java_yield(retVal));
1860 }
1861
1862 @Override
1863 public void visitWhileLoop(JCTree.JCWhileLoop tree) {
1864 // @@@ Patterns
1865 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1866
1867 // Push while condition
1868 pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1869 Value last = toValue(cond, syms.booleanType);
1870 // Yield the boolean result of the condition
1871 append(CoreOp.core_yield(last));
1872 Body.Builder condition = stack.body;
1873
1874 // Pop while condition
1875 popBody();
1876
1877 // Push while body
1878 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1879 scan(tree.body);
1880 appendTerminating(JavaOp::continue_);
1881 Body.Builder body = stack.body;
1882
1883 // Pop while body
1884 popBody();
1885
1886 append(JavaOp.while_(condition, body));
1887 result = null;
1888 }
1889
1890 @Override
1891 public void visitDoLoop(JCTree.JCDoWhileLoop tree) {
1892 // @@@ Patterns
1893 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1894
1895 // Push while body
1896 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1897 scan(tree.body);
1898 appendTerminating(JavaOp::continue_);
1899 Body.Builder body = stack.body;
1900
1901 // Pop while body
1902 popBody();
1903
1904 // Push while condition
1905 pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1906 Value last = toValue(cond, syms.booleanType);
1907 // Yield the boolean result of the condition
1908 append(CoreOp.core_yield(last));
1909 Body.Builder condition = stack.body;
1910
1911 // Pop while condition
1912 popBody();
1913
1914 append(JavaOp.doWhile(body, condition));
1915 result = null;
1916 }
1917
1918 @Override
1919 public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
1920 // Push expression
1921 pushBody(tree.expr, CoreType.functionType(typeToCodeType(tree.expr.type)));
1922 Value last = toValue(tree.expr);
1923 // Yield the Iterable result of the expression
1924 append(CoreOp.core_yield(last));
1925 Body.Builder expression = stack.body;
1926
1927 // Pop expression
1928 popBody();
1929
1930 JCVariableDecl var = tree.getVariable();
1931 VarType varEType = CoreType.varType(typeToCodeType(var.type));
1932
1933 // Push init
1934 // @@@ When lhs assignment is a pattern we embed the pattern match into the init body and
1935 // return the bound variables
1936 Type exprType = types.cvarUpperBound(tree.expr.type);
1937 Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
1938 if (elemtype == null) {
1939 Type iterableType = types.asSuper(tree.expr.type, syms.iterableType.tsym);
1940 com.sun.tools.javac.util.List<Type> iterableParams = iterableType.allparams();
1941 elemtype = iterableParams.isEmpty()
1942 ? syms.objectType
1943 : types.wildUpperBound(iterableParams.head);
1944 }
1945 pushBody(var, CoreType.functionType(varEType, typeToCodeType(elemtype)));
1946 var initVarExpr = convert(stack.block.parameters().get(0), var.type);
1947 Op.Result varEResult = append(CoreOp.var(var.name.toString(), initVarExpr));
1948 append(CoreOp.core_yield(varEResult));
1949 Body.Builder init = stack.body;
1950 // Pop init
1951 popBody();
1952
1953 // Push body
1954 pushBody(tree.body, CoreType.functionType(JavaType.VOID, varEType));
1955 stack.localToOp.put(var.sym, stack.block.parameters().get(0));
1956
1957 scan(tree.body);
1958 appendTerminating(JavaOp::continue_);
1959 Body.Builder body = stack.body;
1960 // Pop body
1961 popBody();
1962
1963 append(JavaOp.enhancedFor(expression, init, body));
1964 result = null;
1965 }
1966
1967 @Override
1968 public void visitForLoop(JCTree.JCForLoop tree) {
1969 class VarDefScanner extends FilterScanner {
1970 final List<JCVariableDecl> decls;
1971
1972 public VarDefScanner() {
1973 super(Set.of(Tag.VARDEF));
1974 this.decls = new ArrayList<>();
1975 }
1976
1977 @Override
1978 public void visitVarDef(JCVariableDecl tree) {
1979 decls.add(tree);
1980 }
1981
1982 void mapVarsToBlockArguments() {
1983 for (int i = 0; i < decls.size(); i++) {
1984 stack.localToOp.put(decls.get(i).sym, stack.block.parameters().get(i));
1985 }
1986 }
1987
1988 List<VarType> varTypes() {
1989 return decls.stream()
1990 .map(t -> CoreType.varType(typeToCodeType(t.type)))
1991 .toList();
1992 }
1993
1994 List<Value> varValues() {
1995 return decls.stream()
1996 .map(t -> stack.localToOp.get(t.sym))
1997 .toList();
1998 }
1999 }
2000
2001 // Scan local variable declarations
2002 VarDefScanner vds = new VarDefScanner();
2003 vds.scan(tree.init);
2004 List<VarType> varTypes = vds.varTypes();
2005
2006 // Push init
2007 if (varTypes.size() > 1) {
2008 pushBody(null, CoreType.functionType(CoreType.tupleType(varTypes)));
2009 scan(tree.init);
2010
2011 // Capture all local variable declarations in tuple
2012 append(CoreOp.core_yield(append(CoreOp.tuple(vds.varValues()))));
2013 } else if (varTypes.size() == 1) {
2014 pushBody(null, CoreType.functionType(varTypes.get(0)));
2015 scan(tree.init);
2016
2017 append(CoreOp.core_yield(vds.varValues().get(0)));
2018 } else {
2019 pushBody(null, CoreType.FUNCTION_TYPE_VOID);
2020 scan(tree.init);
2021
2022 append(CoreOp.core_yield());
2023 }
2024 Body.Builder init = stack.body;
2025
2026 // Pop init
2027 popBody();
2028
2029 // Push cond
2030 pushBody(tree.cond, CoreType.functionType(JavaType.BOOLEAN, varTypes));
2031 if (tree.cond != null) {
2032 vds.mapVarsToBlockArguments();
2033
2034 Value last = toValue(tree.cond, syms.booleanType);
2035 // Yield the boolean result of the condition
2036 append(CoreOp.core_yield(last));
2037 } else {
2038 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
2039 }
2040 Body.Builder cond = stack.body;
2041
2042 // Pop cond
2043 popBody();
2044
2045 // Push update
2046 // @@@ tree.step is a List<JCStatement>
2047 pushBody(null, CoreType.functionType(JavaType.VOID, varTypes));
2048 if (!tree.step.isEmpty()) {
2049 vds.mapVarsToBlockArguments();
2050
2051 scan(tree.step);
2052 }
2053 append(CoreOp.core_yield());
2054 Body.Builder update = stack.body;
2055
2056 // Pop update
2057 popBody();
2058
2059 // Push body
2060 pushBody(tree.body, CoreType.functionType(JavaType.VOID, varTypes));
2061 if (tree.body != null) {
2062 vds.mapVarsToBlockArguments();
2063
2064 scan(tree.body);
2065 }
2066 appendTerminating(JavaOp::continue_);
2067 Body.Builder body = stack.body;
2068
2069 // Pop update
2070 popBody();
2071
2072 append(JavaOp.for_(init, cond, update, body));
2073 result = null;
2074 }
2075
2076 @Override
2077 public void visitConditional(JCTree.JCConditional tree) {
2078 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2079
2080 // Push condition
2081 pushBody(cond,
2082 CoreType.functionType(JavaType.BOOLEAN));
2083 Value condVal = toValue(cond, syms.booleanType);
2084 // Yield the boolean result of the condition
2085 append(CoreOp.core_yield(condVal));
2086 Body.Builder predicateBody = stack.body;
2087
2088 // Pop condition
2089 popBody();
2090
2091 JCTree.JCExpression truepart = TreeInfo.skipParens(tree.truepart);
2092
2093 Type condType = adaptBottom(tree.type);
2094
2095 // Push true body
2096 pushBody(truepart,
2097 CoreType.functionType(typeToCodeType(condType)));
2098
2099 Value trueVal = toValue(truepart, condType);
2100 // Yield the result
2101 append(CoreOp.core_yield(trueVal));
2102 Body.Builder trueBody = stack.body;
2103
2104 // Pop true body
2105 popBody();
2106
2107 JCTree.JCExpression falsepart = TreeInfo.skipParens(tree.falsepart);
2108
2109 // Push false body
2110 pushBody(falsepart,
2111 CoreType.functionType(typeToCodeType(condType)));
2112
2113 Value falseVal = toValue(falsepart, condType);
2114 // Yield the result
2115 append(CoreOp.core_yield(falseVal));
2116 Body.Builder falseBody = stack.body;
2117
2118 // Pop false body
2119 popBody();
2120
2121 result = append(JavaOp.conditionalExpression(typeToCodeType(condType), predicateBody, trueBody, falseBody));
2122 }
2123
2124 private Type condType(JCExpression tree, Type type) {
2125 if (type.hasTag(BOT)) {
2126 return adaptBottom(tree.type);
2127 } else {
2128 return type;
2129 }
2130 }
2131
2132 private Type adaptBottom(Type type) {
2133 return type.hasTag(BOT) ?
2134 (pt.hasTag(NONE) ? syms.objectType : pt) :
2135 type;
2136 }
2137
2138 @Override
2139 public void visitAssert(JCAssert tree) {
2140 // assert <cond:body1> [detail:body2]
2141
2142 List<Body.Builder> bodies = new ArrayList<>();
2143 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2144
2145 // Push condition
2146 pushBody(cond,
2147 CoreType.functionType(JavaType.BOOLEAN));
2148 Value condVal = toValue(cond, syms.booleanType);
2149
2150 // Yield the boolean result of the condition
2151 append(CoreOp.core_yield(condVal));
2152 bodies.add(stack.body);
2153
2154 // Pop condition
2155 popBody();
2156
2157 if (tree.detail != null) {
2158 JCTree.JCExpression detail = TreeInfo.skipParens(tree.detail);
2159
2160 pushBody(detail,
2161 CoreType.functionType(typeToCodeType(tree.detail.type)));
2162 Value detailVal = toValue(detail);
2163
2164 append(CoreOp.core_yield(detailVal));
2165 bodies.add(stack.body);
2166
2167 //Pop detail
2168 popBody();
2169 }
2170
2171 result = append(JavaOp.assert_(bodies));
2172
2173 }
2174
2175 @Override
2176 public void visitBlock(JCTree.JCBlock tree) {
2177 if (stack.tree == tree) {
2178 // Block is associated with the visit of a parent structure
2179 scan(tree.stats);
2180 } else {
2181 // Otherwise, independent block structure
2182 // Push block
2183 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2184 scan(tree.stats);
2185 appendTerminating(CoreOp::core_yield);
2186 Body.Builder body = stack.body;
2187
2188 // Pop block
2189 popBody();
2190
2191 append(JavaOp.block(body));
2192 }
2193 result = null;
2194 }
2195
2196 @Override
2197 public void visitSynchronized(JCTree.JCSynchronized tree) {
2198 // Push expr
2199 pushBody(tree.lock, CoreType.functionType(typeToCodeType(tree.lock.type)));
2200 Value last = toValue(tree.lock);
2201 append(CoreOp.core_yield(last));
2202 Body.Builder expr = stack.body;
2203
2204 // Pop expr
2205 popBody();
2206
2207 // Push body block
2208 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
2209 // Scan body block statements
2210 scan(tree.body.stats);
2211 appendTerminating(CoreOp::core_yield);
2212 Body.Builder blockBody = stack.body;
2213
2214 // Pop body block
2215 popBody();
2216
2217 append(JavaOp.synchronized_(expr, blockBody));
2218 }
2219
2220 @Override
2221 public void visitLabelled(JCTree.JCLabeledStatement tree) {
2222 // Push block
2223 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2224 // Create constant for label
2225 String labelName = tree.label.toString();
2226 Op.Result label = append(CoreOp.constant(JavaType.J_L_STRING, labelName));
2227 // Set label on body stack
2228 stack.setLabel(labelName, label);
2229 scan(tree.body);
2230 appendTerminating(CoreOp::core_yield);
2231 Body.Builder body = stack.body;
2232
2233 // Pop block
2234 popBody();
2235
2236 result = append(JavaOp.labeled(body));
2237 }
2238
2239 @Override
2240 public void visitTry(JCTree.JCTry tree) {
2241 List<Symbol> rVariableDecls = new ArrayList<>();
2242 List<CodeType> rTypes = new ArrayList<>();
2243 List<Body.Builder> resources = new ArrayList<>();
2244 if (!tree.resources.isEmpty()) {
2245 // Resources bodies return the resource variables/values in order of declaration
2246 for (JCTree resource : tree.resources) {
2247 CodeType rType;
2248 if (resource instanceof JCVariableDecl vdecl) {
2249 rType = CoreType.varType(typeToCodeType(vdecl.type));
2250 } else {
2251 rType = typeToCodeType(resource.type);
2252 }
2253
2254 // Push resources body
2255 pushBody(null, CoreType.functionType(rType, rTypes));
2256 for (int i = 0; i < rVariableDecls.size(); i++) {
2257 Symbol rVariableDecl = rVariableDecls.get(i);
2258 if (rVariableDecl != null) {
2259 stack.localToOp.put(rVariableDecl, stack.block.parameters().get(i));
2260 }
2261 }
2262
2263 if (resource instanceof JCTree.JCExpression e) {
2264 append(CoreOp.core_yield(toValue(e)));
2265 } else if (resource instanceof JCTree.JCStatement s) {
2266 append(CoreOp.core_yield(toValue(s)));
2267 }
2268
2269 resources.add(stack.body);
2270
2271 // Pop resources body
2272 popBody();
2273
2274 // Null entries preserve positions for resource expressions, which have no variable declaration.
2275 rVariableDecls.add(resource instanceof JCVariableDecl vdecl ? vdecl.sym : null);
2276 rTypes.add(rType);
2277 }
2278 }
2279
2280 // Push body
2281 // Try body accepts the resource variables (in order of declaration).
2282 pushBody(tree.body, CoreType.functionType(JavaType.VOID, rTypes));
2283 for (int i = 0; i < rVariableDecls.size(); i++) {
2284 stack.localToOp.put(rVariableDecls.get(i), stack.block.parameters().get(i));
2285 }
2286 scan(tree.body);
2287 appendTerminating(CoreOp::core_yield);
2288 Body.Builder body = stack.body;
2289
2290 // Pop block
2291 popBody();
2292
2293 List<CodeType> catchTypes = new ArrayList<>();
2294 List<Body.Builder> catchers = new ArrayList<>();
2295 for (JCTree.JCCatch catcher : tree.catchers) {
2296
2297 catchTypes.add(TreeInfo.isMultiCatch(catcher)
2298 ? CoreType.tupleType(((JCTree.JCTypeUnion) catcher.param.vartype).alternatives.stream()
2299 .map(a -> typeToCodeType(a.type)).toList())
2300 : typeToCodeType(catcher.param.vartype.type));
2301
2302 // Push body
2303 pushBody(catcher.body, CoreType.functionType(JavaType.VOID, typeToCodeType(catcher.param.type)));
2304 Op.Result exVariable = append(CoreOp.var(
2305 catcher.param.name.toString(),
2306 stack.block.parameters().get(0)));
2307 stack.localToOp.put(catcher.param.sym, exVariable);
2308 scan(catcher.body);
2309 appendTerminating(CoreOp::core_yield);
2310 catchers.add(stack.body);
2311
2312 // Pop block
2313 popBody();
2314 }
2315
2316 Body.Builder finalizer;
2317 if (tree.finalizer != null) {
2318 // Push body
2319 pushBody(tree.finalizer, CoreType.FUNCTION_TYPE_VOID);
2320 scan(tree.finalizer);
2321 appendTerminating(CoreOp::core_yield);
2322 finalizer = stack.body;
2323
2324 // Pop block
2325 popBody();
2326 }
2327 else {
2328 finalizer = null;
2329 }
2330
2331 result = append(JavaOp.try_(resources, body, catchTypes, catchers, finalizer));
2332 }
2333
2334 @Override
2335 public void visitUnary(JCTree.JCUnary tree) {
2336 Tag tag = tree.getTag();
2337 switch (tag) {
2338 case POSTINC, POSTDEC, PREINC, PREDEC -> {
2339 // Capture applying rhs and operation
2340 Function<Value, Value> scanRhs = (lhs) -> {
2341 // arithmetic operators are all of kind (T, T)T
2342 Type opType = tree.operator.type.getReturnType();
2343 if (!opType.hasTag(INT) &&
2344 opType.getTag().isSubRangeOf(INT)) {
2345 // unary ++/-- can use sub-int operator types,
2346 // which doesn't make sense for the model
2347 opType = syms.intType;
2348 }
2349
2350 // We first convert LHS, then process RHS
2351 // While JLS doesn't require this, javac generates bytecode this way
2352 Value lhsConv = convert(lhs, opType);
2353 Value one = append(numericOneValue(opType));
2354
2355 Value lhsPlusOne = (tag == Tag.PREINC || tag == Tag.POSTINC) ?
2356 append(JavaOp.add(lhsConv, one)) :
2357 append(JavaOp.sub(lhsConv, one));
2358 lhsPlusOne = convert(lhsPlusOne, tree.type);
2359
2360 // Assign expression result
2361 result = (tag == Tag.POSTINC || tag == Tag.POSTDEC) ?
2362 lhs : lhsPlusOne;
2363 return lhsPlusOne;
2364 };
2365
2366 applyCompoundAssign(tree.arg, scanRhs);
2367 }
2368 case NEG -> {
2369 Value rhs = toValue(tree.arg, tree.type);
2370 result = append(JavaOp.neg(rhs));
2371 }
2372 case NOT -> {
2373 Value rhs = toValue(tree.arg, tree.type);
2374 result = append(JavaOp.not(rhs));
2375 }
2376 case COMPL -> {
2377 Value rhs = toValue(tree.arg, tree.type);
2378 result = append(JavaOp.compl(rhs));
2379 }
2380 case POS -> {
2381 // Result is value of the operand
2382 result = toValue(tree.arg, tree.type);
2383 }
2384 default -> throw unreachable(); // NULLCHK not possible
2385 }
2386 }
2387
2388 @Override
2389 public void visitBinary(JCBinary tree) {
2390 Tag tag = tree.getTag();
2391 if (tag == Tag.AND || tag == Tag.OR) {
2392 // Logical operations
2393 // @@@ Flatten nested sequences
2394
2395 // Push lhs
2396 pushBody(tree.lhs, CoreType.functionType(JavaType.BOOLEAN));
2397 Value lhs = toValue(tree.lhs, syms.booleanType);
2398 // Yield the boolean result of the condition
2399 append(CoreOp.core_yield(lhs));
2400 Body.Builder bodyLhs = stack.body;
2401
2402 // Pop lhs
2403 popBody();
2404
2405 // Push rhs
2406 pushBody(tree.rhs, CoreType.functionType(JavaType.BOOLEAN));
2407 Value rhs = toValue(tree.rhs, syms.booleanType);
2408 // Yield the boolean result of the condition
2409 append(CoreOp.core_yield(rhs));
2410 Body.Builder bodyRhs = stack.body;
2411
2412 // Pop lhs
2413 popBody();
2414
2415 List<Body.Builder> bodies = List.of(bodyLhs, bodyRhs);
2416 result = append(tag == Tag.AND
2417 ? JavaOp.conditionalAnd(bodies)
2418 : JavaOp.conditionalOr(bodies));
2419 } else if (tag == Tag.PLUS && tree.operator.opcode == ByteCodes.string_add) {
2420 //Ignore the operator and query both subexpressions for their type with concats
2421 Type lhsType = tree.lhs.type;
2422 Type rhsType = tree.rhs.type;
2423
2424 Value lhs = toValue(tree.lhs, lhsType.hasTag(BOT) ? syms.stringType : lhsType);
2425 Value rhs = toValue(tree.rhs, rhsType.hasTag(BOT) ? syms.stringType : rhsType);
2426
2427 result = append(JavaOp.concat(lhs, rhs));
2428 }
2429 else {
2430 Type lhsType = tree.operator.type.getParameterTypes().head;
2431 Type rhsType = tree.operator.type.getParameterTypes().tail.head;
2432 Value lhs = toValue(tree.lhs, lhsType);
2433 Value rhs = toValue(tree.rhs, rhsType);
2434
2435 result = switch (tag) {
2436 // Arithmetic operations
2437 case PLUS -> append(JavaOp.add(lhs, rhs));
2438 case MINUS -> append(JavaOp.sub(lhs, rhs));
2439 case MUL -> append(JavaOp.mul(lhs, rhs));
2440 case DIV -> append(JavaOp.div(lhs, rhs));
2441 case MOD -> append(JavaOp.mod(lhs, rhs));
2442
2443 // Test operations
2444 case EQ -> append(JavaOp.eq(lhs, rhs));
2445 case NE -> append(JavaOp.neq(lhs, rhs));
2446 //
2447 case LT -> append(JavaOp.lt(lhs, rhs));
2448 case LE -> append(JavaOp.le(lhs, rhs));
2449 case GT -> append(JavaOp.gt(lhs, rhs));
2450 case GE -> append(JavaOp.ge(lhs, rhs));
2451
2452 // Bitwise operations (including their boolean variants)
2453 case BITOR -> append(JavaOp.or(lhs, rhs));
2454 case BITAND -> append(JavaOp.and(lhs, rhs));
2455 case BITXOR -> append(JavaOp.xor(lhs, rhs));
2456
2457 // Shift operations
2458 case SL -> append(JavaOp.lshl(lhs, rhs));
2459 case SR -> append(JavaOp.ashr(lhs, rhs));
2460 case USR -> append(JavaOp.lshr(lhs, rhs));
2461
2462 default -> throw unreachable();
2463 };
2464 }
2465 }
2466
2467 @Override
2468 public void visitLiteral(JCLiteral tree) {
2469 Object value = switch (tree.type.getTag()) {
2470 case BOOLEAN -> tree.value instanceof Integer i && i == 1;
2471 case CHAR -> (char) (int) tree.value;
2472 default -> tree.value;
2473 };
2474 Type constantType = adaptBottom(tree.type);
2475 result = append(CoreOp.constant(typeToCodeType(constantType), value));
2476 }
2477
2478 @Override
2479 public void visitReturn(JCReturn tree) {
2480 Value retVal = toValue(tree.expr, bodyTarget);
2481 if (retVal == null) {
2482 result = append(CoreOp.return_());
2483 } else {
2484 result = append(CoreOp.return_(retVal));
2485 }
2486 }
2487
2488 @Override
2489 public void visitThrow(JCTree.JCThrow tree) {
2490 Value throwVal = toValue(tree.expr);
2491 result = append(JavaOp.throw_(throwVal));
2492 }
2493
2494 @Override
2495 public void visitBreak(JCTree.JCBreak tree) {
2496 Value label = tree.label != null
2497 ? getLabel(tree.label.toString())
2498 : null;
2499 result = append(JavaOp.break_(label));
2500 }
2501
2502 @Override
2503 public void visitContinue(JCTree.JCContinue tree) {
2504 Value label = tree.label != null
2505 ? getLabel(tree.label.toString())
2506 : null;
2507 result = append(JavaOp.continue_(label));
2508 }
2509
2510 @Override
2511 public void visitClassDef(JCClassDecl tree) {
2512 computeCapturesIfNeeded(tree);
2513 }
2514
2515 AssertionError unreachable() {
2516 return new AssertionError("Should not reach here!");
2517 }
2518
2519 CoreOp.FuncOp scanMethod(JCBlock body) {
2520 scan(body, ReflectMethods.this.currentNode());
2521 appendReturnOrUnreachable(body);
2522 MethodRef sourceRef = symbolToMethodRef(((JCMethodDecl) tree).sym);
2523 CoreOp.FuncOp func = CoreOp.func(sourceRef, stack.body);
2524 func.setLocation(generateLocation(tree, true));
2525 return func;
2526 }
2527
2528 CoreOp.FuncOp scanMethod() {
2529 return scanMethod(((JCMethodDecl)tree).body);
2530 }
2531
2532 CoreOp.FuncOp scanLambda() {
2533 scan(tree, ReflectMethods.this.prevNode());
2534 // Return the quoted result
2535 append(CoreOp.return_(result));
2536 return CoreOp.func(name.toString(), stack.body);
2537 }
2538
2539 Op defaultValue(Type t) {
2540 return switch (t.getTag()) {
2541 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 0);
2542 case CHAR -> CoreOp.constant(typeToCodeType(t), (char)0);
2543 case BOOLEAN -> CoreOp.constant(typeToCodeType(t), false);
2544 case FLOAT -> CoreOp.constant(typeToCodeType(t), 0f);
2545 case LONG -> CoreOp.constant(typeToCodeType(t), 0L);
2546 case DOUBLE -> CoreOp.constant(typeToCodeType(t), 0d);
2547 default -> CoreOp.constant(typeToCodeType(t), null);
2548 };
2549 }
2550
2551 Op numericOneValue(Type t) {
2552 return switch (t.getTag()) {
2553 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 1);
2554 case CHAR -> CoreOp.constant(typeToCodeType(t), (char)1);
2555 case FLOAT -> CoreOp.constant(typeToCodeType(t), 1f);
2556 case LONG -> CoreOp.constant(typeToCodeType(t), 1L);
2557 case DOUBLE -> CoreOp.constant(typeToCodeType(t), 1d);
2558 default -> throw new UnsupportedOperationException(t.toString());
2559 };
2560 }
2561 }
2562
2563 boolean isReflectable(JCMethodDecl tree) {
2564 return codeReflectionEnabled ||
2565 (tree.body != null &&
2566 (reflectAll || tree.sym.attribute(crSyms.codeReflectionType.tsym) != null));
2567 }
2568
2569 boolean isReflectable(JCFunctionalExpression expr) {
2570 return reflectAll || codeReflectionEnabled ||
2571 (prevNode() instanceof JCTypeCast castTree && isReflectable(castTree.clazz.type));
2572 }
2573
2574 boolean isReflectable(Type target) {
2575 if (target.isCompound()) {
2576 return ((IntersectionClassType)target).getComponents().stream()
2577 .anyMatch(this::isReflectable);
2578 } else {
2579 return target.getAnnotationMirrors().stream()
2580 .anyMatch(tc -> tc.type.tsym == crSyms.codeReflectionType.tsym);
2581 }
2582 }
2583
2584 /*
2585 * Converts a method reference which cannot be used directly into a lambda.
2586 * This code has been derived from LambdaToMethod::MemberReferenceToLambda. The main
2587 * difference is that, while that code concerns with translation strategy, boxing
2588 * conversion and type erasure, this version does not and, as such, can remain
2589 * at a higher level. Note that this code needs to create a synthetic variable
2590 * declaration in case of a bounded method reference whose receiver expression
2591 * is other than 'this'/'super' (this is done to prevent the receiver expression
2592 * from being computed twice).
2593 */
2594 private class MemberReferenceToLambda {
2595
2596 private final JCMemberReference tree;
2597 private final Symbol owner;
2598 private final ListBuffer<JCExpression> args = new ListBuffer<>();
2599 private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
2600 private JCVariableDecl receiverVar = null;
2601
2602 MemberReferenceToLambda(JCMemberReference tree, Symbol currentClass) {
2603 this.tree = tree;
2604 this.owner = new MethodSymbol(0, names.lambda, tree.target, currentClass);
2605 if (tree.kind == ReferenceKind.BOUND && !TreeInfo.isThisQualifier(tree.getQualifierExpression())) {
2606 // true bound method reference, hoist receiver expression out
2607 Type recvType = types.asSuper(tree.getQualifierExpression().type, tree.sym.owner);
2608 VarSymbol vsym = makeSyntheticVar("rec$", recvType);
2609 receiverVar = make.VarDef(vsym, tree.getQualifierExpression());
2610 }
2611 }
2612
2613 JCVariableDecl receiverVar() {
2614 return receiverVar;
2615 }
2616
2617 JCLambda lambda() {
2618 int prevPos = make.pos;
2619 try {
2620 make.at(tree);
2621
2622 //body generation - this can be either a method call or a
2623 //new instance creation expression, depending on the member reference kind
2624 VarSymbol rcvr = addParametersReturnReceiver();
2625 JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
2626 ? expressionInvoke(rcvr)
2627 : expressionNew();
2628
2629 JCLambda slam = make.Lambda(params.toList(), expr);
2630 slam.target = tree.target;
2631 slam.type = tree.type;
2632 slam.pos = tree.pos;
2633 return slam;
2634 } finally {
2635 make.at(prevPos);
2636 }
2637 }
2638
2639 /**
2640 * Generate the parameter list for the converted member reference.
2641 *
2642 * @return The receiver variable symbol, if any
2643 */
2644 VarSymbol addParametersReturnReceiver() {
2645 com.sun.tools.javac.util.List<Type> descPTypes = tree.getDescriptorType(types).getParameterTypes();
2646 VarSymbol receiverParam = null;
2647 switch (tree.kind) {
2648 case BOUND:
2649 if (receiverVar != null) {
2650 receiverParam = receiverVar.sym;
2651 }
2652 break;
2653 case UNBOUND:
2654 // The receiver is the first parameter, extract it and
2655 // adjust the SAM and unerased type lists accordingly
2656 receiverParam = addParameter("rec$", descPTypes.head, false);
2657 descPTypes = descPTypes.tail;
2658 break;
2659 }
2660 for (int i = 0; descPTypes.nonEmpty(); ++i) {
2661 // By default use the implementation method parameter type
2662 Type parmType = descPTypes.head;
2663 addParameter("x$" + i, parmType, true);
2664
2665 // Advance to the next parameter
2666 descPTypes = descPTypes.tail;
2667 }
2668
2669 return receiverParam;
2670 }
2671
2672 /**
2673 * determine the receiver of the method call - the receiver can
2674 * be a type qualifier, the synthetic receiver parameter or 'super'.
2675 */
2676 private JCExpression expressionInvoke(VarSymbol receiverParam) {
2677 JCExpression qualifier = receiverParam != null ?
2678 make.at(tree.pos).Ident(receiverParam) :
2679 tree.getQualifierExpression();
2680
2681 //create the qualifier expression
2682 JCFieldAccess select = make.Select(qualifier, tree.sym.name);
2683 select.sym = tree.sym;
2684 select.type = tree.referentType;
2685
2686 //create the method call expression
2687 JCMethodInvocation apply = make.Apply(com.sun.tools.javac.util.List.nil(), select, args.toList()).
2688 setType(tree.referentType.getReturnType());
2689
2690 apply.varargsElement = tree.varargsElement;
2691 return apply;
2692 }
2693
2694 /**
2695 * Lambda body to use for a 'new'.
2696 */
2697 private JCExpression expressionNew() {
2698 Type expectedType = tree.referentType.getReturnType().hasTag(TypeTag.VOID) ?
2699 tree.expr.type : tree.referentType.getReturnType();
2700 if (tree.kind == ReferenceKind.ARRAY_CTOR) {
2701 //create the array creation expression
2702 JCNewArray newArr = make.NewArray(
2703 make.Type(types.elemtype(expectedType)),
2704 com.sun.tools.javac.util.List.of(make.Ident(params.first())),
2705 null);
2706 newArr.type = tree.getQualifierExpression().type;
2707 return newArr;
2708 } else {
2709 //create the instance creation expression
2710 //note that method reference syntax does not allow an explicit
2711 //enclosing class (so the enclosing class is null)
2712 // but this may need to be patched up later with the proxy for the outer this
2713 JCExpression newType = make.Type(types.erasure(expectedType));
2714 if (expectedType.tsym.type.getTypeArguments().nonEmpty()) {
2715 newType = make.TypeApply(newType, com.sun.tools.javac.util.List.nil());
2716 }
2717 JCNewClass newClass = make.NewClass(null,
2718 com.sun.tools.javac.util.List.nil(),
2719 newType,
2720 args.toList(),
2721 null);
2722 newClass.constructor = tree.sym;
2723 newClass.constructorType = tree.referentType;
2724 newClass.type = expectedType;
2725 newClass.varargsElement = tree.varargsElement;
2726 return newClass;
2727 }
2728 }
2729
2730 private VarSymbol makeSyntheticVar(String name, Type type) {
2731 VarSymbol vsym = new VarSymbol(PARAMETER | SYNTHETIC, names.fromString(name), type, owner);
2732 vsym.pos = tree.pos;
2733 return vsym;
2734 }
2735
2736 private VarSymbol addParameter(String name, Type type, boolean genArg) {
2737 VarSymbol vsym = makeSyntheticVar(name, type);
2738 params.append(make.VarDef(vsym, null));
2739 if (genArg) {
2740 args.append(make.Ident(vsym));
2741 }
2742 return vsym;
2743 }
2744 }
2745
2746 static class JCReflectMethodsClassDecl extends JCClassDecl {
2747
2748 SequencedMap<String, Op> ops;
2749
2750 JCReflectMethodsClassDecl(JCClassDecl cls, SequencedMap<String, Op> ops) {
2751 super(cls.mods, cls.name, cls.typarams, cls.extending, cls.implementing, cls.permitting, cls.defs, cls.sym);
2752 this.pos = cls.pos;
2753 this.type = cls.type;
2754 this.ops = ops;
2755 }
2756 }
2757
2758 public static class Provider implements CodeReflectionTransformer {
2759 @Override
2760 public JCTree translateTopLevelClass(Context context, JCTree tree, TreeMaker make) {
2761 return ReflectMethods.instance(context).translateTopLevelClass(tree, make);
2762 }
2763
2764 @Override
2765 public void genCode(Context context, JCClassDecl cdef) throws IOException {
2766 if (cdef instanceof JCReflectMethodsClassDecl rmcdef) {
2767 JavaFileManager fileManager = context.get(JavaFileManager.class);
2768 JavaFileManager.Location outLocn;
2769 if (fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
2770 outLocn = fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT, cdef.sym.packge().modle.name.toString());
2771 } else {
2772 outLocn = StandardLocation.CLASS_OUTPUT;
2773 }
2774 String className = cdef.sym.flatName().toString() + "$$CM";
2775 ClassDesc classDesc = ClassDesc.of(className);
2776 JavaFileObject outFile = fileManager.getJavaFileForOutput(outLocn, className, JavaFileObject.Kind.CLASS, cdef.sym.sourcefile);
2777 ClassDesc hostClass = ClassDesc.of(cdef.sym.flatName().toString());
2778
2779 CoreOp.ModuleOp module = OpBuilder.createBuilderFunctions(
2780 rmcdef.ops,
2781 b -> b.add(JavaOp.fieldLoad(
2782 FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))));
2783 byte[] data = BytecodeGenerator.generateClassData(MethodHandles.lookup(), classDesc, module);
2784 // inject InnerClassesAttribute and NestHostAttribute
2785 var clm = ClassFile.of().parse(data);
2786 data = ClassFile.of().transformClass(clm, ClassTransform.endHandler(clb ->
2787 clb.with(InnerClassesAttribute.of(InnerClassInfo.of(classDesc, Optional.of(hostClass), Optional.of("$CM"), ClassFile.ACC_STATIC)))
2788 .with(NestHostAttribute.of(hostClass))));
2789 try (OutputStream out = outFile.openOutputStream()) {
2790 out.write(data);
2791 }
2792 }
2793 }
2794 }
2795
2796 // type and ref conversion utils
2797
2798 JavaType symbolToErasedDesc(Symbol s) {
2799 return typeToCodeType(s.erasure(types));
2800 }
2801
2802 JavaType typeToCodeType(Type t) {
2803 Assert.check(!t.hasTag(METHOD));
2804 t = asDenotable(t);
2805 return switch (t.getTag()) {
2806 case VOID -> JavaType.VOID;
2807 case CHAR -> JavaType.CHAR;
2808 case BOOLEAN -> JavaType.BOOLEAN;
2809 case BYTE -> JavaType.BYTE;
2810 case SHORT -> JavaType.SHORT;
2811 case INT -> JavaType.INT;
2812 case FLOAT -> JavaType.FLOAT;
2813 case LONG -> JavaType.LONG;
2814 case DOUBLE -> JavaType.DOUBLE;
2815 case ARRAY -> {
2816 Type et = ((ArrayType)t).elemtype;
2817 yield JavaType.array(typeToCodeType(et));
2818 }
2819 case WILDCARD -> {
2820 Type.WildcardType wt = (Type.WildcardType)t;
2821 yield wt.isUnbound() ?
2822 JavaType.wildcard() :
2823 JavaType.wildcard(wt.isExtendsBound() ? BoundKind.EXTENDS : BoundKind.SUPER, typeToCodeType(wt.type));
2824 }
2825 case TYPEVAR -> {
2826 Type ub = t.getUpperBound();
2827 if (ub.contains(t)) {
2828 // @@@ stop infinite recursion, ex: <E extends Enum<E>>
2829 ub = types.erasure(ub);
2830 }
2831 yield t.tsym.owner.kind == Kind.MTH ?
2832 JavaType.typeVar(t.tsym.name.toString(), symbolToErasedMethodRef(t.tsym.owner),
2833 typeToCodeType(ub)) :
2834 JavaType.typeVar(t.tsym.name.toString(),
2835 (jdk.incubator.code.dialect.java.ClassType)symbolToErasedDesc(t.tsym.owner),
2836 typeToCodeType(ub));
2837 }
2838 case CLASS -> {
2839 Assert.check(!t.isIntersection() && !t.isUnion());
2840 JavaType typ;
2841 if (t.getEnclosingType() != Type.noType) {
2842 Name innerName = t.tsym.flatName().subName(t.getEnclosingType().tsym.flatName().length() + 1);
2843 typ = JavaType.qualified(typeToCodeType(t.getEnclosingType()), innerName.toString());
2844 } else {
2845 typ = JavaType.type(ClassDesc.of(t.tsym.flatName().toString()));
2846 }
2847
2848 List<JavaType> typeArguments;
2849 if (t.getTypeArguments().nonEmpty()) {
2850 typeArguments = new ArrayList<>();
2851 for (Type ta : t.getTypeArguments()) {
2852 typeArguments.add(typeToCodeType(ta));
2853 }
2854 } else {
2855 typeArguments = List.of();
2856 }
2857
2858 // Use flat name to ensure demarcation of nested classes
2859 yield JavaType.parameterized(typ, typeArguments);
2860 }
2861 default -> throw new UnsupportedOperationException("Unsupported type: kind=" + t.getKind() + " type=" + t);
2862 };
2863 }
2864
2865 Type codeTypeToType(CodeType jt) {
2866 return switch (jt) {
2867 case PrimitiveType pt when pt == JavaType.BOOLEAN -> syms.booleanType;
2868 case PrimitiveType pt when pt == JavaType.CHAR -> syms.charType;
2869 case PrimitiveType pt when pt == JavaType.BYTE -> syms.byteType;
2870 case PrimitiveType pt when pt == JavaType.SHORT -> syms.shortType;
2871 case PrimitiveType pt when pt == JavaType.INT -> syms.intType;
2872 case PrimitiveType pt when pt == JavaType.LONG -> syms.longType;
2873 case PrimitiveType pt when pt == JavaType.FLOAT -> syms.floatType;
2874 case PrimitiveType pt when pt == JavaType.DOUBLE -> syms.doubleType;
2875 case ClassType ct when ct.hasTypeArguments() -> {
2876 Type enclosing = ct.enclosingType().map(this::codeTypeToType).orElse(Type.noType);
2877 com.sun.tools.javac.util.List<Type> typeArgs = com.sun.tools.javac.util.List.from(ct.typeArguments()).map(this::codeTypeToType);
2878 yield new Type.ClassType(enclosing, typeArgs, codeTypeToType(ct.rawType()).tsym);
2879 }
2880 case ClassType ct -> types.erasure(syms.enterClass(attrEnv().toplevel.modle, names.fromString(ct.toClassName())).type);
2881 case jdk.incubator.code.dialect.java.ArrayType at -> new Type.ArrayType(codeTypeToType(at.componentType()), syms.arrayClass);
2882 default -> Type.noType;
2883 };
2884 }
2885
2886 Type symbolSiteType(Symbol s) {
2887 boolean isMember = s.owner == syms.predefClass ||
2888 s.isMemberOf(currentClassSym, types);
2889 return isMember ? currentClassSym.type : s.owner.type;
2890 }
2891
2892 FieldRef symbolToErasedFieldRef(Symbol s, Type site) {
2893 // @@@ Made Gen::binaryQualifier public, duplicate logic?
2894 // Ensure correct qualifying class is used in the reference, see JLS 13.1
2895 // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2896 return symbolErasedFieldRef(gen.binaryQualifier(s, types.erasure(site)));
2897 }
2898
2899 FieldRef symbolErasedFieldRef(Symbol s) {
2900 Type erasedType = s.erasure(types);
2901 return FieldRef.field(
2902 typeToCodeType(s.owner.erasure(types)),
2903 s.name.toString(),
2904 typeToCodeType(erasedType));
2905 }
2906
2907 MethodRef symbolToErasedMethodRef(Symbol s, Type site) {
2908 // @@@ Made Gen::binaryQualifier public, duplicate logic?
2909 // Ensure correct qualifying class is used in the reference, see JLS 13.1
2910 // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2911 return symbolToErasedMethodRef(gen.binaryQualifier(s, types.erasure(site)));
2912 }
2913
2914 MethodRef symbolToErasedMethodRef(Symbol s) {
2915 Type erasedType = s.erasure(types);
2916 return MethodRef.method(
2917 typeToCodeType(s.owner.erasure(types)),
2918 s.name.toString(),
2919 typeToCodeType(erasedType.getReturnType()),
2920 erasedType.getParameterTypes().stream().map(this::typeToCodeType).toArray(CodeType[]::new));
2921 }
2922
2923 MethodRef symbolToMethodRef(Symbol sym) {
2924 return MethodRef.method(typeToCodeType(sym.owner.type),
2925 sym.name.toString(),
2926 typeToCodeType(sym.type.getReturnType()),
2927 sym.type.getParameterTypes().stream().map(ReflectMethods.this::typeToCodeType).toList());
2928 }
2929
2930 FunctionType typeToFunctionType(Type t) {
2931 return CoreType.functionType(
2932 typeToCodeType(t.getReturnType()),
2933 t.getParameterTypes().stream().map(this::typeToCodeType).toArray(CodeType[]::new));
2934 }
2935
2936 RecordTypeRef symbolToRecordTypeRef(Symbol.ClassSymbol s) {
2937 CodeType recordType = typeToCodeType(s.type);
2938 List<RecordTypeRef.ComponentRef> components = s.getRecordComponents().stream()
2939 .map(rc -> new RecordTypeRef.ComponentRef(typeToCodeType(rc.type), rc.name.toString()))
2940 .toList();
2941 return RecordTypeRef.recordType(recordType, components);
2942 }
2943
2944 Env<AttrContext> attrEnv() {
2945 return typeEnvs.get(currentClassSym);
2946 }
2947
2948 Type asDenotable(Type t) {
2949 // The goal of this type mapping is to replace occurrences of intersection and union types
2950 // with fresh type variables with appropriate upper bounds. For instance, consider the generic type
2951 // Foo<A & B & C>. We need to:
2952 // 1. replace A & B & C with a fresh type variable, so this becomes Foo<#1>, #1 <: A
2953 // 2. add #1 to the set of type-variables to be projected
2954 // 3. run upward projection on Foo<#1>, which gives Foo<? extends A>
2955 // In other words, by replacing intersection types with fresh type variables we make sure that the output
2956 // of this method is a type that is fully denotable -- e.g. can be fully represented in terms of the
2957 // CodeType API.
2958 class DenotableProjection extends StructuralTypeMapping<Void> {
2959 final ListBuffer<Type> tvars = new ListBuffer<>();
2960 final Map<CapturedType, CapturedType> pendingCaptures = new HashMap<>();
2961 final Type t;
2962
2963 DenotableProjection(Type t) {
2964 tvars.appendList(types.captures(t));
2965 this.t = t;
2966 }
2967
2968 Type asDenotable() {
2969 return types.upward(apply(t), tvars.toList());
2970 }
2971
2972 @Override
2973 public Type visitCapturedType(CapturedType t, Void _unused) {
2974 CapturedType newVar = pendingCaptures.get(t);
2975 if (newVar == null) {
2976 newVar = addTypeVar(t.getUpperBound(), t.getLowerBound(), t.tsym.owner);
2977 try {
2978 pendingCaptures.put(t, newVar);
2979 newVar.setUpperBound(apply(newVar.getUpperBound()));
2980 } finally {
2981 pendingCaptures.remove(t);
2982 }
2983 newVar.lower = apply(newVar.lower);
2984 }
2985 return newVar;
2986 }
2987
2988 @Override
2989 public Type visitClassType(Type.ClassType t, Void unused) {
2990 if (t.isIntersection()) {
2991 Type bound = visit(((IntersectionClassType) t).getExplicitComponents().head, null);
2992 return addTypeVar(bound, syms.botType, t.tsym);
2993 } else if (t.isUnion()) {
2994 Type bound = visit(((UnionClassType)t).getLub(), null);
2995 return addTypeVar(bound, syms.botType, t.tsym);
2996 } else {
2997 return super.visitClassType(t, null);
2998 }
2999 }
3000
3001 CapturedType addTypeVar(Type upper, Type lower, Symbol owner) {
3002 CapturedType newTvar = new CapturedType(names.empty, owner, upper, lower, null);
3003 tvars.append(newTvar);
3004 return newTvar;
3005 }
3006 }
3007
3008 return new DenotableProjection(t).asDenotable();
3009 }
3010 }