Search in sources :

Example 1 with CaseLabel

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;
    }
}
Also used : SourceTypeBinding(org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding) TypeBinding(org.eclipse.jdt.internal.compiler.lookup.TypeBinding) CompilerOptions(org.eclipse.jdt.internal.compiler.impl.CompilerOptions) CaseLabel(org.eclipse.jdt.internal.compiler.codegen.CaseLabel) ReferenceBinding(org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding)

Example 2 with CaseLabel

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;
    }
}
Also used : BranchLabel(org.eclipse.jdt.internal.compiler.codegen.BranchLabel) SourceTypeBinding(org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding) TypeBinding(org.eclipse.jdt.internal.compiler.lookup.TypeBinding) CaseLabel(org.eclipse.jdt.internal.compiler.codegen.CaseLabel)

Aggregations

CaseLabel (org.eclipse.jdt.internal.compiler.codegen.CaseLabel)2 SourceTypeBinding (org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding)2 TypeBinding (org.eclipse.jdt.internal.compiler.lookup.TypeBinding)2 BranchLabel (org.eclipse.jdt.internal.compiler.codegen.BranchLabel)1 CompilerOptions (org.eclipse.jdt.internal.compiler.impl.CompilerOptions)1 ReferenceBinding (org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding)1