| /******************************************************************************* |
| * Copyright (c) 2000, 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 |
| * Stephan Herrmann - Contributions for |
| * bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE |
| * bug 349326 - [1.7] new warning for missing try-with-resources |
| * bug 265744 - Enum switch should warn about missing default |
| * bug 374605 - Unreasonable warning for enum-based switch statements |
| * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import java.util.Arrays; |
| import java.util.function.Function; |
| |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; |
| import org.eclipse.jdt.internal.compiler.codegen.CaseLabel; |
| import org.eclipse.jdt.internal.compiler.codegen.CodeStream; |
| import org.eclipse.jdt.internal.compiler.codegen.Opcodes; |
| import org.eclipse.jdt.internal.compiler.flow.FlowContext; |
| import org.eclipse.jdt.internal.compiler.flow.FlowInfo; |
| import org.eclipse.jdt.internal.compiler.flow.SwitchFlowContext; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; |
| |
| @SuppressWarnings("rawtypes") |
| public class SwitchStatement extends Expression { |
| |
| public Expression expression; |
| public Statement[] statements; |
| public BlockScope scope; |
| public int explicitDeclarations; |
| public BranchLabel breakLabel; |
| public CaseStatement[] cases; |
| public CaseStatement defaultCase; |
| public int blockStart; |
| public int caseCount; |
| int[] constants; |
| int[] constMapping; |
| String[] stringConstants; |
| public boolean switchLabeledRules = false; // true if case ->, false if case : |
| public int nConstants; |
| |
| // fallthrough |
| public final static int CASE = 0; |
| public final static int FALLTHROUGH = 1; |
| public final static int ESCAPING = 2; |
| public final static int BREAKING = 3; |
| |
| // for switch on strings |
| private static final char[] SecretStringVariableName = " switchDispatchString".toCharArray(); //$NON-NLS-1$ |
| |
| |
| public SyntheticMethodBinding synthetic; // use for switch on enums types |
| |
| // for local variables table attributes |
| int preSwitchInitStateIndex = -1; |
| int mergedInitStateIndex = -1; |
| |
| CaseStatement[] duplicateCaseStatements = null; |
| int duplicateCaseStatementsCounter = 0; |
| private LocalVariableBinding dispatchStringCopy = null; |
| |
| protected int getFallThroughState(Statement stmt, BlockScope blockScope) { |
| if (this.switchLabeledRules) { |
| if ((stmt instanceof Expression && ((Expression) stmt).isTrulyExpression()) || stmt instanceof ThrowStatement) |
| return BREAKING; |
| if (!stmt.canCompleteNormally()) |
| return BREAKING; |
| |
| if (stmt instanceof Block) { |
| Block block = (Block) stmt; |
| // Note implicit break anyway - Let the flow analysis do the dead code analysis |
| BreakStatement breakStatement = new BreakStatement(null, block.sourceEnd -1, block.sourceEnd); |
| breakStatement.isSynthetic = true; // suppress dead code flagging - codegen will not generate dead code anyway |
| |
| int l = block.statements == null ? 0 : block.statements.length; |
| if (l == 0) { |
| block.statements = new Statement[] {breakStatement}; |
| block.scope = this.scope; // (upper scope) see Block.resolve() for similar |
| } else { |
| Statement[] newArray = new Statement[l + 1]; |
| System.arraycopy(block.statements, 0, newArray, 0, l); |
| newArray[l] = breakStatement; |
| block.statements = newArray; |
| } |
| return BREAKING; |
| } |
| } |
| return FALLTHROUGH; |
| } |
| protected void completeNormallyCheck(BlockScope blockScope) { |
| // do nothing |
| } |
| protected boolean needToCheckFlowInAbsenceOfDefaultBranch() { |
| return true; |
| } |
| @Override |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { |
| try { |
| flowInfo = this.expression.analyseCode(currentScope, flowContext, flowInfo); |
| if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0 |
| || (this.expression.resolvedType != null |
| && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum()))) { |
| this.expression.checkNPE(currentScope, flowContext, flowInfo, 1); |
| } |
| SwitchFlowContext switchContext = |
| new SwitchFlowContext(flowContext, this, (this.breakLabel = new BranchLabel()), true, true); |
| switchContext.isExpression = this instanceof SwitchExpression; |
| |
| // analyse the block by considering specially the case/default statements (need to bind them |
| // to the entry point) |
| FlowInfo caseInits = FlowInfo.DEAD_END; |
| // in case of statements before the first case |
| this.preSwitchInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); |
| int caseIndex = 0; |
| if (this.statements != null) { |
| int initialComplaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; |
| int complaintLevel = initialComplaintLevel; |
| int fallThroughState = CASE; |
| for (int i = 0, max = this.statements.length; i < max; i++) { |
| Statement statement = this.statements[i]; |
| if ((caseIndex < this.caseCount) && (statement == this.cases[caseIndex])) { // statement is a case |
| this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block |
| caseIndex++; |
| if (fallThroughState == FALLTHROUGH |
| && (statement.bits & ASTNode.DocumentedFallthrough) == 0) { // the case is not fall-through protected by a line comment |
| this.scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase); |
| } |
| caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); |
| complaintLevel = initialComplaintLevel; // reset complaint |
| fallThroughState = CASE; |
| } else if (statement == this.defaultCase) { // statement is the default case |
| this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block |
| if (fallThroughState == FALLTHROUGH |
| && (statement.bits & ASTNode.DocumentedFallthrough) == 0) { |
| this.scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase); |
| } |
| caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); |
| complaintLevel = initialComplaintLevel; // reset complaint |
| fallThroughState = CASE; |
| } else { |
| if (!(this instanceof SwitchExpression) && |
| currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK14 && |
| statement instanceof YieldStatement && |
| ((YieldStatement) statement).isImplicit) { |
| YieldStatement y = (YieldStatement) statement; |
| Expression e = ((YieldStatement) statement).expression; |
| /* JLS 13 14.11.2 |
| Switch labeled rules in switch statements differ from those in switch expressions (15.28). |
| In switch statements they must be switch labeled statement expressions, ... */ |
| if (!y.expression.statementExpression()) { |
| this.scope.problemReporter().invalidExpressionAsStatement(e); |
| } |
| } |
| fallThroughState = getFallThroughState(statement, currentScope); // reset below if needed |
| } |
| if ((complaintLevel = statement.complainIfUnreachable(caseInits, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) { |
| caseInits = statement.analyseCode(this.scope, switchContext, caseInits); |
| if (caseInits == FlowInfo.DEAD_END) { |
| fallThroughState = ESCAPING; |
| } |
| switchContext.expireNullCheckedFieldInfo(); |
| } |
| } |
| completeNormallyCheck(currentScope); |
| } |
| |
| final TypeBinding resolvedTypeBinding = this.expression.resolvedType; |
| if (resolvedTypeBinding.isEnum()) { |
| final SourceTypeBinding sourceTypeBinding = currentScope.classScope().referenceContext.binding; |
| this.synthetic = sourceTypeBinding.addSyntheticMethodForSwitchEnum(resolvedTypeBinding, this); |
| } |
| // if no default case, then record it may jump over the block directly to the end |
| if (this.defaultCase == null && needToCheckFlowInAbsenceOfDefaultBranch()) { |
| // only retain the potential initializations |
| flowInfo.addPotentialInitializationsFrom(caseInits.mergedWith(switchContext.initsOnBreak)); |
| this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); |
| return flowInfo; |
| } |
| |
| // merge all branches inits |
| FlowInfo mergedInfo = caseInits.mergedWith(switchContext.initsOnBreak); |
| this.mergedInitStateIndex = |
| currentScope.methodScope().recordInitializationStates(mergedInfo); |
| return mergedInfo; |
| } finally { |
| if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block |
| } |
| } |
| |
| /** |
| * Switch on String code generation |
| * This assumes that hashCode() specification for java.lang.String is API |
| * and is stable. |
| * |
| * @see "http://download.oracle.com/javase/6/docs/api/java/lang/String.html" |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| */ |
| public void generateCodeForStringSwitch(BlockScope currentScope, CodeStream codeStream) { |
| |
| try { |
| if ((this.bits & IsReachable) == 0) { |
| return; |
| } |
| int pc = codeStream.position; |
| |
| class StringSwitchCase implements Comparable { |
| int hashCode; |
| String string; |
| BranchLabel label; |
| public StringSwitchCase(int hashCode, String string, BranchLabel label) { |
| this.hashCode = hashCode; |
| this.string = string; |
| this.label = label; |
| } |
| @Override |
| public int compareTo(Object o) { |
| StringSwitchCase that = (StringSwitchCase) o; |
| if (this.hashCode == that.hashCode) { |
| return 0; |
| } |
| if (this.hashCode > that.hashCode) { |
| return 1; |
| } |
| return -1; |
| } |
| @Override |
| public String toString() { |
| return "StringSwitchCase :\n" + //$NON-NLS-1$ |
| "case " + this.hashCode + ":(" + this.string + ")\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| /* |
| * With multi constant case statements, the number of case statements (hence branch labels) |
| * and number of constants (hence hashcode labels) could be different. For e.g: |
| |
| switch(s) { |
| case "FB", "c": |
| System.out.println("A/C"); |
| break; |
| case "Ea": |
| System.out.println("B"); |
| break; |
| |
| With the above code, we will have |
| 2 branch labels for FB and c |
| 3 stringCases for FB, c and Ea |
| 2 hashCodeCaseLabels one for FB, Ea and one for c |
| |
| Should produce something like this: |
| lookupswitch { // 2 |
| 99: 32 |
| 2236: 44 |
| default: 87 |
| |
| "FB" and "Ea" producing the same hashcode values, but still belonging in different case statements. |
| First, produce the two branch labels pertaining to the case statements |
| And the three string cases and use the this.constMapping to get the correct branch label. |
| */ |
| final boolean hasCases = this.caseCount != 0; |
| int constSize = hasCases ? this.stringConstants.length : 0; |
| BranchLabel[] sourceCaseLabels = this.<BranchLabel>gatherLabels(codeStream, new BranchLabel[this.nConstants], BranchLabel::new); |
| StringSwitchCase [] stringCases = new StringSwitchCase[constSize]; // may have to shrink later if multiple strings hash to same code. |
| CaseLabel [] hashCodeCaseLabels = new CaseLabel[constSize]; |
| this.constants = new int[constSize]; // hashCode() values. |
| for (int i = 0; i < constSize; i++) { |
| stringCases[i] = new StringSwitchCase(this.stringConstants[i].hashCode(), this.stringConstants[i], sourceCaseLabels[this.constMapping[i]]); |
| hashCodeCaseLabels[i] = new CaseLabel(codeStream); |
| hashCodeCaseLabels[i].tagBits |= BranchLabel.USED; |
| } |
| Arrays.sort(stringCases); |
| |
| int uniqHashCount = 0; |
| int lastHashCode = 0; |
| for (int i = 0, length = constSize; i < length; ++i) { |
| int hashCode = stringCases[i].hashCode; |
| if (i == 0 || hashCode != lastHashCode) { |
| lastHashCode = this.constants[uniqHashCount++] = hashCode; |
| } |
| } |
| |
| if (uniqHashCount != constSize) { // multiple keys hashed to the same value. |
| System.arraycopy(this.constants, 0, this.constants = new int[uniqHashCount], 0, uniqHashCount); |
| System.arraycopy(hashCodeCaseLabels, 0, hashCodeCaseLabels = new CaseLabel[uniqHashCount], 0, uniqHashCount); |
| } |
| int[] sortedIndexes = new int[uniqHashCount]; // hash code are sorted already anyways. |
| for (int i = 0; i < uniqHashCount; i++) { |
| sortedIndexes[i] = i; |
| } |
| |
| CaseLabel defaultCaseLabel = new CaseLabel(codeStream); |
| defaultCaseLabel.tagBits |= BranchLabel.USED; |
| |
| // prepare the labels and constants |
| this.breakLabel.initialize(codeStream); |
| |
| BranchLabel defaultBranchLabel = new BranchLabel(codeStream); |
| if (hasCases) defaultBranchLabel.tagBits |= BranchLabel.USED; |
| if (this.defaultCase != null) { |
| this.defaultCase.targetLabel = defaultBranchLabel; |
| } |
| // generate expression |
| this.expression.generateCode(currentScope, codeStream, true); |
| codeStream.store(this.dispatchStringCopy, true); // leaves string on operand stack |
| codeStream.addVariable(this.dispatchStringCopy); |
| codeStream.invokeStringHashCode(); |
| if (hasCases) { |
| codeStream.lookupswitch(defaultCaseLabel, this.constants, sortedIndexes, hashCodeCaseLabels); |
| for (int i = 0, j = 0, max = constSize; i < max; i++) { |
| int hashCode = stringCases[i].hashCode; |
| if (i == 0 || hashCode != lastHashCode) { |
| lastHashCode = hashCode; |
| if (i != 0) { |
| codeStream.goto_(defaultBranchLabel); |
| } |
| hashCodeCaseLabels[j++].place(); |
| } |
| codeStream.load(this.dispatchStringCopy); |
| codeStream.ldc(stringCases[i].string); |
| codeStream.invokeStringEquals(); |
| codeStream.ifne(stringCases[i].label); |
| } |
| codeStream.goto_(defaultBranchLabel); |
| } else { |
| codeStream.pop(); |
| } |
| |
| // generate the switch block statements |
| int caseIndex = 0; |
| if (this.statements != null) { |
| for (int i = 0, maxCases = this.statements.length; i < maxCases; i++) { |
| Statement statement = this.statements[i]; |
| if ((caseIndex < this.caseCount) && (statement == this.cases[caseIndex])) { // statements[i] is a case |
| this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block |
| if (this.preSwitchInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); |
| } |
| caseIndex++; |
| } else { |
| if (statement == this.defaultCase) { // statements[i] is a case or a default case |
| defaultCaseLabel.place(); // branch label gets placed by generateCode below. |
| this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block |
| if (this.preSwitchInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); |
| } |
| } |
| } |
| statementGenerateCode(currentScope, codeStream, statement); |
| } |
| } |
| |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.mergedInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); |
| } |
| codeStream.removeVariable(this.dispatchStringCopy); |
| if (this.scope != currentScope) { |
| codeStream.exitUserScope(this.scope); |
| } |
| // place the trailing labels (for break and default case) |
| this.breakLabel.place(); |
| if (this.defaultCase == null) { |
| // we want to force an line number entry to get an end position after the switch statement |
| codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd, true); |
| defaultCaseLabel.place(); |
| defaultBranchLabel.place(); |
| } |
| if (this.expectedType() != null) { |
| TypeBinding expectedType = this.expectedType().erasure(); |
| boolean optimizedGoto = codeStream.lastAbruptCompletion == -1; |
| // if the last bytecode was an optimized goto (value is already on the stack) or an enum switch without default case, then we need to adjust the |
| // stack depth to reflect the fact that there is an value on the stack (return type of the switch expression) |
| codeStream.recordExpressionType(expectedType, optimizedGoto ? 0 : 1, optimizedGoto); |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } finally { |
| if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block |
| } |
| } |
| private <T extends BranchLabel>T[] gatherLabels(CodeStream codeStream, T[] caseLabels, |
| Function<CodeStream, T> newLabel) |
| { |
| for (int i = 0, j = 0, max = this.caseCount; i < max; i++) { |
| CaseStatement stmt = this.cases[i]; |
| int l = stmt.constantExpressions.length; |
| stmt.targetLabels = new BranchLabel[l]; |
| for (int k = 0; k < l; ++k) { |
| stmt.targetLabels[k] = (caseLabels[j] = newLabel.apply(codeStream)); |
| caseLabels[j++].tagBits |= BranchLabel.USED; |
| } |
| } |
| return caseLabels; |
| } |
| /** |
| * Switch code generation |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| */ |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream) { |
| if (this.expression.resolvedType.id == TypeIds.T_JavaLangString) { |
| generateCodeForStringSwitch(currentScope, codeStream); |
| return; |
| } |
| try { |
| if ((this.bits & IsReachable) == 0) { |
| return; |
| } |
| int pc = codeStream.position; |
| |
| // prepare the labels and constants |
| this.breakLabel.initialize(codeStream); |
| int constantCount = this.constants == null ? 0 : this.constants.length; |
| CaseLabel[] caseLabels = this.<CaseLabel>gatherLabels(codeStream, new CaseLabel[this.nConstants], CaseLabel::new); |
| |
| CaseLabel defaultLabel = new CaseLabel(codeStream); |
| final boolean hasCases = this.caseCount != 0; |
| if (hasCases) defaultLabel.tagBits |= BranchLabel.USED; |
| if (this.defaultCase != null) { |
| this.defaultCase.targetLabel = defaultLabel; |
| } |
| |
| final TypeBinding resolvedType1 = this.expression.resolvedType; |
| boolean valueRequired = false; |
| if (resolvedType1.isEnum()) { |
| // go through the translation table |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.synthetic, null /* default declaringClass */); |
| this.expression.generateCode(currentScope, codeStream, true); |
| // get enum constant ordinal() |
| codeStream.invokeEnumOrdinal(resolvedType1.constantPoolName()); |
| codeStream.iaload(); |
| if (!hasCases) { |
| // we can get rid of the generated ordinal value |
| codeStream.pop(); |
| } |
| valueRequired = hasCases; |
| } else { |
| valueRequired = this.expression.constant == Constant.NotAConstant || hasCases; |
| // generate expression |
| this.expression.generateCode(currentScope, codeStream, valueRequired); |
| } |
| // generate the appropriate switch table/lookup bytecode |
| if (hasCases) { |
| int[] sortedIndexes = new int[constantCount]; |
| // we sort the keys to be able to generate the code for tableswitch or lookupswitch |
| for (int i = 0; i < constantCount; i++) { |
| sortedIndexes[i] = i; |
| } |
| int[] localKeysCopy; |
| System.arraycopy(this.constants, 0, (localKeysCopy = new int[constantCount]), 0, constantCount); |
| CodeStream.sort(localKeysCopy, 0, constantCount - 1, sortedIndexes); |
| |
| int max = localKeysCopy[constantCount - 1]; |
| int min = localKeysCopy[0]; |
| if ((long) (constantCount * 2.5) > ((long) max - (long) min)) { |
| |
| // work-around 1.3 VM bug, if max>0x7FFF0000, must use lookup bytecode |
| // see http://dev.eclipse.org/bugs/show_bug.cgi?id=21557 |
| if (max > 0x7FFF0000 && currentScope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_4) { |
| codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels); |
| |
| } else { |
| codeStream.tableswitch( |
| defaultLabel, |
| min, |
| max, |
| this.constants, |
| sortedIndexes, |
| this.constMapping, |
| caseLabels); |
| } |
| } else { |
| codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels); |
| } |
| codeStream.recordPositionsFrom(codeStream.position, this.expression.sourceEnd); |
| } else if (valueRequired) { |
| codeStream.pop(); |
| } |
| |
| // generate the switch block statements |
| int caseIndex = 0; |
| if (this.statements != null) { |
| for (int i = 0, maxCases = this.statements.length; i < maxCases; i++) { |
| Statement statement = this.statements[i]; |
| if ((caseIndex < constantCount) && (statement == this.cases[caseIndex])) { // statements[i] is a case |
| this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block |
| if (this.preSwitchInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); |
| } |
| caseIndex++; |
| } else { |
| if (statement == this.defaultCase) { // statements[i] is a case or a default case |
| this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block |
| if (this.preSwitchInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); |
| } |
| } |
| } |
| statementGenerateCode(currentScope, codeStream, statement); |
| } |
| } |
| boolean enumInSwitchExpression = resolvedType1.isEnum() && this instanceof SwitchExpression; |
| boolean isEnumSwitchWithoutDefaultCase = this.defaultCase == null && enumInSwitchExpression; |
| if (isEnumSwitchWithoutDefaultCase) { |
| // we want to force an line number entry to get an end position after the switch statement |
| if (this.preSwitchInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); |
| } |
| defaultLabel.place(); |
| /* a default case is not needed for enum if all enum values are used in the switch expression |
| * we need to handle the default case to throw an error (IncompatibleClassChangeError) in order |
| * to make the stack map consistent. All cases will return a value on the stack except the missing default |
| * case. |
| * There is no returned value for the default case so we handle it with an exception thrown. An |
| * IllegalClassChangeError seems legitimate as this would mean the enum type has been recompiled with more |
| * enum constants and the class that is using the switch on the enum has not been recompiled |
| */ |
| codeStream.newJavaLangIncompatibleClassChangeError(); |
| codeStream.dup(); |
| codeStream.invokeJavaLangIncompatibleClassChangeErrorDefaultConstructor(); |
| codeStream.athrow(); |
| } |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.mergedInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); |
| } |
| if (this.scope != currentScope) { |
| codeStream.exitUserScope(this.scope); |
| } |
| // place the trailing labels (for break and default case) |
| this.breakLabel.place(); |
| if (this.defaultCase == null && !enumInSwitchExpression) { |
| // we want to force an line number entry to get an end position after the switch statement |
| codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd, true); |
| defaultLabel.place(); |
| } |
| if (this instanceof SwitchExpression) { |
| TypeBinding switchResolveType = this.resolvedType; |
| if (this.expectedType() != null) { |
| switchResolveType = this.expectedType().erasure(); |
| } |
| boolean optimizedGoto = codeStream.lastAbruptCompletion == -1; |
| // if the last bytecode was an optimized goto (value is already on the stack) or an enum switch without default case, then we need to adjust the |
| // stack depth to reflect the fact that there is an value on the stack (return type of the switch expression) |
| codeStream.recordExpressionType(switchResolveType, optimizedGoto ? 0 : 1, optimizedGoto || isEnumSwitchWithoutDefaultCase); |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } finally { |
| if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block |
| } |
| } |
| protected void statementGenerateCode(BlockScope currentScope, CodeStream codeStream, Statement statement) { |
| statement.generateCode(this.scope, codeStream); |
| } |
| |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { |
| generateCode(currentScope, codeStream); // redirecting to statement part |
| } |
| @Override |
| public StringBuffer printStatement(int indent, StringBuffer output) { |
| |
| printIndent(indent, output).append("switch ("); //$NON-NLS-1$ |
| this.expression.printExpression(0, output).append(") {"); //$NON-NLS-1$ |
| if (this.statements != null) { |
| for (int i = 0; i < this.statements.length; i++) { |
| output.append('\n'); |
| if (this.statements[i] instanceof CaseStatement) { |
| this.statements[i].printStatement(indent, output); |
| } else { |
| this.statements[i].printStatement(indent+2, output); |
| } |
| } |
| } |
| output.append("\n"); //$NON-NLS-1$ |
| return printIndent(indent, output).append('}'); |
| } |
| |
| private int getNConstants() { |
| int n = 0; |
| for (int i = 0, l = this.statements.length; i < l; ++i) { |
| final Statement statement = this.statements[i]; |
| if (statement instanceof CaseStatement) { |
| Expression[] exprs = ((CaseStatement) statement).constantExpressions; |
| n += exprs != null ? exprs.length : 0; |
| } |
| } |
| return n; |
| } |
| protected void addSecretTryResultVariable() { |
| // do nothing |
| } |
| @Override |
| public void resolve(BlockScope upperScope) { |
| try { |
| boolean isEnumSwitch = false; |
| boolean isStringSwitch = false; |
| TypeBinding expressionType = this.expression.resolveType(upperScope); |
| CompilerOptions compilerOptions = upperScope.compilerOptions(); |
| if (expressionType != null) { |
| this.expression.computeConversion(upperScope, expressionType, expressionType); |
| checkType: { |
| if (!expressionType.isValidBinding()) { |
| expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon |
| break checkType; |
| } else if (expressionType.isBaseType()) { |
| if (this.expression.isConstantValueOfTypeAssignableToType(expressionType, TypeBinding.INT)) |
| break checkType; |
| if (expressionType.isCompatibleWith(TypeBinding.INT)) |
| break checkType; |
| } else if (expressionType.isEnum()) { |
| isEnumSwitch = true; |
| if (compilerOptions.complianceLevel < ClassFileConstants.JDK1_5) { |
| upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=360317 |
| } |
| break checkType; |
| } else if (upperScope.isBoxingCompatibleWith(expressionType, TypeBinding.INT)) { |
| this.expression.computeConversion(upperScope, TypeBinding.INT, expressionType); |
| break checkType; |
| } else if (compilerOptions.complianceLevel >= ClassFileConstants.JDK1_7 && expressionType.id == TypeIds.T_JavaLangString) { |
| isStringSwitch = true; |
| break checkType; |
| } |
| upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); |
| expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon |
| } |
| } |
| if (isStringSwitch) { |
| // the secret variable should be created before iterating over the switch's statements that could |
| // create more locals. This must be done to prevent overlapping of locals |
| // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=356002 |
| this.dispatchStringCopy = new LocalVariableBinding(SecretStringVariableName, upperScope.getJavaLangString(), ClassFileConstants.AccDefault, false); |
| upperScope.addLocalVariable(this.dispatchStringCopy); |
| this.dispatchStringCopy.setConstant(Constant.NotAConstant); |
| this.dispatchStringCopy.useFlag = LocalVariableBinding.USED; |
| } |
| if (this.statements != null) { |
| this.scope = new BlockScope(upperScope); |
| // addSecretTryResultVariable(); |
| int length; |
| // collection of cases is too big but we will only iterate until caseCount |
| this.cases = new CaseStatement[length = this.statements.length]; |
| this.nConstants = getNConstants(); |
| if (!isStringSwitch) { |
| this.constants = new int[this.nConstants]; |
| this.constMapping = new int[this.nConstants]; |
| } else { |
| this.stringConstants = new String[this.nConstants]; |
| this.constMapping = new int[this.nConstants]; |
| } |
| int counter = 0; |
| int caseCounter = 0; |
| for (int i = 0; i < length; i++) { |
| Constant[] constantsList; |
| int[] caseIndex = new int[this.nConstants]; |
| final Statement statement = this.statements[i]; |
| if (!(statement instanceof CaseStatement)) { |
| statement.resolve(this.scope); |
| continue; |
| } |
| if ((constantsList = statement.resolveCase(this.scope, expressionType, this)) != Constant.NotAConstantList) { |
| for (Constant con : constantsList) { |
| if (con == Constant.NotAConstant) |
| continue; |
| if (!isStringSwitch) { |
| int key = con.intValue(); |
| //----check for duplicate case statement------------ |
| for (int j = 0; j < counter; j++) { |
| if (this.constants[j] == key) { |
| reportDuplicateCase((CaseStatement) statement, this.cases[caseIndex[j]], length); |
| } |
| } |
| this.constants[counter] = key; |
| } else { |
| String key = con.stringValue(); |
| //----check for duplicate case statement------------ |
| for (int j = 0; j < counter; j++) { |
| if (this.stringConstants[j].equals(key)) { |
| reportDuplicateCase((CaseStatement) statement, this.cases[caseIndex[j]], length); |
| } |
| } |
| this.stringConstants[counter] = key; |
| } |
| this.constMapping[counter] = counter; |
| caseIndex[counter] = caseCounter; |
| counter++; |
| } |
| } |
| caseCounter++; |
| } |
| if (length != counter) { // resize constants array |
| if (!isStringSwitch) { |
| System.arraycopy(this.constants, 0, this.constants = new int[counter], 0, counter); |
| } else { |
| System.arraycopy(this.stringConstants, 0, this.stringConstants = new String[counter], 0, counter); |
| } |
| System.arraycopy(this.constMapping, 0, this.constMapping = new int[counter], 0, counter); |
| } |
| } else { |
| if ((this.bits & UndocumentedEmptyBlock) != 0) { |
| upperScope.problemReporter().undocumentedEmptyBlock(this.blockStart, this.sourceEnd); |
| } |
| } |
| reportMixingCaseTypes(); |
| // check default case for all kinds of switch: |
| if (this.defaultCase == null) { |
| if (ignoreMissingDefaultCase(compilerOptions, isEnumSwitch)) { |
| if (isEnumSwitch) { |
| upperScope.methodScope().hasMissingSwitchDefault = true; |
| } |
| } else { |
| upperScope.problemReporter().missingDefaultCase(this, isEnumSwitch, expressionType); |
| } |
| } |
| // for enum switch, check if all constants are accounted for (perhaps depending on existence of a default case) |
| if (isEnumSwitch && compilerOptions.complianceLevel >= ClassFileConstants.JDK1_5) { |
| if (this.defaultCase == null || compilerOptions.reportMissingEnumCaseDespiteDefault) { |
| int constantCount = this.constants == null ? 0 : this.constants.length; // could be null if no case statement |
| if (constantCount >= this.caseCount |
| && constantCount != ((ReferenceBinding)expressionType).enumConstantCount()) { |
| FieldBinding[] enumFields = ((ReferenceBinding)expressionType.erasure()).fields(); |
| for (int i = 0, max = enumFields.length; i <max; i++) { |
| FieldBinding enumConstant = enumFields[i]; |
| if ((enumConstant.modifiers & ClassFileConstants.AccEnum) == 0) continue; |
| findConstant : { |
| for (int j = 0; j < constantCount; j++) { |
| if ((enumConstant.id + 1) == this.constants[j]) // zero should not be returned see bug 141810 |
| break findConstant; |
| } |
| // enum constant did not get referenced from switch |
| boolean suppress = (this.defaultCase != null && (this.defaultCase.bits & DocumentedCasesOmitted) != 0); |
| if (!suppress) { |
| reportMissingEnumConstantCase(upperScope, enumConstant); |
| } |
| } |
| } |
| } |
| } |
| } |
| } finally { |
| if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block |
| } |
| } |
| protected void reportMissingEnumConstantCase(BlockScope upperScope, FieldBinding enumConstant) { |
| upperScope.problemReporter().missingEnumConstantCase(this, enumConstant); |
| } |
| protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions, boolean isEnumSwitch) { |
| return compilerOptions.getSeverity(CompilerOptions.MissingDefaultCase) == ProblemSeverities.Ignore; |
| } |
| @Override |
| public boolean isTrulyExpression() { |
| return false; |
| } |
| private void reportMixingCaseTypes() { |
| if (this.caseCount == 0) { |
| this.switchLabeledRules = this.defaultCase != null ? this.defaultCase.isExpr : this.switchLabeledRules; |
| return; |
| } |
| boolean isExpr = this.switchLabeledRules = this.cases[0].isExpr; |
| for (int i = 1, l = this.caseCount; i < l; ++i) { |
| if (this.cases[i].isExpr != isExpr) { |
| this.scope.problemReporter().switchExpressionMixedCase(this.cases[i]); |
| return; |
| } |
| } |
| if (this.defaultCase != null && this.defaultCase.isExpr != isExpr) { |
| this.scope.problemReporter().switchExpressionMixedCase(this.defaultCase); |
| } |
| } |
| private void reportDuplicateCase(final CaseStatement duplicate, final CaseStatement original, int length) { |
| if (this.duplicateCaseStatements == null) { |
| this.scope.problemReporter().duplicateCase(original); |
| if (duplicate != original) |
| this.scope.problemReporter().duplicateCase(duplicate); |
| this.duplicateCaseStatements = new CaseStatement[length]; |
| this.duplicateCaseStatements[this.duplicateCaseStatementsCounter++] = original; |
| if (duplicate != original) |
| this.duplicateCaseStatements[this.duplicateCaseStatementsCounter++] = duplicate; |
| } else { |
| boolean found = false; |
| searchReportedDuplicate: for (int k = 2; k < this.duplicateCaseStatementsCounter; k++) { |
| if (this.duplicateCaseStatements[k] == duplicate) { |
| found = true; |
| break searchReportedDuplicate; |
| } |
| } |
| if (!found) { |
| this.scope.problemReporter().duplicateCase(duplicate); |
| this.duplicateCaseStatements[this.duplicateCaseStatementsCounter++] = duplicate; |
| } |
| } |
| } |
| |
| @Override |
| public void traverse( |
| ASTVisitor visitor, |
| BlockScope blockScope) { |
| |
| if (visitor.visit(this, blockScope)) { |
| this.expression.traverse(visitor, blockScope); |
| if (this.statements != null) { |
| int statementsLength = this.statements.length; |
| for (int i = 0; i < statementsLength; i++) |
| this.statements[i].traverse(visitor, this.scope); |
| } |
| } |
| visitor.endVisit(this, blockScope); |
| } |
| |
| /** |
| * Dispatch the call on its last statement. |
| */ |
| @Override |
| public void branchChainTo(BranchLabel label) { |
| |
| // in order to improve debug attributes for stepping (11431) |
| // we want to inline the jumps to #breakLabel which already got |
| // generated (if any), and have them directly branch to a better |
| // location (the argument label). |
| // we know at this point that the breakLabel already got placed |
| if (this.breakLabel.forwardReferenceCount() > 0) { |
| label.becomeDelegateFor(this.breakLabel); |
| } |
| } |
| |
| @Override |
| public boolean doesNotCompleteNormally() { |
| if (this.statements == null || this.statements.length == 0) |
| return false; |
| for (int i = 0, length = this.statements.length; i < length; i++) { |
| if (this.statements[i].breaksOut(null)) |
| return false; |
| } |
| return this.statements[this.statements.length - 1].doesNotCompleteNormally(); |
| } |
| |
| @Override |
| public boolean completesByContinue() { |
| if (this.statements == null || this.statements.length == 0) |
| return false; |
| for (int i = 0, length = this.statements.length; i < length; i++) { |
| if (this.statements[i].completesByContinue()) |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean canCompleteNormally() { |
| if (this.statements == null || this.statements.length == 0) |
| return true; |
| if (!this.switchLabeledRules) { // switch labeled statement group |
| if (this.statements[this.statements.length - 1].canCompleteNormally()) |
| return true; // last statement as well as last switch label after blocks if exists. |
| if (this.defaultCase == null) |
| return true; |
| for (int i = 0, length = this.statements.length; i < length; i++) { |
| if (this.statements[i].breaksOut(null)) |
| return true; |
| } |
| } else { |
| // switch block consists of switch rules |
| for (Statement stmt : this.statements) { |
| if (stmt instanceof CaseStatement) |
| continue; // skip case |
| if (this.defaultCase == null) |
| return true; |
| if (stmt instanceof Expression) |
| return true; |
| if (stmt.canCompleteNormally()) |
| return true; |
| if (stmt instanceof YieldStatement && ((YieldStatement) stmt).isImplicit) // note: artificially introduced |
| return true; |
| if (stmt instanceof Block) { |
| Block block = (Block) stmt; |
| if (block.canCompleteNormally()) |
| return true; |
| if (block.breaksOut(null)) |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean continueCompletes() { |
| if (this.statements == null || this.statements.length == 0) |
| return false; |
| for (int i = 0, length = this.statements.length; i < length; i++) { |
| if (this.statements[i].continueCompletes()) |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public StringBuffer printExpression(int indent, StringBuffer output) { |
| return printStatement(indent, output); |
| } |
| } |