/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*/
/*
* $Id: MethodGenerator.java,v 1.2.4.1 2005/09/05 11:16:47 pvedula Exp $
*/
package com.sun.org.apache.xalan.internal.xsltc.compiler.util;
import com.sun.org.apache.bcel.internal.Const;
import com.sun.org.apache.bcel.internal.classfile.Field;
import com.sun.org.apache.bcel.internal.classfile.Method;
import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.ASTORE;
import com.sun.org.apache.bcel.internal.generic.BranchHandle;
import com.sun.org.apache.bcel.internal.generic.BranchInstruction;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.DLOAD;
import com.sun.org.apache.bcel.internal.generic.DSTORE;
import com.sun.org.apache.bcel.internal.generic.FLOAD;
import com.sun.org.apache.bcel.internal.generic.FSTORE;
import com.sun.org.apache.bcel.internal.generic.GETFIELD;
import com.sun.org.apache.bcel.internal.generic.GOTO;
import com.sun.org.apache.bcel.internal.generic.ICONST;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC;
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
import com.sun.org.apache.bcel.internal.generic.ISTORE;
import com.sun.org.apache.bcel.internal.generic.IfInstruction;
import com.sun.org.apache.bcel.internal.generic.IndexedInstruction;
import com.sun.org.apache.bcel.internal.generic.Instruction;
import com.sun.org.apache.bcel.internal.generic.InstructionConst;
import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.InstructionTargeter;
import com.sun.org.apache.bcel.internal.generic.LLOAD;
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.LocalVariableInstruction;
import com.sun.org.apache.bcel.internal.generic.MethodGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.bcel.internal.generic.Select;
import com.sun.org.apache.bcel.internal.generic.TargetLostException;
import com.sun.org.apache.bcel.internal.generic.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.Pattern;
import com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* @author Jacek Ambroziak
* @author Santiago Pericas-Geertsen
* @LastModified: Nov 2017
*/
public class MethodGenerator extends MethodGen
implements com.sun.org.apache.xalan.internal.xsltc.compiler.Constants {
protected static final int INVALID_INDEX = -1;
private static final String START_ELEMENT_SIG
= "(" + STRING_SIG + ")V";
private static final String END_ELEMENT_SIG
= START_ELEMENT_SIG;
private static final int DOM_INDEX = 1;
private static final int ITERATOR_INDEX = 2;
private static final int HANDLER_INDEX = 3;
private static final int MAX_METHOD_SIZE = 65535;
private static final int MAX_BRANCH_TARGET_OFFSET = 32767;
private static final int MIN_BRANCH_TARGET_OFFSET = -32768;
private static final int TARGET_METHOD_SIZE = 60000;
private static final int MINIMUM_OUTLINEABLE_CHUNK_SIZE = 1000;
private Instruction _iloadCurrent;
private Instruction _istoreCurrent;
private final Instruction _astoreHandler;
private final Instruction _aloadHandler;
private final Instruction _astoreIterator;
private final Instruction _aloadIterator;
private final Instruction _aloadDom;
private final Instruction _astoreDom;
private final Instruction _startElement;
private final Instruction _endElement;
private final Instruction _startDocument;
private final Instruction _endDocument;
private final Instruction _attribute;
private final Instruction _uniqueAttribute;
private final Instruction _namespace;
private final Instruction _setStartNode;
private final Instruction _reset;
private final Instruction _nextNode;
private SlotAllocator _slotAllocator;
private boolean _allocatorInit = false;
private LocalVariableRegistry _localVariableRegistry;
/**
* A mapping between patterns and instruction lists used by
* test sequences to avoid compiling the same pattern multiple
* times. Note that patterns whose kernels are "*", "node()"
* and "@*" can between shared by test sequences.
*/
private Map _preCompiled = new HashMap<>();
public MethodGenerator(int access_flags, Type return_type,
Type[] arg_types, String[] arg_names,
String method_name, String class_name,
InstructionList il, ConstantPoolGen cpg) {
super(access_flags, return_type, arg_types, arg_names, method_name,
class_name, il, cpg);
_astoreHandler = new ASTORE(HANDLER_INDEX);
_aloadHandler = new ALOAD(HANDLER_INDEX);
_astoreIterator = new ASTORE(ITERATOR_INDEX);
_aloadIterator = new ALOAD(ITERATOR_INDEX);
_aloadDom = new ALOAD(DOM_INDEX);
_astoreDom = new ASTORE(DOM_INDEX);
final int startElement =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"startElement",
START_ELEMENT_SIG);
_startElement = new INVOKEINTERFACE(startElement, 2);
final int endElement =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"endElement",
END_ELEMENT_SIG);
_endElement = new INVOKEINTERFACE(endElement, 2);
final int attribute =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"addAttribute",
"("
+ STRING_SIG
+ STRING_SIG
+ ")V");
_attribute = new INVOKEINTERFACE(attribute, 3);
final int uniqueAttribute =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"addUniqueAttribute",
"("
+ STRING_SIG
+ STRING_SIG
+ "I)V");
_uniqueAttribute = new INVOKEINTERFACE(uniqueAttribute, 4);
final int namespace =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"namespaceAfterStartElement",
"("
+ STRING_SIG
+ STRING_SIG
+ ")V");
_namespace = new INVOKEINTERFACE(namespace, 3);
int index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"startDocument",
"()V");
_startDocument = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"endDocument",
"()V");
_endDocument = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(NODE_ITERATOR,
SET_START_NODE,
SET_START_NODE_SIG);
_setStartNode = new INVOKEINTERFACE(index, 2);
index = cpg.addInterfaceMethodref(NODE_ITERATOR,
"reset", "()"+NODE_ITERATOR_SIG);
_reset = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(NODE_ITERATOR, NEXT, NEXT_SIG);
_nextNode = new INVOKEINTERFACE(index, 1);
_slotAllocator = new SlotAllocator();
_slotAllocator.initialize(getLocalVariableRegistry().getLocals(false));
_allocatorInit = true;
}
/**
* Allocates a local variable. If the slot allocator has already been
* initialized, then call addLocalVariable2() so that the new variable
* is known to the allocator. Failing to do this may cause the allocator
* to return a slot that is already in use.
*/
public LocalVariableGen addLocalVariable(String name, Type type,
InstructionHandle start,
InstructionHandle end)
{
LocalVariableGen lvg;
if (_allocatorInit) {
lvg = addLocalVariable2(name, type, start);
} else {
lvg = super.addLocalVariable(name, type, start, end);
getLocalVariableRegistry().registerLocalVariable(lvg);
}
return lvg;
}
public LocalVariableGen addLocalVariable2(String name, Type type,
InstructionHandle start)
{
LocalVariableGen lvg = super.addLocalVariable(name, type,
_slotAllocator.allocateSlot(type),
start, null);
getLocalVariableRegistry().registerLocalVariable(lvg);
return lvg;
}
private LocalVariableRegistry getLocalVariableRegistry() {
if (_localVariableRegistry == null) {
_localVariableRegistry = new LocalVariableRegistry();
}
return _localVariableRegistry;
}
/**
* Keeps track of all local variables used in the method.
*
The
* {@link MethodGen#addLocalVariable(String,Type,InstructionHandle,InstructionHandle)}
* and
* {@link MethodGen#addLocalVariable(String,Type,int,InstructionHandle,InstructionHandle)}
* methods of {@link MethodGen} will only keep track of
* {@link LocalVariableGen} object until it'ss removed by a call to
* {@link MethodGen#removeLocalVariable(LocalVariableGen)}.
*
In order to support efficient copying of local variables to outlined
* methods by
* {@link #outline(InstructionHandle,InstructionHandle,String,ClassGenerator)},
* this class keeps track of all local variables defined by the method.
*/
protected class LocalVariableRegistry {
/**
*
A java.lang.List of all
* {@link LocalVariableGen}s created for this method, indexed by the
* slot number of the local variable. The JVM stack frame of local
* variables is divided into "slots". A single slot can be used to
* store more than one variable in a method, without regard to type, so
* long as the byte code keeps the ranges of the two disjoint.
*
If only one registration of use of a particular slot occurs, the
* corresponding entry of _variables contains the
* LocalVariableGen; if more than one occurs, the
* corresponding entry contains all such LocalVariableGens
* registered for the same slot; and if none occurs, the entry will be
* null.
*/
protected List
* @param lvg The variable to be registered
*/
@SuppressWarnings("unchecked")
protected void registerLocalVariable(LocalVariableGen lvg) {
int slot = lvg.getIndex();
int registrySize = _variables.size();
// If the LocalVariableGen uses a slot index beyond any previously
// encountered, expand the _variables, padding with intervening null
// entries as required.
if (slot >= registrySize) {
for (int i = registrySize; i < slot; i++) {
_variables.add(null);
}
_variables.add(lvg);
} else {
// If the LocalVariableGen reuses a slot, make sure the entry
// in _variables contains an ArrayList and add the newly
// registered LocalVariableGen to the list. If the entry in
// _variables just contains null padding, store the
// LocalVariableGen directly.
Object localsInSlot = _variables.get(slot);
if (localsInSlot != null) {
if (localsInSlot instanceof LocalVariableGen) {
List listOfLocalsInSlot = new ArrayList<>();
listOfLocalsInSlot.add((LocalVariableGen)localsInSlot);
listOfLocalsInSlot.add(lvg);
_variables.set(slot, listOfLocalsInSlot);
} else {
((List) localsInSlot).add(lvg);
}
} else {
_variables.set(slot, lvg);
}
}
registerByName(lvg);
}
/**
*
Find which {@link LocalVariableGen}, if any, is registered for a
* particular JVM local stack frame slot at a particular position in the
* byte code for the method.
*
Preconditions:
*
*
The {@link InstructionList#setPositions()} has been called for
* the {@link InstructionList} associated with this
* {@link MethodGenerator}.
*
* @param slot the JVM local stack frame slot number
* @param offset the position in the byte code
* @return the LocalVariableGen for the local variable
* stored in the relevant slot at the relevant offset; null
* if there is none.
*/
protected LocalVariableGen lookupRegisteredLocalVariable(int slot,
int offset) {
Object localsInSlot = (_variables != null) ? _variables.get(slot)
: null;
// If this slot index was never used, _variables.get will return
// null; if it was used once, it will return the LocalVariableGen;
// more than once it will return an ArrayList of all the
// LocalVariableGens for variables stored in that slot. For each
// LocalVariableGen, check whether its range includes the
// specified offset, and return the first such encountered.
if (localsInSlot != null) {
if (localsInSlot instanceof LocalVariableGen) {
LocalVariableGen lvg = (LocalVariableGen)localsInSlot;
if (offsetInLocalVariableGenRange(lvg, offset)) {
return lvg;
}
} else {
@SuppressWarnings("unchecked")
List listOfLocalsInSlot =
(List) localsInSlot;
for (LocalVariableGen lvg : listOfLocalsInSlot) {
if (offsetInLocalVariableGenRange(lvg, offset)) {
return lvg;
}
}
}
}
// No local variable stored in the specified slot at the specified
return null;
}
/**
*
Set up a mapping of the name of the specified
* {@link LocalVariableGen} object to the LocalVariableGen
* itself.
*
This is a bit of a hack. XSLTC is relying on the fact that the
* name that is being looked up won't be duplicated, which isn't
* guaranteed. It replaces code which used to call
* {@link MethodGen#getLocalVariables()} and looped through the
* LocalVariableGen objects it contained to find the one
* with the specified name. However, getLocalVariables()
* has the side effect of setting the start and end for any
* LocalVariableGen which did not already have them
* set, which causes problems for outlining..
*
See also {@link #lookUpByName(String)} and
* {@link #removeByNameTracking(LocalVariableGen)}
LocalVariableGen
*/
@SuppressWarnings("unchecked")
protected void registerByName(LocalVariableGen lvg) {
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());
if (duplicateNameEntry == null) {
_nameToLVGMap.put(lvg.getName(), lvg);
} else {
List sameNameList;
if (duplicateNameEntry instanceof ArrayList) {
sameNameList = (List)duplicateNameEntry;
sameNameList.add(lvg);
} else {
sameNameList = new ArrayList<>();
sameNameList.add((LocalVariableGen)duplicateNameEntry);
sameNameList.add(lvg);
}
_nameToLVGMap.put(lvg.getName(), sameNameList);
}
}
/**
* Remove the mapping from the name of the specified
* {@link LocalVariableGen} to itself.
* See also {@link #registerByName(LocalVariableGen)} and
* {@link #lookUpByName(String)}
* @param lvg a LocalVariableGen
*/
@SuppressWarnings("unchecked")
protected void removeByNameTracking(LocalVariableGen lvg) {
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());
if (duplicateNameEntry instanceof ArrayList) {
List sameNameList =
(List)duplicateNameEntry;
for (int i = 0; i < sameNameList.size(); i++) {
if (sameNameList.get(i) == lvg) {
sameNameList.remove(i);
break;
}
}
} else {
_nameToLVGMap.remove(lvg);
}
}
/**
*
Given the name of a variable, finds a {@link LocalVariableGen}
* corresponding to it.
*
See also {@link #registerByName(LocalVariableGen)} and
* {@link #removeByNameTracking(LocalVariableGen)}
* @param name
* @return
*/
@SuppressWarnings("unchecked")
protected LocalVariableGen lookUpByName(String name) {
LocalVariableGen lvg = null;
Object duplicateNameEntry = _nameToLVGMap.get(name);
if (duplicateNameEntry instanceof ArrayList) {
List sameNameList =
(List)duplicateNameEntry;
for (int i = 0; i < sameNameList.size(); i++) {
lvg = sameNameList.get(i);
if (lvg.getName() == null ? name == null : lvg.getName().equals(name)) {
break;
}
}
} else {
lvg = (LocalVariableGen) duplicateNameEntry;
}
return lvg;
}
/**
*
Gets all {@link LocalVariableGen} objects for this method.
*
When the includeRemoved argument has the value
* false, this method replaces uses of
* {@link MethodGen#getLocalVariables()} which has
* a side-effect of setting the start and end range for any
* LocalVariableGen if either was null. That
* side-effect causes problems for outlining of code in XSLTC.
* @param includeRemoved Specifies whether all local variables ever
* declared should be returned (true) or only those not
* removed (false)
* @return an array of LocalVariableGen containing all the
* local variables
*/
@SuppressWarnings("unchecked")
protected LocalVariableGen[] getLocals(boolean includeRemoved) {
LocalVariableGen[] locals = null;
List allVarsEverDeclared = new ArrayList<>();
if (includeRemoved) {
int slotCount = allVarsEverDeclared.size();
for (int i = 0; i < slotCount; i++) {
Object slotEntries = _variables.get(i);
if (slotEntries != null) {
if (slotEntries instanceof ArrayList) {
List slotList =
(List)slotEntries;
for (int j = 0; j < slotList.size(); j++) {
allVarsEverDeclared.add(slotList.get(i));
}
} else {
allVarsEverDeclared.add((LocalVariableGen)slotEntries);
}
}
}
} else {
for (Map.Entry nameVarsPair : _nameToLVGMap.entrySet()) {
Object vars = nameVarsPair.getValue();
if (vars != null) {
if (vars instanceof ArrayList) {
List varsList =
(List) vars;
for (int i = 0; i < varsList.size(); i++) {
allVarsEverDeclared.add(varsList.get(i));
}
} else {
allVarsEverDeclared.add((LocalVariableGen)vars);
}
}
}
}
locals = new LocalVariableGen[allVarsEverDeclared.size()];
allVarsEverDeclared.toArray(locals);
return locals;
}
}
/**
* Determines whether a particular variable is in use at a particular offset
* in the byte code for this method.
*
Preconditions:
*
*
The {@link InstructionList#setPositions()} has been called for the
* {@link InstructionList} associated with this {@link MethodGenerator}.
*
* @param lvg the {@link LocalVariableGen} for the variable
* @param offset the position in the byte code
* @return true if and only if the specified variable is in
* use at the particular byte code offset.
*/
boolean offsetInLocalVariableGenRange(LocalVariableGen lvg, int offset) {
InstructionHandle lvgStart = lvg.getStart();
InstructionHandle lvgEnd = lvg.getEnd();
// If no start handle is recorded for the LocalVariableGen, it is
// assumed to be in use from the beginning of the method.
if (lvgStart == null) {
lvgStart = getInstructionList().getStart();
}
// If no end handle is recorded for the LocalVariableGen, it is assumed
// to be in use to the end of the method.
if (lvgEnd == null) {
lvgEnd = getInstructionList().getEnd();
}
// Does the range of the instruction include the specified offset?
// Note that the InstructionHandle.getPosition method returns the
// offset of the beginning of an instruction. A LocalVariableGen's
// range includes the end instruction itself, so that instruction's
// length must be taken into consideration in computing whether the
// varible is in range at a particular offset.
return ((lvgStart.getPosition() <= offset)
&& (lvgEnd.getPosition()
+ lvgEnd.getInstruction().getLength() >= offset));
}
public void removeLocalVariable(LocalVariableGen lvg) {
_slotAllocator.releaseSlot(lvg);
getLocalVariableRegistry().removeByNameTracking(lvg);
super.removeLocalVariable(lvg);
}
public Instruction loadDOM() {
return _aloadDom;
}
public Instruction storeDOM() {
return _astoreDom;
}
public Instruction storeHandler() {
return _astoreHandler;
}
public Instruction loadHandler() {
return _aloadHandler;
}
public Instruction storeIterator() {
return _astoreIterator;
}
public Instruction loadIterator() {
return _aloadIterator;
}
public final Instruction setStartNode() {
return _setStartNode;
}
public final Instruction reset() {
return _reset;
}
public final Instruction nextNode() {
return _nextNode;
}
public final Instruction startElement() {
return _startElement;
}
public final Instruction endElement() {
return _endElement;
}
public final Instruction startDocument() {
return _startDocument;
}
public final Instruction endDocument() {
return _endDocument;
}
public final Instruction attribute() {
return _attribute;
}
public final Instruction uniqueAttribute() {
return _uniqueAttribute;
}
public final Instruction namespace() {
return _namespace;
}
public Instruction loadCurrentNode() {
if (_iloadCurrent == null) {
int idx = getLocalIndex("current");
if (idx > 0)
_iloadCurrent = new ILOAD(idx);
else
_iloadCurrent = new ICONST(0);
}
return _iloadCurrent;
}
public Instruction storeCurrentNode() {
return _istoreCurrent != null
? _istoreCurrent
: (_istoreCurrent = new ISTORE(getLocalIndex("current")));
}
/** by default context node is the same as current node. MK437 */
public Instruction loadContextNode() {
return loadCurrentNode();
}
public Instruction storeContextNode() {
return storeCurrentNode();
}
public int getLocalIndex(String name) {
return getLocalVariable(name).getIndex();
}
public LocalVariableGen getLocalVariable(String name) {
return getLocalVariableRegistry().lookUpByName(name);
}
public void setMaxLocals() {
// Get the current number of local variable slots
int maxLocals = super.getMaxLocals();
int prevLocals = maxLocals;
// Get numer of actual variables
final LocalVariableGen[] localVars = super.getLocalVariables();
if (localVars != null) {
if (localVars.length > maxLocals)
maxLocals = localVars.length;
}
// We want at least 5 local variable slots (for parameters)
if (maxLocals < 5) maxLocals = 5;
super.setMaxLocals(maxLocals);
}
/**
* Add a pre-compiled pattern to this mode.
*/
public void addInstructionList(Pattern pattern, InstructionList ilist) {
_preCompiled.put(pattern, ilist);
}
/**
* Get the instruction list for a pre-compiled pattern. Used by
* test sequences to avoid compiling patterns more than once.
*/
public InstructionList getInstructionList(Pattern pattern) {
return _preCompiled.get(pattern);
}
/**
* Used to keep track of an outlineable chunk of instructions in the
* current method. See {@link OutlineableChunkStart} and
* {@link OutlineableChunkEnd} for more information.
*/
private class Chunk implements Comparable