001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.compiler.ast;
011:
012: import org.eclipse.jdt.internal.compiler.ASTVisitor;
013: import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
014: import org.eclipse.jdt.internal.compiler.codegen.*;
015: import org.eclipse.jdt.internal.compiler.flow.*;
016: import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
017: import org.eclipse.jdt.internal.compiler.impl.Constant;
018: import org.eclipse.jdt.internal.compiler.lookup.*;
019: import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
020:
021: public class SwitchStatement extends Statement {
022:
023: public Expression expression;
024: public Statement[] statements;
025: public BlockScope scope;
026: public int explicitDeclarations;
027: public BranchLabel breakLabel;
028: public CaseStatement[] cases;
029: public CaseStatement defaultCase;
030: public int blockStart;
031: public int caseCount;
032: int[] constants;
033:
034: // fallthrough
035: public final static int CASE = 0;
036: public final static int FALLTHROUGH = 1;
037: public final static int ESCAPING = 2;
038:
039: public SyntheticMethodBinding synthetic; // use for switch on enums types
040:
041: // for local variables table attributes
042: int preSwitchInitStateIndex = -1;
043: int mergedInitStateIndex = -1;
044:
045: public FlowInfo analyseCode(BlockScope currentScope,
046: FlowContext flowContext, FlowInfo flowInfo) {
047:
048: try {
049: flowInfo = expression.analyseCode(currentScope,
050: flowContext, flowInfo);
051: SwitchFlowContext switchContext = new SwitchFlowContext(
052: flowContext, this , (breakLabel = new BranchLabel()));
053:
054: // analyse the block by considering specially the case/default statements (need to bind them
055: // to the entry point)
056: FlowInfo caseInits = FlowInfo.DEAD_END;
057: // in case of statements before the first case
058: preSwitchInitStateIndex = currentScope.methodScope()
059: .recordInitializationStates(flowInfo);
060: int caseIndex = 0;
061: if (statements != null) {
062: boolean didAlreadyComplain = false;
063: int fallThroughState = CASE;
064: for (int i = 0, max = statements.length; i < max; i++) {
065: Statement statement = statements[i];
066: if ((caseIndex < caseCount)
067: && (statement == cases[caseIndex])) { // statement is a case
068: this .scope.enclosingCase = cases[caseIndex]; // record entering in a switch case block
069: caseIndex++;
070: if (fallThroughState == FALLTHROUGH
071: && (statement.bits & ASTNode.DocumentedFallthrough) == 0) { // the case is not fall-through protected by a line comment
072: scope.problemReporter()
073: .possibleFallThroughCase(
074: this .scope.enclosingCase);
075: }
076: caseInits = caseInits.mergedWith(flowInfo
077: .unconditionalInits());
078: didAlreadyComplain = false; // reset complaint
079: fallThroughState = CASE;
080: } else if (statement == defaultCase) { // statement is the default case
081: this .scope.enclosingCase = defaultCase; // record entering in a switch case block
082: if (fallThroughState == FALLTHROUGH
083: && (statement.bits & ASTNode.DocumentedFallthrough) == 0) {
084: scope.problemReporter()
085: .possibleFallThroughCase(
086: this .scope.enclosingCase);
087: }
088: caseInits = caseInits.mergedWith(flowInfo
089: .unconditionalInits());
090: didAlreadyComplain = false; // reset complaint
091: fallThroughState = CASE;
092: } else {
093: fallThroughState = FALLTHROUGH; // reset below if needed
094: }
095: if (!statement.complainIfUnreachable(caseInits,
096: scope, didAlreadyComplain)) {
097: caseInits = statement.analyseCode(scope,
098: switchContext, caseInits);
099: if (caseInits == FlowInfo.DEAD_END) {
100: fallThroughState = ESCAPING;
101: }
102: } else {
103: didAlreadyComplain = true;
104: }
105: }
106: }
107:
108: final TypeBinding resolvedTypeBinding = this .expression.resolvedType;
109: if (caseCount > 0 && resolvedTypeBinding.isEnum()) {
110: final SourceTypeBinding sourceTypeBinding = this .scope
111: .classScope().referenceContext.binding;
112: this .synthetic = sourceTypeBinding
113: .addSyntheticMethodForSwitchEnum(resolvedTypeBinding);
114: }
115: // if no default case, then record it may jump over the block directly to the end
116: if (defaultCase == null) {
117: // only retain the potential initializations
118: flowInfo.addPotentialInitializationsFrom(caseInits
119: .mergedWith(switchContext.initsOnBreak));
120: mergedInitStateIndex = currentScope.methodScope()
121: .recordInitializationStates(flowInfo);
122: return flowInfo;
123: }
124:
125: // merge all branches inits
126: FlowInfo mergedInfo = caseInits
127: .mergedWith(switchContext.initsOnBreak);
128: mergedInitStateIndex = currentScope.methodScope()
129: .recordInitializationStates(mergedInfo);
130: return mergedInfo;
131: } finally {
132: if (this .scope != null)
133: this .scope.enclosingCase = null; // no longer inside switch case block
134: }
135: }
136:
137: /**
138: * Switch code generation
139: *
140: * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
141: * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
142: */
143: public void generateCode(BlockScope currentScope,
144: CodeStream codeStream) {
145:
146: try {
147: if ((bits & IsReachable) == 0) {
148: return;
149: }
150: int pc = codeStream.position;
151:
152: // prepare the labels and constants
153: this .breakLabel.initialize(codeStream);
154: CaseLabel[] caseLabels = new CaseLabel[this .caseCount];
155: boolean needSwitch = this .caseCount != 0;
156: for (int i = 0; i < caseCount; i++) {
157: cases[i].targetLabel = (caseLabels[i] = new CaseLabel(
158: codeStream));
159: caseLabels[i].tagBits |= BranchLabel.USED;
160: }
161: CaseLabel defaultLabel = new CaseLabel(codeStream);
162: if (needSwitch)
163: defaultLabel.tagBits |= BranchLabel.USED;
164: if (defaultCase != null) {
165: defaultCase.targetLabel = defaultLabel;
166: }
167:
168: final TypeBinding resolvedType = this .expression.resolvedType;
169: if (resolvedType.isEnum()) {
170: if (needSwitch) {
171: // go through the translation table
172: codeStream.invokestatic(this .synthetic);
173: expression.generateCode(currentScope, codeStream,
174: true);
175: // get enum constant ordinal()
176: codeStream.invokeEnumOrdinal(resolvedType
177: .constantPoolName());
178: codeStream.iaload();
179: } else {
180: // no need to go through the translation table
181: expression.generateCode(currentScope, codeStream,
182: false);
183: }
184: } else {
185: // generate expression
186: expression.generateCode(currentScope, codeStream,
187: needSwitch); // value required (switch without cases)
188: }
189: // generate the appropriate switch table/lookup bytecode
190: if (needSwitch) {
191: int[] sortedIndexes = new int[this .caseCount];
192: // we sort the keys to be able to generate the code for tableswitch or lookupswitch
193: for (int i = 0; i < caseCount; i++) {
194: sortedIndexes[i] = i;
195: }
196: int[] localKeysCopy;
197: System.arraycopy(this .constants, 0,
198: (localKeysCopy = new int[this .caseCount]), 0,
199: this .caseCount);
200: CodeStream.sort(localKeysCopy, 0, this .caseCount - 1,
201: sortedIndexes);
202:
203: int max = localKeysCopy[this .caseCount - 1];
204: int min = localKeysCopy[0];
205: if ((long) (caseCount * 2.5) > ((long) max - (long) min)) {
206:
207: // work-around 1.3 VM bug, if max>0x7FFF0000, must use lookup bytecode
208: // see http://dev.eclipse.org/bugs/show_bug.cgi?id=21557
209: if (max > 0x7FFF0000
210: && currentScope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_4) {
211: codeStream.lookupswitch(defaultLabel,
212: this .constants, sortedIndexes,
213: caseLabels);
214:
215: } else {
216: codeStream.tableswitch(defaultLabel, min, max,
217: this .constants, sortedIndexes,
218: caseLabels);
219: }
220: } else {
221: codeStream.lookupswitch(defaultLabel,
222: this .constants, sortedIndexes, caseLabels);
223: }
224: codeStream.updateLastRecordedEndPC(this .scope,
225: codeStream.position);
226: }
227:
228: // generate the switch block statements
229: int caseIndex = 0;
230: if (this .statements != null) {
231: for (int i = 0, maxCases = this .statements.length; i < maxCases; i++) {
232: Statement statement = this .statements[i];
233: if ((caseIndex < this .caseCount)
234: && (statement == this .cases[caseIndex])) { // statements[i] is a case
235: this .scope.enclosingCase = this .cases[caseIndex]; // record entering in a switch case block
236: if (preSwitchInitStateIndex != -1) {
237: codeStream
238: .removeNotDefinitelyAssignedVariables(
239: currentScope,
240: preSwitchInitStateIndex);
241: }
242: caseIndex++;
243: } else {
244: if (statement == this .defaultCase) { // statements[i] is a case or a default case
245: this .scope.enclosingCase = this .defaultCase; // record entering in a switch case block
246: if (preSwitchInitStateIndex != -1) {
247: codeStream
248: .removeNotDefinitelyAssignedVariables(
249: currentScope,
250: preSwitchInitStateIndex);
251: }
252: }
253: }
254: statement.generateCode(scope, codeStream);
255: }
256: }
257: // May loose some local variable initializations : affecting the local variable attributes
258: if (mergedInitStateIndex != -1) {
259: codeStream.removeNotDefinitelyAssignedVariables(
260: currentScope, mergedInitStateIndex);
261: codeStream.addDefinitelyAssignedVariables(currentScope,
262: mergedInitStateIndex);
263: }
264: if (scope != currentScope) {
265: codeStream.exitUserScope(this .scope);
266: }
267: // place the trailing labels (for break and default case)
268: this .breakLabel.place();
269: if (defaultCase == null) {
270: // we want to force an line number entry to get an end position after the switch statement
271: codeStream.recordPositionsFrom(codeStream.position,
272: this .sourceEnd, true);
273: defaultLabel.place();
274: }
275: codeStream.recordPositionsFrom(pc, this .sourceStart);
276: } finally {
277: if (this .scope != null)
278: this .scope.enclosingCase = null; // no longer inside switch case block
279: }
280: }
281:
282: public StringBuffer printStatement(int indent, StringBuffer output) {
283:
284: printIndent(indent, output).append("switch ("); //$NON-NLS-1$
285: expression.printExpression(0, output).append(") {"); //$NON-NLS-1$
286: if (statements != null) {
287: for (int i = 0; i < statements.length; i++) {
288: output.append('\n');
289: if (statements[i] instanceof CaseStatement) {
290: statements[i].printStatement(indent, output);
291: } else {
292: statements[i].printStatement(indent + 2, output);
293: }
294: }
295: }
296: output.append("\n"); //$NON-NLS-1$
297: return printIndent(indent, output).append('}');
298: }
299:
300: public void resolve(BlockScope upperScope) {
301:
302: try {
303: boolean isEnumSwitch = false;
304: TypeBinding expressionType = expression
305: .resolveType(upperScope);
306: if (expressionType != null) {
307: expression.computeConversion(upperScope,
308: expressionType, expressionType);
309: checkType: {
310: if (expressionType.isBaseType()) {
311: if (expression
312: .isConstantValueOfTypeAssignableToType(
313: expressionType, TypeBinding.INT))
314: break checkType;
315: if (expressionType
316: .isCompatibleWith(TypeBinding.INT))
317: break checkType;
318: } else if (expressionType.isEnum()) {
319: isEnumSwitch = true;
320: break checkType;
321: } else if (upperScope.isBoxingCompatibleWith(
322: expressionType, TypeBinding.INT)) {
323: expression.computeConversion(upperScope,
324: TypeBinding.INT, expressionType);
325: break checkType;
326: }
327: upperScope.problemReporter().incorrectSwitchType(
328: expression, expressionType);
329: expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon
330: }
331: }
332: if (statements != null) {
333: scope = /*explicitDeclarations == 0 ? upperScope : */new BlockScope(
334: upperScope);
335: int length;
336: // collection of cases is too big but we will only iterate until caseCount
337: cases = new CaseStatement[length = statements.length];
338: this .constants = new int[length];
339: CaseStatement[] duplicateCaseStatements = null;
340: int duplicateCaseStatementsCounter = 0;
341: int counter = 0;
342: for (int i = 0; i < length; i++) {
343: Constant constant;
344: final Statement statement = statements[i];
345: if ((constant = statement.resolveCase(scope,
346: expressionType, this )) != Constant.NotAConstant) {
347: int key = constant.intValue();
348: //----check for duplicate case statement------------
349: for (int j = 0; j < counter; j++) {
350: if (this .constants[j] == key) {
351: final CaseStatement currentCaseStatement = (CaseStatement) statement;
352: if (duplicateCaseStatements == null) {
353: scope.problemReporter()
354: .duplicateCase(cases[j]);
355: scope
356: .problemReporter()
357: .duplicateCase(
358: currentCaseStatement);
359: duplicateCaseStatements = new CaseStatement[length];
360: duplicateCaseStatements[duplicateCaseStatementsCounter++] = cases[j];
361: duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
362: } else {
363: boolean found = false;
364: searchReportedDuplicate: for (int k = 2; k < duplicateCaseStatementsCounter; k++) {
365: if (duplicateCaseStatements[k] == statement) {
366: found = true;
367: break searchReportedDuplicate;
368: }
369: }
370: if (!found) {
371: scope
372: .problemReporter()
373: .duplicateCase(
374: currentCaseStatement);
375: duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
376: }
377: }
378: }
379: }
380: this .constants[counter++] = key;
381: }
382: }
383: if (length != counter) { // resize constants array
384: System.arraycopy(this .constants, 0,
385: this .constants = new int[counter], 0,
386: counter);
387: }
388: } else {
389: if ((this .bits & UndocumentedEmptyBlock) != 0) {
390: upperScope.problemReporter()
391: .undocumentedEmptyBlock(this .blockStart,
392: this .sourceEnd);
393: }
394: }
395: // for enum switch, check if all constants are accounted for (if no default)
396: if (isEnumSwitch
397: && defaultCase == null
398: && upperScope.compilerOptions().getSeverity(
399: CompilerOptions.IncompleteEnumSwitch) != ProblemSeverities.Ignore) {
400: int constantCount = this .constants == null ? 0
401: : this .constants.length; // could be null if no case statement
402: if (constantCount == caseCount // ignore diagnosis if unresolved constants
403: && caseCount != ((ReferenceBinding) expressionType)
404: .enumConstantCount()) {
405: FieldBinding[] enumFields = ((ReferenceBinding) expressionType
406: .erasure()).fields();
407: for (int i = 0, max = enumFields.length; i < max; i++) {
408: FieldBinding enumConstant = enumFields[i];
409: if ((enumConstant.modifiers & ClassFileConstants.AccEnum) == 0)
410: continue;
411: findConstant: {
412: for (int j = 0; j < caseCount; j++) {
413: if ((enumConstant.id + 1) == this .constants[j]) // zero should not be returned see bug 141810
414: break findConstant;
415: }
416: // enum constant did not get referenced from switch
417: upperScope.problemReporter()
418: .missingEnumConstantCase(this ,
419: enumConstant);
420: }
421: }
422: }
423: }
424: } finally {
425: if (this .scope != null)
426: this .scope.enclosingCase = null; // no longer inside switch case block
427: }
428: }
429:
430: public void traverse(ASTVisitor visitor, BlockScope blockScope) {
431:
432: if (visitor.visit(this , blockScope)) {
433: expression.traverse(visitor, scope);
434: if (statements != null) {
435: int statementsLength = statements.length;
436: for (int i = 0; i < statementsLength; i++)
437: statements[i].traverse(visitor, scope);
438: }
439: }
440: visitor.endVisit(this , blockScope);
441: }
442:
443: /**
444: * Dispatch the call on its last statement.
445: */
446: public void branchChainTo(BranchLabel label) {
447:
448: // in order to improve debug attributes for stepping (11431)
449: // we want to inline the jumps to #breakLabel which already got
450: // generated (if any), and have them directly branch to a better
451: // location (the argument label).
452: // we know at this point that the breakLabel already got placed
453: if (this .breakLabel.forwardReferenceCount() > 0) {
454: label.becomeDelegateFor(this.breakLabel);
455: }
456: }
457: }
|