/* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.jvm; import com.sun.tools.javac.code.*; import com.sun.tools.javac.comp.Resolve; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.*; import static com.sun.tools.javac.code.Kinds.Kind.MTH; import static com.sun.tools.javac.code.TypeTag.*; import static com.sun.tools.javac.jvm.ByteCodes.*; import static com.sun.tools.javac.tree.JCTree.Tag.PLUS; import com.sun.tools.javac.jvm.Items.*; import java.util.HashMap; import java.util.Map; /** This lowers the String concatenation to something that JVM can understand. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public abstract class StringConcat { /** * Maximum number of slots for String Concat call. * JDK's StringConcatFactory does not support more than that. */ private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; private static final char TAG_ARG = '\u0001'; private static final char TAG_CONST = '\u0002'; protected final Gen gen; protected final Symtab syms; protected final Names names; protected final TreeMaker make; protected final Types types; protected final Map<Type, Symbol> sbAppends; protected final Resolve rs; protected static final Context.Key<StringConcat> concatKey = new Context.Key<>(); public static StringConcat instance(Context context) { StringConcat instance = context.get(concatKey); if (instance == null) { instance = makeConcat(context); } return instance; } private static StringConcat makeConcat(Context context) { Target target = Target.instance(context); String opt = Options.instance(context).get("stringConcat"); if (target.hasStringConcatFactory()) { if (opt == null) { opt = "indyWithConstants"; } } else { if (opt != null && !"inline".equals(opt)) { Assert.error("StringConcatFactory-based string concat is requested on a platform that does not support it."); } opt = "inline"; } switch (opt) { case "inline": return new Inline(context); case "indy": return new IndyPlain(context); case "indyWithConstants": return new IndyConstants(context); default: Assert.error("Unknown stringConcat: " + opt); throw new IllegalStateException("Unknown stringConcat: " + opt); } } protected StringConcat(Context context) { context.put(concatKey, this); gen = Gen.instance(context); syms = Symtab.instance(context); types = Types.instance(context); names = Names.instance(context); make = TreeMaker.instance(context); rs = Resolve.instance(context); sbAppends = new HashMap<>(); } public abstract Item makeConcat(JCTree.JCAssignOp tree); public abstract Item makeConcat(JCTree.JCBinary tree); protected List<JCTree> collectAll(JCTree tree) { return collect(tree, List.nil()); } protected List<JCTree> collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs) { return List.<JCTree>nil() .appendList(collectAll(lhs)) .appendList(collectAll(rhs)); } private List<JCTree> collect(JCTree tree, List<JCTree> res) { tree = TreeInfo.skipParens(tree); if (tree.hasTag(PLUS) && tree.type.constValue() == null) { JCTree.JCBinary op = (JCTree.JCBinary) tree; if (op.operator.kind == MTH && op.operator.opcode == string_add) { return res .appendList(collect(op.lhs, res)) .appendList(collect(op.rhs, res)); } } return res.append(tree); } /** * If the type is not accessible from current context, try to figure out the * sharpest accessible supertype. * * @param originalType type to sharpen * @return sharped type */ Type sharpestAccessible(Type originalType) { if (originalType.hasTag(ARRAY)) { return types.makeArrayType(sharpestAccessible(types.elemtype(originalType))); } Type type = originalType; while (!rs.isAccessible(gen.getAttrEnv(), type.asElement())) { type = types.supertype(type); } return type; } /** * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string * concatenation. */ private static class Inline extends StringConcat { public Inline(Context context) { super(context); } @Override public Item makeConcat(JCTree.JCAssignOp tree) { // Generate code to make a string builder JCDiagnostic.DiagnosticPosition pos = tree.pos(); // Create a string builder. newStringBuilder(tree); // Generate code for first string, possibly save one // copy under builder Item l = gen.genExpr(tree.lhs, tree.lhs.type); if (l.width() > 0) { gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1)); } // Load first string and append to builder. l.load(); appendString(tree.lhs); // Append all other strings to builder. List<JCTree> args = collectAll(tree.rhs); for (JCTree t : args) { gen.genExpr(t, t.type).load(); appendString(t); } // Convert builder to string. builderToString(pos); return l; } @Override public Item makeConcat(JCTree.JCBinary tree) { JCDiagnostic.DiagnosticPosition pos = tree.pos(); // Create a string builder. newStringBuilder(tree); // Append all strings to builder. List<JCTree> args = collectAll(tree); for (JCTree t : args) { gen.genExpr(t, t.type).load(); appendString(t); } // Convert builder to string. builderToString(pos); return gen.getItems().makeStackItem(syms.stringType); } private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) { JCDiagnostic.DiagnosticPosition pos = tree.pos(); gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType)); gen.getCode().emitop0(dup); gen.callMethod(pos, syms.stringBuilderType, names.init, List.<Type>nil(), false); return pos; } private void appendString(JCTree tree) { Type t = tree.type.baseType(); if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { t = syms.objectType; } Assert.checkNull(t.constValue()); Symbol method = sbAppends.get(t); if (method == null) { method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null); sbAppends.put(t, method); } gen.getItems().makeMemberItem(method, false).invoke(); } private void builderToString(JCDiagnostic.DiagnosticPosition pos) { gen.callMethod(pos, syms.stringBuilderType, names.toString, List.<Type>nil(), false); } } /** * Base class for indified concatenation bytecode flavors. */ private static abstract class Indy extends StringConcat { public Indy(Context context) { super(context); } @Override public Item makeConcat(JCTree.JCAssignOp tree) { List<JCTree> args = collectAll(tree.lhs, tree.rhs); Item l = gen.genExpr(tree.lhs, tree.lhs.type); emit(args, tree.type, tree.pos()); return l; } @Override public Item makeConcat(JCTree.JCBinary tree) { List<JCTree> args = collectAll(tree.lhs, tree.rhs); emit(args, tree.type, tree.pos()); return gen.getItems().makeStackItem(syms.stringType); } protected abstract void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos); /** Peel the argument list into smaller chunks. */ protected List<List<JCTree>> split(List<JCTree> args) { ListBuffer<List<JCTree>> splits = new ListBuffer<>(); int slots = 0; // Need to peel, so that neither call has more than acceptable number // of slots for the arguments. ListBuffer<JCTree> cArgs = new ListBuffer<>(); for (JCTree t : args) { int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1; if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) { splits.add(cArgs.toList()); cArgs.clear(); slots = 0; } cArgs.add(t); slots += needSlots; } // Flush the tail slice if (!cArgs.isEmpty()) { splits.add(cArgs.toList()); } return splits.toList(); } } /** * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory, * without handling constants specially. * * We bypass empty strings, because they have no meaning at this level. This * captures the Java language trick to force String concat with e.g. ("" + int)-like * expression. Down here, we already know we are in String concat business, and do * not require these markers. */ private static class IndyPlain extends Indy { public IndyPlain(Context context) { super(context); } /** Emit the indy concat for all these arguments, possibly peeling along the way */ protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { List<List<JCTree>> split = split(args); for (List<JCTree> t : split) { Assert.check(!t.isEmpty(), "Arguments list is empty"); ListBuffer<Type> dynamicArgs = new ListBuffer<>(); for (JCTree arg : t) { Object constVal = arg.type.constValue(); if ("".equals(constVal)) continue; if (arg.type == syms.botType) { dynamicArgs.add(types.boxedClass(syms.voidType).type); } else { dynamicArgs.add(sharpestAccessible(arg.type)); } gen.genExpr(arg, arg.type).load(); } doCall(type, pos, dynamicArgs.toList()); } // More that one peel slice produced: concatenate the results if (split.size() > 1) { ListBuffer<Type> argTypes = new ListBuffer<>(); for (int c = 0; c < split.size(); c++) { argTypes.append(syms.stringType); } doCall(type, pos, argTypes.toList()); } } /** Produce the actual invokedynamic call to StringConcatFactory */ private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List<Type> dynamicArgTypes) { Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, type, List.<Type>nil(), syms.methodClass); int prevPos = make.pos; try { make.at(pos); List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, syms.stringType, syms.methodTypeType); Symbol bsm = rs.resolveInternalMethod(pos, gen.getAttrEnv(), syms.stringConcatFactory, names.makeConcat, bsm_staticArgs, null); Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcat, syms.noSymbol, ClassFile.REF_invokeStatic, (Symbol.MethodSymbol)bsm, indyType, List.nil().toArray()); Items.Item item = gen.getItems().makeDynamicItem(dynSym); item.invoke(); } finally { make.at(prevPos); } } } /** * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory. * This code concatenates all known constants into the recipe, possibly escaping * some constants separately. * * We also bypass empty strings, because they have no meaning at this level. This * captures the Java language trick to force String concat with e.g. ("" + int)-like * expression. Down here, we already know we are in String concat business, and do * not require these markers. */ private static final class IndyConstants extends Indy { public IndyConstants(Context context) { super(context); } @Override protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { List<List<JCTree>> split = split(args); for (List<JCTree> t : split) { Assert.check(!t.isEmpty(), "Arguments list is empty"); StringBuilder recipe = new StringBuilder(t.size()); ListBuffer<Type> dynamicArgs = new ListBuffer<>(); ListBuffer<Object> staticArgs = new ListBuffer<>(); for (JCTree arg : t) { Object constVal = arg.type.constValue(); if ("".equals(constVal)) continue; if (arg.type == syms.botType) { // Concat the null into the recipe right away recipe.append((String) null); } else if (constVal != null) { // Concat the String representation of the constant, except // for the case it contains special tags, which requires us // to expose it as detached constant. String a = arg.type.stringValue(); if (a.indexOf(TAG_CONST) != -1 || a.indexOf(TAG_ARG) != -1) { recipe.append(TAG_CONST); staticArgs.add(a); } else { recipe.append(a); } } else { // Ordinary arguments come through the dynamic arguments. recipe.append(TAG_ARG); dynamicArgs.add(sharpestAccessible(arg.type)); gen.genExpr(arg, arg.type).load(); } } doCall(type, pos, recipe.toString(), staticArgs.toList(), dynamicArgs.toList()); } // More that one peel slice produced: concatenate the results // All arguments are assumed to be non-constant Strings. if (split.size() > 1) { ListBuffer<Type> argTypes = new ListBuffer<>(); StringBuilder recipe = new StringBuilder(); for (int c = 0; c < split.size(); c++) { argTypes.append(syms.stringType); recipe.append(TAG_ARG); } doCall(type, pos, recipe.toString(), List.nil(), argTypes.toList()); } } /** Produce the actual invokedynamic call to StringConcatFactory */ private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List<Object> staticArgs, List<Type> dynamicArgTypes) { Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, type, List.<Type>nil(), syms.methodClass); int prevPos = make.pos; try { make.at(pos); ListBuffer<Type> constTypes = new ListBuffer<>(); ListBuffer<Object> constants = new ListBuffer<>(); for (Object t : staticArgs) { constants.add(t); constTypes.add(syms.stringType); } List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, syms.stringType, syms.methodTypeType) .append(syms.stringType) .appendList(constTypes); Symbol bsm = rs.resolveInternalMethod(pos, gen.getAttrEnv(), syms.stringConcatFactory, names.makeConcatWithConstants, bsm_staticArgs, null); Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcatWithConstants, syms.noSymbol, ClassFile.REF_invokeStatic, (Symbol.MethodSymbol)bsm, indyType, List.<Object>of(recipe).appendList(constants).toArray()); Items.Item item = gen.getItems().makeDynamicItem(dynSym); item.invoke(); } finally { make.at(prevPos); } } } }