| /******************************************************************************* |
| * Copyright (c) 2006, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.codegen; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ClassFile; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| import org.eclipse.jdt.internal.compiler.problem.AbortMethod; |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public class StackMapFrameCodeStream extends CodeStream { |
| public static class ExceptionMarker implements Comparable { |
| public char[] constantPoolName; |
| public int pc; |
| |
| public ExceptionMarker(int pc, char[] constantPoolName) { |
| this.pc = pc; |
| this.constantPoolName = constantPoolName; |
| } |
| @Override |
| public int compareTo(Object o) { |
| if (o instanceof ExceptionMarker) { |
| return this.pc - ((ExceptionMarker) o).pc; |
| } |
| return 0; |
| } |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof ExceptionMarker) { |
| ExceptionMarker marker = (ExceptionMarker) obj; |
| return this.pc == marker.pc && CharOperation.equals(this.constantPoolName, marker.constantPoolName); |
| } |
| return false; |
| } |
| @Override |
| public int hashCode() { |
| return this.pc + CharOperation.hashCode(this.constantPoolName); |
| } |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append('(').append(this.pc).append(',').append(this.constantPoolName).append(')'); |
| return String.valueOf(buffer); |
| } |
| } |
| |
| public static class StackDepthMarker { |
| public int pc; |
| public int delta; |
| public TypeBinding typeBinding; |
| |
| public StackDepthMarker(int pc, int delta, TypeBinding typeBinding) { |
| this.pc = pc; |
| this.typeBinding = typeBinding; |
| this.delta = delta; |
| } |
| |
| public StackDepthMarker(int pc, int delta) { |
| this.pc = pc; |
| this.delta = delta; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append('(').append(this.pc).append(',').append(this.delta); |
| if (this.typeBinding != null) { |
| buffer |
| .append(',') |
| .append(this.typeBinding.qualifiedPackageName()) |
| .append(this.typeBinding.qualifiedSourceName()); |
| } |
| buffer.append(')'); |
| return String.valueOf(buffer); |
| } |
| } |
| |
| public static class StackMarker { |
| public int pc; |
| public int destinationPC; |
| public VerificationTypeInfo[] infos; |
| |
| public StackMarker(int pc, int destinationPC) { |
| this.pc = pc; |
| this.destinationPC = destinationPC; |
| } |
| |
| public void setInfos(VerificationTypeInfo[] infos) { |
| this.infos = infos; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer |
| .append("[copy stack items from ") //$NON-NLS-1$ |
| .append(this.pc) |
| .append(" to ") //$NON-NLS-1$ |
| .append(this.destinationPC); |
| if (this.infos!= null) { |
| for (int i = 0, max = this.infos.length; i < max; i++) { |
| if (i > 0) buffer.append(','); |
| buffer.append(this.infos[i]); |
| } |
| } |
| buffer.append(']'); |
| return String.valueOf(buffer); |
| } |
| } |
| |
| static class FramePosition { |
| int counter; |
| } |
| |
| public int[] stateIndexes; |
| public int stateIndexesCounter; |
| private HashMap framePositions; |
| public Set exceptionMarkers; |
| public ArrayList stackDepthMarkers; |
| public ArrayList stackMarkers; |
| |
| public StackMapFrameCodeStream(ClassFile givenClassFile) { |
| super(givenClassFile); |
| this.generateAttributes |= ClassFileConstants.ATTR_STACK_MAP; |
| } |
| @Override |
| public void addDefinitelyAssignedVariables(Scope scope, int initStateIndex) { |
| // Required to fix 1PR0XVS: LFRE:WINNT - Compiler: variable table for method appears incorrect |
| loop: for (int i = 0; i < this.visibleLocalsCount; i++) { |
| LocalVariableBinding localBinding = this.visibleLocals[i]; |
| if (localBinding != null) { |
| // Check if the local is definitely assigned |
| boolean isDefinitelyAssigned = isDefinitelyAssigned(scope, initStateIndex, localBinding); |
| if (!isDefinitelyAssigned) { |
| if (this.stateIndexes != null) { |
| for (int j = 0, max = this.stateIndexesCounter; j < max; j++) { |
| if (isDefinitelyAssigned(scope, this.stateIndexes[j], localBinding)) { |
| if ((localBinding.initializationCount == 0) || (localBinding.initializationPCs[((localBinding.initializationCount - 1) << 1) + 1] != -1)) { |
| /* There are two cases: |
| * 1) there is no initialization interval opened ==> add an opened interval |
| * 2) there is already some initialization intervals but the last one is closed ==> add an opened interval |
| * An opened interval means that the value at localBinding.initializationPCs[localBinding.initializationCount - 1][1] |
| * is equals to -1. |
| * initializationPCs is a collection of pairs of int: |
| * first value is the startPC and second value is the endPC. -1 one for the last value means that the interval |
| * is not closed yet. |
| */ |
| localBinding.recordInitializationStartPC(this.position); |
| } |
| continue loop; |
| } |
| } |
| } |
| } else { |
| if ((localBinding.initializationCount == 0) || (localBinding.initializationPCs[((localBinding.initializationCount - 1) << 1) + 1] != -1)) { |
| /* There are two cases: |
| * 1) there is no initialization interval opened ==> add an opened interval |
| * 2) there is already some initialization intervals but the last one is closed ==> add an opened interval |
| * An opened interval means that the value at localBinding.initializationPCs[localBinding.initializationCount - 1][1] |
| * is equals to -1. |
| * initializationPCs is a collection of pairs of int: |
| * first value is the startPC and second value is the endPC. -1 one for the last value means that the interval |
| * is not closed yet. |
| */ |
| localBinding.recordInitializationStartPC(this.position); |
| } |
| } |
| } |
| } |
| } |
| public void addExceptionMarker(int pc, TypeBinding typeBinding) { |
| if (this.exceptionMarkers == null) { |
| this.exceptionMarkers = new HashSet(); |
| } |
| if (typeBinding == null) { |
| this.exceptionMarkers.add(new ExceptionMarker(pc, ConstantPool.JavaLangThrowableConstantPoolName)); |
| } else { |
| switch(typeBinding.id) { |
| case TypeIds.T_null : |
| this.exceptionMarkers.add(new ExceptionMarker(pc, ConstantPool.JavaLangClassNotFoundExceptionConstantPoolName)); |
| break; |
| case TypeIds.T_long : |
| this.exceptionMarkers.add(new ExceptionMarker(pc, ConstantPool.JavaLangNoSuchFieldErrorConstantPoolName)); |
| break; |
| default: |
| this.exceptionMarkers.add(new ExceptionMarker(pc, typeBinding.constantPoolName())); |
| } |
| } |
| } |
| public void addFramePosition(int pc) { |
| Integer newEntry = Integer.valueOf(pc); |
| FramePosition value; |
| if ((value = (FramePosition) this.framePositions.get(newEntry)) != null) { |
| value.counter++; |
| } else { |
| this.framePositions.put(newEntry, new FramePosition()); |
| } |
| } |
| @Override |
| public void optimizeBranch(int oldPosition, BranchLabel lbl) { |
| super.optimizeBranch(oldPosition, lbl); |
| removeFramePosition(oldPosition); |
| } |
| public void removeFramePosition(int pc) { |
| Integer entry = Integer.valueOf(pc); |
| FramePosition value; |
| if ((value = (FramePosition) this.framePositions.get(entry)) != null) { |
| value.counter--; |
| if (value.counter <= 0) { |
| this.framePositions.remove(entry); |
| } |
| } |
| } |
| @Override |
| public void addVariable(LocalVariableBinding localBinding) { |
| if (localBinding.initializationPCs == null) { |
| record(localBinding); |
| } |
| localBinding.recordInitializationStartPC(this.position); |
| } |
| private void addStackMarker(int pc, int destinationPC) { |
| if (this.stackMarkers == null) { |
| this.stackMarkers = new ArrayList(); |
| this.stackMarkers.add(new StackMarker(pc, destinationPC)); |
| } else { |
| int size = this.stackMarkers.size(); |
| if (size == 0 || ((StackMarker) this.stackMarkers.get(size - 1)).pc != this.position) { |
| this.stackMarkers.add(new StackMarker(pc, destinationPC)); |
| } |
| } |
| } |
| private void addStackDepthMarker(int pc, int delta, TypeBinding typeBinding) { |
| if (this.stackDepthMarkers == null) { |
| this.stackDepthMarkers = new ArrayList(); |
| this.stackDepthMarkers.add(new StackDepthMarker(pc, delta, typeBinding)); |
| } else { |
| int size = this.stackDepthMarkers.size(); |
| if (size == 0) { |
| this.stackDepthMarkers.add(new StackDepthMarker(pc, delta, typeBinding)); |
| } else { |
| StackDepthMarker stackDepthMarker = (StackDepthMarker) this.stackDepthMarkers.get(size - 1); |
| if (stackDepthMarker.pc != this.position) { |
| this.stackDepthMarkers.add(new StackDepthMarker(pc, delta, typeBinding)); |
| } else { |
| // We replace the recorded stack depth marker with a new value that contains the given typeBinding |
| // This case can happen when multiple conditional expression are nested see bug 362591 |
| this.stackDepthMarkers.set(size - 1, new StackDepthMarker(pc, delta, typeBinding)); |
| } |
| } |
| } |
| } |
| @Override |
| public void decrStackSize(int offset) { |
| super.decrStackSize(offset); |
| addStackDepthMarker(this.position, -1, null); |
| } |
| @Override |
| public void recordExpressionType(TypeBinding typeBinding) { |
| addStackDepthMarker(this.position, 0, typeBinding); |
| } |
| /** |
| * Macro for building a class descriptor object |
| */ |
| @Override |
| public void generateClassLiteralAccessForType(TypeBinding accessedType, FieldBinding syntheticFieldBinding) { |
| if (accessedType.isBaseType() && accessedType != TypeBinding.NULL) { |
| getTYPE(accessedType.id); |
| return; |
| } |
| |
| if (this.targetLevel >= ClassFileConstants.JDK1_5) { |
| // generation using the new ldc_w bytecode |
| this.ldc(accessedType); |
| } else { |
| // use in CLDC mode |
| BranchLabel endLabel = new BranchLabel(this); |
| if (syntheticFieldBinding != null) { // non interface case |
| fieldAccess(Opcodes.OPC_getstatic, syntheticFieldBinding, null /* default declaringClass */); |
| dup(); |
| ifnonnull(endLabel); |
| pop(); |
| } |
| |
| /* Macro for building a class descriptor object... using or not a field cache to store it into... |
| this sequence is responsible for building the actual class descriptor. |
| |
| If the fieldCache is set, then it is supposed to be the body of a synthetic access method |
| factoring the actual descriptor creation out of the invocation site (saving space). |
| If the fieldCache is nil, then we are dumping the bytecode on the invocation site, since |
| we have no way to get a hand on the field cache to do better. */ |
| |
| |
| // Wrap the code in an exception handler to convert a ClassNotFoundException into a NoClassDefError |
| |
| ExceptionLabel classNotFoundExceptionHandler = new ExceptionLabel(this, TypeBinding.NULL /*represents ClassNotFoundException*/); |
| classNotFoundExceptionHandler.placeStart(); |
| this.ldc(accessedType == TypeBinding.NULL ? "java.lang.Object" : String.valueOf(accessedType.constantPoolName()).replace('/', '.')); //$NON-NLS-1$ |
| invokeClassForName(); |
| |
| /* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=37565 |
| if (accessedType == BaseTypes.NullBinding) { |
| this.ldc("java.lang.Object"); //$NON-NLS-1$ |
| } else if (accessedType.isArrayType()) { |
| this.ldc(String.valueOf(accessedType.constantPoolName()).replace('/', '.')); |
| } else { |
| // we make it an array type (to avoid class initialization) |
| this.ldc("[L" + String.valueOf(accessedType.constantPoolName()).replace('/', '.') + ";"); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| this.invokeClassForName(); |
| if (!accessedType.isArrayType()) { // extract the component type, which doesn't initialize the class |
| this.invokeJavaLangClassGetComponentType(); |
| } |
| */ |
| /* We need to protect the runtime code from binary inconsistencies |
| in case the accessedType is missing, the ClassNotFoundException has to be converted |
| into a NoClassDefError(old ex message), we thus need to build an exception handler for this one. */ |
| classNotFoundExceptionHandler.placeEnd(); |
| |
| if (syntheticFieldBinding != null) { // non interface case |
| dup(); |
| fieldAccess(Opcodes.OPC_putstatic, syntheticFieldBinding, null /* default declaringClass */); |
| } |
| int fromPC = this.position; |
| goto_(endLabel); |
| int savedStackDepth = this.stackDepth; |
| // Generate the body of the exception handler |
| /* ClassNotFoundException on stack -- the class literal could be doing more things |
| on the stack, which means that the stack may not be empty at this point in the |
| above code gen. So we save its state and restart it from 1. */ |
| |
| pushExceptionOnStack(TypeBinding.NULL);/*represents ClassNotFoundException*/ |
| classNotFoundExceptionHandler.place(); |
| |
| // Transform the current exception, and repush and throw a |
| // NoClassDefFoundError(ClassNotFound.getMessage()) |
| |
| newNoClassDefFoundError(); |
| dup_x1(); |
| swap(); |
| |
| // Retrieve the message from the old exception |
| invokeThrowableGetMessage(); |
| |
| // Send the constructor taking a message string as an argument |
| invokeNoClassDefFoundErrorStringConstructor(); |
| athrow(); |
| endLabel.place(); |
| addStackMarker(fromPC, this.position); |
| this.stackDepth = savedStackDepth; |
| } |
| } |
| @Override |
| public void generateOuterAccess(Object[] mappingSequence, ASTNode invocationSite, Binding target, Scope scope) { |
| int currentPosition = this.position; |
| super.generateOuterAccess(mappingSequence, invocationSite, target, scope); |
| if (currentPosition == this.position) { |
| // no code has been generate during outer access => no enclosing instance is available |
| throw new AbortMethod(scope.referenceCompilationUnit().compilationResult, null); |
| } |
| } |
| public ExceptionMarker[] getExceptionMarkers() { |
| Set exceptionMarkerSet = this.exceptionMarkers; |
| if (this.exceptionMarkers == null) return null; |
| int size = exceptionMarkerSet.size(); |
| ExceptionMarker[] markers = new ExceptionMarker[size]; |
| int n = 0; |
| for (Iterator iterator = exceptionMarkerSet.iterator(); iterator.hasNext(); ) { |
| markers[n++] = (ExceptionMarker) iterator.next(); |
| } |
| Arrays.sort(markers); |
| // System.out.print('['); |
| // for (int n = 0; n < size; n++) { |
| // if (n != 0) System.out.print(','); |
| // System.out.print(positions[n]); |
| // } |
| // System.out.println(']'); |
| return markers; |
| } |
| public int[] getFramePositions() { |
| Set set = this.framePositions.keySet(); |
| int size = set.size(); |
| int[] positions = new int[size]; |
| int n = 0; |
| for (Iterator iterator = set.iterator(); iterator.hasNext(); ) { |
| positions[n++] = ((Integer) iterator.next()).intValue(); |
| } |
| Arrays.sort(positions); |
| // System.out.print('['); |
| // for (int n = 0; n < size; n++) { |
| // if (n != 0) System.out.print(','); |
| // System.out.print(positions[n]); |
| // } |
| // System.out.println(']'); |
| return positions; |
| } |
| public StackDepthMarker[] getStackDepthMarkers() { |
| if (this.stackDepthMarkers == null) return null; |
| int length = this.stackDepthMarkers.size(); |
| if (length == 0) return null; |
| StackDepthMarker[] result = new StackDepthMarker[length]; |
| this.stackDepthMarkers.toArray(result); |
| return result; |
| } |
| public StackMarker[] getStackMarkers() { |
| if (this.stackMarkers == null) return null; |
| int length = this.stackMarkers.size(); |
| if (length == 0) return null; |
| StackMarker[] result = new StackMarker[length]; |
| this.stackMarkers.toArray(result); |
| return result; |
| } |
| public boolean hasFramePositions() { |
| return this.framePositions.size() != 0; |
| } |
| @Override |
| public void init(ClassFile targetClassFile) { |
| super.init(targetClassFile); |
| this.stateIndexesCounter = 0; |
| if (this.framePositions != null) { |
| this.framePositions.clear(); |
| } |
| if (this.exceptionMarkers != null) { |
| this.exceptionMarkers.clear(); |
| } |
| if (this.stackDepthMarkers != null) { |
| this.stackDepthMarkers.clear(); |
| } |
| if (this.stackMarkers != null) { |
| this.stackMarkers.clear(); |
| } |
| } |
| |
| @Override |
| public void initializeMaxLocals(MethodBinding methodBinding) { |
| super.initializeMaxLocals(methodBinding); |
| if (this.framePositions == null) { |
| this.framePositions = new HashMap(); |
| } else { |
| this.framePositions.clear(); |
| } |
| } |
| public void popStateIndex() { |
| this.stateIndexesCounter--; |
| } |
| public void pushStateIndex(int naturalExitMergeInitStateIndex) { |
| if (this.stateIndexes == null) { |
| this.stateIndexes = new int[3]; |
| } |
| int length = this.stateIndexes.length; |
| if (length == this.stateIndexesCounter) { |
| // resize |
| System.arraycopy(this.stateIndexes, 0, (this.stateIndexes = new int[length * 2]), 0, length); |
| } |
| this.stateIndexes[this.stateIndexesCounter++] = naturalExitMergeInitStateIndex; |
| } |
| @Override |
| public void removeNotDefinitelyAssignedVariables(Scope scope, int initStateIndex) { |
| int index = this.visibleLocalsCount; |
| loop : for (int i = 0; i < index; i++) { |
| LocalVariableBinding localBinding = this.visibleLocals[i]; |
| if (localBinding != null && localBinding.initializationCount > 0) { |
| boolean isDefinitelyAssigned = isDefinitelyAssigned(scope, initStateIndex, localBinding); |
| if (!isDefinitelyAssigned) { |
| if (this.stateIndexes != null) { |
| for (int j = 0, max = this.stateIndexesCounter; j < max; j++) { |
| if (isDefinitelyAssigned(scope, this.stateIndexes[j], localBinding)) { |
| continue loop; |
| } |
| } |
| } |
| localBinding.recordInitializationEndPC(this.position); |
| } |
| } |
| } |
| } |
| @Override |
| public void reset(ClassFile givenClassFile) { |
| super.reset(givenClassFile); |
| this.stateIndexesCounter = 0; |
| if (this.framePositions != null) { |
| this.framePositions.clear(); |
| } |
| if (this.exceptionMarkers != null) { |
| this.exceptionMarkers.clear(); |
| } |
| if (this.stackDepthMarkers != null) { |
| this.stackDepthMarkers.clear(); |
| } |
| if (this.stackMarkers != null) { |
| this.stackMarkers.clear(); |
| } |
| } |
| @Override |
| protected void writePosition(BranchLabel label) { |
| super.writePosition(label); |
| addFramePosition(label.position); |
| } |
| @Override |
| protected void writePosition(BranchLabel label, int forwardReference) { |
| super.writePosition(label, forwardReference); |
| addFramePosition(label.position); |
| } |
| @Override |
| protected void writeSignedWord(int pos, int value) { |
| super.writeSignedWord(pos, value); |
| addFramePosition(this.position); |
| } |
| @Override |
| protected void writeWidePosition(BranchLabel label) { |
| super.writeWidePosition(label); |
| addFramePosition(label.position); |
| } |
| @Override |
| public void areturn() { |
| super.areturn(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void ireturn() { |
| super.ireturn(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void lreturn() { |
| super.lreturn(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void freturn() { |
| super.freturn(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void dreturn() { |
| super.dreturn(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void return_() { |
| super.return_(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void athrow() { |
| super.athrow(); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void pushOnStack(TypeBinding binding) { |
| super.pushOnStack(binding); |
| addStackDepthMarker(this.position, 1, binding); |
| } |
| @Override |
| public void pushExceptionOnStack(TypeBinding binding) { |
| super.pushExceptionOnStack(binding); |
| addExceptionMarker(this.position, binding); |
| } |
| @Override |
| public void goto_(BranchLabel label) { |
| super.goto_(label); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void goto_w(BranchLabel label) { |
| super.goto_w(label); |
| addFramePosition(this.position); |
| } |
| @Override |
| public void resetInWideMode() { |
| this.resetSecretLocals(); |
| super.resetInWideMode(); |
| } |
| @Override |
| public void resetForCodeGenUnusedLocals() { |
| this.resetSecretLocals(); |
| super.resetForCodeGenUnusedLocals(); |
| } |
| public void resetSecretLocals() { |
| for (int i = 0, max = this.locals.length; i < max; i++) { |
| LocalVariableBinding localVariableBinding = this.locals[i]; |
| if (localVariableBinding != null && localVariableBinding.isSecret()) { |
| // all other locals are reinitialized inside the computation of their resolved positions |
| localVariableBinding.resetInitializations(); |
| } |
| } |
| } |
| } |