| /******************************************************************************* |
| * Copyright (c) 2006, 2019 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 { |
| private TypeBinding binding; |
| public int pc; |
| |
| public ExceptionMarker(int pc, TypeBinding typeBinding) { |
| this.pc = pc; |
| this.binding = typeBinding; |
| } |
| |
| @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 && this.binding.equals(marker.binding); |
| } |
| return false; |
| } |
| |
| public TypeBinding getBinding() { |
| return this.binding; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.pc + CharOperation.hashCode(this.binding.constantPoolName()); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append('(').append(this.pc).append(',').append(this.binding.constantPoolName()).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 |
| 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) { |
| continue; |
| } 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(); |
| } |
| |
| this.exceptionMarkers.add(new ExceptionMarker(pc, typeBinding)); |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public void recordExpressionType(TypeBinding typeBinding, int delta, boolean adjustStackDepth) { |
| if (adjustStackDepth) { |
| // optimized goto |
| // the break label already adjusted the stack depth (-1 or -2 depending on the return type) |
| // we need to adjust back to what it was |
| switch (typeBinding.id) { |
| case TypeIds.T_long: |
| case TypeIds.T_double: |
| this.stackDepth += 2; |
| break; |
| case TypeIds.T_void: |
| break; |
| default: |
| this.stackDepth++; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Macro for building a class descriptor object |
| */ |
| @Override |
| public void generateClassLiteralAccessForType(Scope scope, 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" //$NON-NLS-1$ |
| : String.valueOf(accessedType.constantPoolName()).replace('/', '.')); |
| 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 */); |
| } |
| 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(scope.getJavaLangClassNotFoundException()); |
| 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(); |
| 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 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 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(); |
| } |
| } |
| } |
| } |