diff options
Diffstat (limited to 'org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java')
-rw-r--r-- | org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java | 327 |
1 files changed, 268 insertions, 59 deletions
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java index 15dbd6bfda..a694b0a101 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 IBM Corporation and others. + * 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 @@ -43,7 +43,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; @SuppressWarnings("rawtypes") -public class SwitchStatement extends Statement { +public class SwitchStatement extends Expression { public Expression expression; public Statement[] statements; @@ -55,12 +55,16 @@ public class SwitchStatement extends Statement { 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$ @@ -76,6 +80,38 @@ public class SwitchStatement extends Statement { 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 instanceof Block) { + Block block = (Block) stmt; + if (block.doesNotCompleteNormally()) { + return BREAKING; + } + // else add an implicit break + BreakStatement breakStatement = new BreakStatement(null, block.sourceEnd -1, block.sourceEnd); + breakStatement.isImplicit = true; + + 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 + } @Override public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { try { @@ -120,7 +156,7 @@ public class SwitchStatement extends Statement { complaintLevel = initialComplaintLevel; // reset complaint fallThroughState = CASE; } else { - fallThroughState = FALLTHROUGH; // reset below if needed + 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); @@ -130,6 +166,7 @@ public class SwitchStatement extends Statement { switchContext.expireNullCheckedFieldInfo(); } } + completeNormallyCheck(currentScope); } final TypeBinding resolvedTypeBinding = this.expression.resolvedType; @@ -199,33 +236,77 @@ public class SwitchStatement extends Statement { "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; - - StringSwitchCase [] stringCases = new StringSwitchCase[this.caseCount]; // may have to shrink later if multiple strings hash to same code. - BranchLabel[] sourceCaseLabels = new BranchLabel[this.caseCount]; - CaseLabel [] hashCodeCaseLabels = new CaseLabel[this.caseCount]; - this.constants = new int[this.caseCount]; // hashCode() values. - for (int i = 0, max = this.caseCount; i < max; i++) { - this.cases[i].targetLabel = (sourceCaseLabels[i] = new BranchLabel(codeStream)); // A branch label, not a case label. - sourceCaseLabels[i].tagBits |= BranchLabel.USED; - stringCases[i] = new StringSwitchCase(this.stringConstants[i].hashCode(), this.stringConstants[i], sourceCaseLabels[i]); + int constSize = hasCases ? this.stringConstants.length : 0; + BranchLabel[] sourceCaseLabels; + if (currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK12) { + for (int i = 0, max = this.caseCount; i < max; i++) { + int l = this.cases[i].constantExpressions.length; + this.cases[i].targetLabels = new BranchLabel[l]; + } + sourceCaseLabels = new BranchLabel[this.nConstants]; + int j = 0; + for (int i = 0, max = this.caseCount; i < max; i++) { + CaseStatement stmt = this.cases[i]; + for (int k = 0, l = stmt.constantExpressions.length; k < l; ++k) { + stmt.targetLabels[k] = (sourceCaseLabels[j] = new BranchLabel(codeStream)); + sourceCaseLabels[j++].tagBits |= BranchLabel.USED; + } + } + } else { + sourceCaseLabels = new BranchLabel[this.caseCount]; + for (int i = 0, max = this.caseCount; i < max; i++) { + this.cases[i].targetLabel = (sourceCaseLabels[i] = new BranchLabel(codeStream)); // A branch label, not a case label. + sourceCaseLabels[i].tagBits |= BranchLabel.USED; + } + } + 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 = this.caseCount; i < length; ++i) { + 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 != this.caseCount) { // multiple keys hashed to the same value. + 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); } @@ -252,7 +333,7 @@ public class SwitchStatement extends Statement { codeStream.invokeStringHashCode(); if (hasCases) { codeStream.lookupswitch(defaultCaseLabel, this.constants, sortedIndexes, hashCodeCaseLabels); - for (int i = 0, j = 0, max = this.caseCount; i < max; i++) { + for (int i = 0, j = 0, max = constSize; i < max; i++) { int hashCode = stringCases[i].hashCode; if (i == 0 || hashCode != lastHashCode) { lastHashCode = hashCode; @@ -291,7 +372,7 @@ public class SwitchStatement extends Statement { } } } - statement.generateCode(this.scope, codeStream); + statementGenerateCode(currentScope, codeStream, statement); } } @@ -312,6 +393,13 @@ public class SwitchStatement extends Statement { 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 @@ -339,11 +427,32 @@ public class SwitchStatement extends Statement { // prepare the labels and constants this.breakLabel.initialize(codeStream); - CaseLabel[] caseLabels = new CaseLabel[this.caseCount]; - for (int i = 0, max = this.caseCount; i < max; i++) { - this.cases[i].targetLabel = (caseLabels[i] = new CaseLabel(codeStream)); - caseLabels[i].tagBits |= BranchLabel.USED; + int constantCount = this.constants == null ? 0 : this.constants.length; + int nCaseLabels = 0; + CaseLabel[] caseLabels; + if (currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK12) { + for (int i = 0, max = this.caseCount; i < max; i++) { + int l = this.cases[i].constantExpressions.length; + nCaseLabels += l; + this.cases[i].targetLabels = new BranchLabel[l]; + } + caseLabels = new CaseLabel[nCaseLabels]; + int j = 0; + for (int i = 0, max = this.caseCount; i < max; i++) { + CaseStatement stmt = this.cases[i]; + for (int k = 0, l = stmt.constantExpressions.length; k < l; ++k) { + stmt.targetLabels[k] = (caseLabels[j] = new CaseLabel(codeStream)); + caseLabels[j++].tagBits |= BranchLabel.USED; + } + } + } else { + caseLabels = new CaseLabel[this.caseCount]; + for (int i = 0, max = this.caseCount; i < max; i++) { + this.cases[i].targetLabel = (caseLabels[i] = new CaseLabel(codeStream)); + caseLabels[i].tagBits |= BranchLabel.USED; + } } + CaseLabel defaultLabel = new CaseLabel(codeStream); final boolean hasCases = this.caseCount != 0; if (hasCases) defaultLabel.tagBits |= BranchLabel.USED; @@ -351,19 +460,20 @@ public class SwitchStatement extends Statement { this.defaultCase.targetLabel = defaultLabel; } - final TypeBinding resolvedType = this.expression.resolvedType; + final TypeBinding resolvedType1 = this.expression.resolvedType; boolean valueRequired = false; - if (resolvedType.isEnum()) { + 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(resolvedType.constantPoolName()); + 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 @@ -371,18 +481,18 @@ public class SwitchStatement extends Statement { } // generate the appropriate switch table/lookup bytecode if (hasCases) { - int[] sortedIndexes = new int[this.caseCount]; + 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 < this.caseCount; i++) { + for (int i = 0; i < constantCount; i++) { sortedIndexes[i] = i; } int[] localKeysCopy; - System.arraycopy(this.constants, 0, (localKeysCopy = new int[this.caseCount]), 0, this.caseCount); - CodeStream.sort(localKeysCopy, 0, this.caseCount - 1, sortedIndexes); + System.arraycopy(this.constants, 0, (localKeysCopy = new int[constantCount]), 0, constantCount); + CodeStream.sort(localKeysCopy, 0, constantCount - 1, sortedIndexes); - int max = localKeysCopy[this.caseCount - 1]; + int max = localKeysCopy[constantCount - 1]; int min = localKeysCopy[0]; - if ((long) (this.caseCount * 2.5) > ((long) max - (long) min)) { + 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 @@ -396,6 +506,7 @@ public class SwitchStatement extends Statement { max, this.constants, sortedIndexes, + this.constMapping, caseLabels); } } else { @@ -411,7 +522,7 @@ public class SwitchStatement extends Statement { 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 + 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); @@ -425,8 +536,29 @@ public class SwitchStatement extends Statement { } } } - statement.generateCode(this.scope, codeStream); + 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) { @@ -438,18 +570,35 @@ public class SwitchStatement extends Statement { } // place the trailing labels (for break and default case) this.breakLabel.place(); - if (this.defaultCase == null) { + 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$ @@ -468,6 +617,19 @@ public class SwitchStatement extends Statement { 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)) { + continue; + } + CaseStatement caseStmt = (CaseStatement) statement; + n += caseStmt.constantExpressions != null ? caseStmt.constantExpressions.length : + caseStmt.constantExpression != null ? 1 : 0; + } + return n; + } @Override public void resolve(BlockScope upperScope) { try { @@ -517,34 +679,49 @@ public class SwitchStatement extends Statement { 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[length]; + this.constants = new int[this.nConstants]; + this.constMapping = new int[this.nConstants]; } else { - this.stringConstants = new String[length]; + this.stringConstants = new String[this.nConstants]; + this.constMapping = new int[this.nConstants]; } int counter = 0; for (int i = 0; i < length; i++) { - Constant constant; + Constant[] constantsList; final Statement statement = this.statements[i]; - if ((constant = statement.resolveCase(this.scope, expressionType, this)) != Constant.NotAConstant) { - if (!isStringSwitch) { - int key = constant.intValue(); - //----check for duplicate case statement------------ - for (int j = 0; j < counter; j++) { - if (this.constants[j] == key) { - reportDuplicateCase((CaseStatement) statement, this.cases[j], length); + 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[j], length); + } } - } - this.constants[counter++] = key; - } else { - String key = constant.stringValue(); - //----check for duplicate case statement------------ - for (int j = 0; j < counter; j++) { - if (this.stringConstants[j].equals(key)) { - reportDuplicateCase((CaseStatement) statement, this.cases[j], length); + this.constants[counter] = key; + this.constMapping[counter] = counter; + counter++; + } 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[j], length); + } } + this.stringConstants[counter] = key; + this.constMapping[counter] = counter; + counter++; } - this.stringConstants[counter++] = key; } } } @@ -554,15 +731,17 @@ public class SwitchStatement extends Statement { } 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 (compilerOptions.getSeverity(CompilerOptions.MissingDefaultCase) == ProblemSeverities.Ignore) { + if (ignoreMissingDefaultCase(compilerOptions, isEnumSwitch)) { if (isEnumSwitch) { upperScope.methodScope().hasMissingSwitchDefault = true; } @@ -574,21 +753,21 @@ public class SwitchStatement extends Statement { 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 - && this.caseCount != ((ReferenceBinding)expressionType).enumConstantCount()) { + 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 < this.caseCount; j++) { + 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) { - upperScope.problemReporter().missingEnumConstantCase(this, enumConstant); + reportMissingEnumConstantCase(upperScope, enumConstant); } } } @@ -599,7 +778,32 @@ public class SwitchStatement extends Statement { 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); @@ -675,4 +879,9 @@ public class SwitchStatement extends Statement { } return false; } + + @Override + public StringBuffer printExpression(int indent, StringBuffer output) { + return printStatement(indent, output); + } } |