use of org.eclipse.jdt.internal.compiler.codegen.CaseLabel in project bazel-jdt-java-toolchain by salesforce.
the class SwitchStatement method generateCode.
/**
* 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 && !this.isNonTraditional) {
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.otherConstants == null ? 0 : this.otherConstants.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 (this.containsPatterns) {
generateCodeSwitchPatternPrologue(currentScope, codeStream);
valueRequired = true;
} else if (resolvedType1.isEnum()) {
// go through the translation table
codeStream.invoke(Opcodes.OPC_invokestatic, this.synthetic, null);
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)) {
// 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];
CaseStatement caseStatement = null;
if ((caseIndex < constantCount) && (statement == this.cases[caseIndex])) {
// statements[i] is a case
// record entering in a switch case block
this.scope.enclosingCase = this.cases[caseIndex];
if (this.preSwitchInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex);
}
caseStatement = (CaseStatement) statement;
patternCaseExitPreviousCaseScope(codeStream, caseIndex);
caseIndex++;
} else {
if (statement == this.defaultCase) {
// statements[i] is a case or a default case
// record entering in a switch case block
this.scope.enclosingCase = this.defaultCase;
if (this.preSwitchInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex);
}
}
}
statementGenerateCode(currentScope, codeStream, statement);
generateCodePatternCaseEpilogue(codeStream, caseIndex, caseStatement);
}
}
boolean enumInSwitchExpression = resolvedType1.isEnum() && this instanceof SwitchExpression;
boolean isEnumSwitchWithoutDefaultCase = this.defaultCase == null && enumInSwitchExpression;
CompilerOptions compilerOptions = this.scope != null ? this.scope.compilerOptions() : null;
boolean isPatternSwitchSealedWithoutDefaultCase = this.defaultCase == null && compilerOptions != null && this.containsPatterns && JavaFeature.SEALED_CLASSES.isSupported(compilerOptions) && JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) && this.expression.resolvedType instanceof ReferenceBinding && ((ReferenceBinding) this.expression.resolvedType).isSealed();
if (isEnumSwitchWithoutDefaultCase || isPatternSwitchSealedWithoutDefaultCase) {
// 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);
}
generateCodeSwitchPatternEpilogue(codeStream);
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 || isPatternSwitchSealedWithoutDefaultCase)) {
// 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 {
// no longer inside switch case block
if (this.scope != null)
this.scope.enclosingCase = null;
}
}
use of org.eclipse.jdt.internal.compiler.codegen.CaseLabel in project bazel-jdt-java-toolchain by salesforce.
the class SwitchStatement method generateCodeForStringSwitch.
/**
* 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 // $NON-NLS-1$
"StringSwitchCase :\n" + "case " + this.hashCode + ":(" + this.string + // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
")\n";
}
}
/*
* 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.otherConstants.length : 0;
BranchLabel[] sourceCaseLabels = this.<BranchLabel>gatherLabels(codeStream, new BranchLabel[this.nConstants], BranchLabel::new);
// may have to shrink later if multiple strings hash to same code.
StringSwitchCase[] stringCases = new StringSwitchCase[constSize];
CaseLabel[] hashCodeCaseLabels = new CaseLabel[constSize];
// hashCode() values.
this.constants = new int[constSize];
for (int i = 0; i < constSize; i++) {
String literal = this.otherConstants[i].c.stringValue();
stringCases[i] = new StringSwitchCase(literal.hashCode(), literal, 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);
}
// hash code are sorted already anyways.
int[] sortedIndexes = new int[uniqHashCount];
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);
// leaves string on operand stack
codeStream.store(this.dispatchStringCopy, true);
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
// record entering in a switch case block
this.scope.enclosingCase = this.cases[caseIndex];
if (this.preSwitchInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex);
}
if (statement == this.defaultCase) {
// statements[i] is a case or a default case
// branch label gets placed by generateCode below.
defaultCaseLabel.place();
}
caseIndex++;
} else {
if (statement == this.defaultCase) {
// statements[i] is a case or a default case
// branch label gets placed by generateCode below.
defaultCaseLabel.place();
// record entering in a switch case block
this.scope.enclosingCase = this.defaultCase;
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 {
// no longer inside switch case block
if (this.scope != null)
this.scope.enclosingCase = null;
}
}
Aggregations