/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.painless.node; import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Operation; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; /** * Represents an assignment with the lhs and rhs as child nodes. */ public final class EAssignment extends AExpression { private AExpression lhs; private AExpression rhs; private final boolean pre; private final boolean post; private Operation operation; private boolean cat = false; private Type promote = null; private Type shiftDistance; // for shifts, the RHS is promoted independently private Cast there = null; private Cast back = null; public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean pre, boolean post, Operation operation) { super(location); this.lhs = Objects.requireNonNull(lhs); this.rhs = rhs; this.pre = pre; this.post = post; this.operation = operation; } @Override void extractVariables(Set<String> variables) { lhs.extractVariables(variables); rhs.extractVariables(variables); } @Override void analyze(Locals locals) { analyzeLHS(locals); analyzeIncrDecr(); if (operation != null) { analyzeCompound(locals); } else if (rhs != null) { analyzeSimple(locals); } else { throw new IllegalStateException("Illegal tree structure."); } } private void analyzeLHS(Locals locals) { if (lhs instanceof AStoreable) { AStoreable lhs = (AStoreable)this.lhs; lhs.read = read; lhs.write = true; lhs.analyze(locals); } else { throw new IllegalArgumentException("Left-hand side cannot be assigned a value."); } } private void analyzeIncrDecr() { if (pre && post) { throw createError(new IllegalStateException("Illegal tree structure.")); } else if (pre || post) { if (rhs != null) { throw createError(new IllegalStateException("Illegal tree structure.")); } Sort sort = lhs.actual.sort; if (operation == Operation.INCR) { if (sort == Sort.DOUBLE) { rhs = new EConstant(location, 1D); } else if (sort == Sort.FLOAT) { rhs = new EConstant(location, 1F); } else if (sort == Sort.LONG) { rhs = new EConstant(location, 1L); } else { rhs = new EConstant(location, 1); } operation = Operation.ADD; } else if (operation == Operation.DECR) { if (sort == Sort.DOUBLE) { rhs = new EConstant(location, 1D); } else if (sort == Sort.FLOAT) { rhs = new EConstant(location, 1F); } else if (sort == Sort.LONG) { rhs = new EConstant(location, 1L); } else { rhs = new EConstant(location, 1); } operation = Operation.SUB; } else { throw createError(new IllegalStateException("Illegal tree structure.")); } } } private void analyzeCompound(Locals locals) { rhs.analyze(locals); boolean shift = false; if (operation == Operation.MUL) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true); } else if (operation == Operation.DIV) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true); } else if (operation == Operation.REM) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true); } else if (operation == Operation.ADD) { promote = AnalyzerCaster.promoteAdd(lhs.actual, rhs.actual); } else if (operation == Operation.SUB) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true); } else if (operation == Operation.LSH) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, false); shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false); shift = true; } else if (operation == Operation.RSH) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, false); shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false); shift = true; } else if (operation == Operation.USH) { promote = AnalyzerCaster.promoteNumeric(lhs.actual, false); shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false); shift = true; } else if (operation == Operation.BWAND) { promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual); } else if (operation == Operation.XOR) { promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual); } else if (operation == Operation.BWOR) { promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual); } else { throw createError(new IllegalStateException("Illegal tree structure.")); } if (promote == null || (shift && shiftDistance == null)) { throw createError(new ClassCastException("Cannot apply compound assignment " + "[" + operation.symbol + "=] to types [" + lhs.actual + "] and [" + rhs.actual + "].")); } cat = operation == Operation.ADD && promote.sort == Sort.STRING; if (cat) { if (rhs instanceof EBinary && ((EBinary)rhs).operation == Operation.ADD && rhs.actual.sort == Sort.STRING) { ((EBinary)rhs).cat = true; } rhs.expected = rhs.actual; } else if (shift) { if (promote.sort == Sort.DEF) { // shifts are promoted independently, but for the def type, we need object. rhs.expected = promote; } else if (shiftDistance.sort == Sort.LONG) { rhs.expected = Definition.INT_TYPE; rhs.explicit = true; } else { rhs.expected = shiftDistance; } } else { rhs.expected = promote; } rhs = rhs.cast(locals); there = AnalyzerCaster.getLegalCast(location, lhs.actual, promote, false, false); back = AnalyzerCaster.getLegalCast(location, promote, lhs.actual, true, false); this.statement = true; this.actual = read ? lhs.actual : Definition.VOID_TYPE; } private void analyzeSimple(Locals locals) { AStoreable lhs = (AStoreable)this.lhs; // If the lhs node is a def optimized node we update the actual type to remove the need for a cast. if (lhs.isDefOptimized()) { rhs.analyze(locals); rhs.expected = rhs.actual; lhs.updateActual(rhs.actual); // Otherwise, we must adapt the rhs type to the lhs type with a cast. } else { rhs.expected = lhs.actual; rhs.analyze(locals); } rhs = rhs.cast(locals); this.statement = true; this.actual = read ? lhs.actual : Definition.VOID_TYPE; } /** * Handles writing byte code for variable/method chains for all given possibilities * including String concatenation, compound assignment, regular assignment, and simple * reads. Includes proper duplication for chained assignments and assignments that are * also read from. */ @Override void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); // For the case where the assignment represents a String concatenation // we must, depending on the Java version, write a StringBuilder or // track types going onto the stack. This must be done before the // lhs is read because we need the StringBuilder to be placed on the // stack ahead of any potential concatenation arguments. int catElementStackSize = 0; if (cat) { catElementStackSize = writer.writeNewStrings(); } // Cast the lhs to a storeable to perform the necessary operations to store the rhs. AStoreable lhs = (AStoreable)this.lhs; lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation if (cat) { // Handle the case where we are doing a compound assignment // representing a String concatenation. writer.writeDup(lhs.accessElementCount(), catElementStackSize); // dup the top element and insert it // before concat helper on stack lhs.load(writer, globals); // read the current lhs's value writer.writeAppendStrings(lhs.actual); // append the lhs's value using the StringBuilder rhs.write(writer, globals); // write the bytecode for the rhs if (!(rhs instanceof EBinary) || !((EBinary)rhs).cat) { // check to see if the rhs has already done a concatenation writer.writeAppendStrings(rhs.actual); // append the rhs's value since it's hasn't already } writer.writeToStrings(); // put the value for string concat onto the stack writer.writeCast(back); // if necessary, cast the String to the lhs actual type if (lhs.read) { writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // if this lhs is also read // from dup the value onto the stack } lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array } else if (operation != null) { // Handle the case where we are doing a compound assignment that // does not represent a String concatenation. writer.writeDup(lhs.accessElementCount(), 0); // if necessary, dup the previous lhs's value // to be both loaded from and stored to lhs.load(writer, globals); // load the current lhs's value if (lhs.read && post) { writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also // read from and is a post increment } writer.writeCast(there); // if necessary cast the current lhs's value // to the promotion type between the lhs and rhs types rhs.write(writer, globals); // write the bytecode for the rhs // XXX: fix these types, but first we need def compound assignment tests. // its tricky here as there are possibly explicit casts, too. // write the operation instruction for compound assignment if (promote.sort == Sort.DEF) { writer.writeDynamicBinaryInstruction(location, promote, Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT); } else { writer.writeBinaryInstruction(location, promote, operation); } writer.writeCast(back); // if necessary cast the promotion type value back to the lhs's type if (lhs.read && !post) { writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also // read from and is not a post increment } lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array } else { // Handle the case for a simple write. rhs.write(writer, globals); // write the bytecode for the rhs rhs if (lhs.read) { writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also read from } lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array } } @Override public String toString() { List<Object> subs = new ArrayList<>(); subs.add(lhs); if (rhs != null) { // Make sure "=" is in the symbol so this is easy to read at a glance subs.add(operation == null ? "=" : operation.symbol + "="); subs.add(rhs); return singleLineToString(subs); } subs.add(operation.symbol); if (pre) { subs.add("pre"); } if (post) { subs.add("post"); } return singleLineToString(subs); } }