*** empty log message ***
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / compiler / ast / TryStatement.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v0.5 
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v05.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  ******************************************************************************/
11 package net.sourceforge.phpdt.internal.compiler.ast;
12
13 import net.sourceforge.phpdt.internal.compiler.IAbstractSyntaxTreeVisitor;
14 import net.sourceforge.phpdt.internal.compiler.codegen.CodeStream;
15 import net.sourceforge.phpdt.internal.compiler.codegen.ExceptionLabel;
16 import net.sourceforge.phpdt.internal.compiler.codegen.Label;
17 import net.sourceforge.phpdt.internal.compiler.flow.ExceptionHandlingFlowContext;
18 import net.sourceforge.phpdt.internal.compiler.flow.FinallyFlowContext;
19 import net.sourceforge.phpdt.internal.compiler.flow.FlowContext;
20 import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo;
21 import net.sourceforge.phpdt.internal.compiler.flow.InsideSubRoutineFlowContext;
22 import net.sourceforge.phpdt.internal.compiler.flow.UnconditionalFlowInfo;
23 import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
24 import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding;
25 import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
26 import net.sourceforge.phpdt.internal.compiler.lookup.MethodScope;
27 import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
28 import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
29
30 public class TryStatement extends Statement {
31         
32         public Block tryBlock;
33         public Block[] catchBlocks;
34         public Argument[] catchArguments;
35         public Block finallyBlock;
36         BlockScope scope;
37
38         public boolean subRoutineCannotReturn = true;
39         // should rename into subRoutineComplete to be set to false by default
40
41         ReferenceBinding[] caughtExceptionTypes;
42         boolean tryBlockExit;
43         boolean[] catchExits;
44         public int[] preserveExceptionHandler;
45
46         Label subRoutineStartLabel;
47         public LocalVariableBinding anyExceptionVariable,
48                 returnAddressVariable,
49                 secretReturnValue;
50
51         public final static char[] SecretReturnName = " returnAddress".toCharArray(); //$NON-NLS-1$
52         public final static char[] SecretAnyHandlerName = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$
53         public static final char[] SecretLocalDeclarationName = " returnValue".toCharArray(); //$NON-NLS-1$
54
55         // for local variables table attributes
56         int preTryInitStateIndex = -1;
57         int mergedInitStateIndex = -1;
58
59         public FlowInfo analyseCode(
60                 BlockScope currentScope,
61                 FlowContext flowContext,
62                 FlowInfo flowInfo) {
63
64                 // Consider the try block and catch block so as to compute the intersection of initializations and      
65                 // the minimum exit relative depth amongst all of them. Then consider the subroutine, and append its
66                 // initialization to the try/catch ones, if the subroutine completes normally. If the subroutine does not
67                 // complete, then only keep this result for the rest of the analysis
68
69                 // process the finally block (subroutine) - create a context for the subroutine
70
71                 preTryInitStateIndex =
72                         currentScope.methodScope().recordInitializationStates(flowInfo);
73
74                 if (anyExceptionVariable != null) {
75                         anyExceptionVariable.used = true;
76                 }
77                 if (returnAddressVariable != null) {
78                         returnAddressVariable.used = true;
79                 }
80                 InsideSubRoutineFlowContext insideSubContext;
81                 FinallyFlowContext finallyContext;
82                 UnconditionalFlowInfo subInfo;
83                 if (subRoutineStartLabel == null) {
84                         // no finally block
85                         insideSubContext = null;
86                         finallyContext = null;
87                         subInfo = null;
88                 } else {
89                         // analyse finally block first
90                         insideSubContext = new InsideSubRoutineFlowContext(flowContext, this);
91                         subInfo =
92                                 finallyBlock
93                                         .analyseCode(
94                                                 currentScope,
95                                                 finallyContext = new FinallyFlowContext(flowContext, finallyBlock),
96                                                 flowInfo.copy())
97                                         .unconditionalInits();
98                         if (!((subInfo == FlowInfo.DeadEnd) || subInfo.isFakeReachable())) {
99                                 subRoutineCannotReturn = false;
100                         }
101                 }
102                 // process the try block in a context handling the local exceptions.
103                 ExceptionHandlingFlowContext handlingContext =
104                         new ExceptionHandlingFlowContext(
105                                 insideSubContext == null ? flowContext : insideSubContext,
106                                 tryBlock,
107                                 caughtExceptionTypes,
108                                 scope,
109                                 flowInfo.unconditionalInits());
110
111                 FlowInfo tryInfo;
112                 if (tryBlock.statements == null) {
113                         tryInfo = flowInfo;
114                         tryBlockExit = false;
115                 } else {
116                         tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
117                         tryBlockExit = (tryInfo == FlowInfo.DeadEnd) || tryInfo.isFakeReachable();
118                 }
119
120                 // check unreachable catch blocks
121                 handlingContext.complainIfUnusedExceptionHandlers(catchBlocks, scope, this);
122
123                 // process the catch blocks - computing the minimal exit depth amongst try/catch
124                 if (catchArguments != null) {
125                         int catchCount;
126                         catchExits = new boolean[catchCount = catchBlocks.length];
127                         for (int i = 0; i < catchCount; i++) {
128                                 // keep track of the inits that could potentially have led to this exception handler (for final assignments diagnosis)
129                                 ///*
130                                 FlowInfo catchInfo =
131                                         flowInfo
132                                                 .copy()
133                                                 .unconditionalInits()
134                                                 .addPotentialInitializationsFrom(
135                                                         handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits())
136                                                 .addPotentialInitializationsFrom(tryInfo.unconditionalInits())
137                                                 .addPotentialInitializationsFrom(handlingContext.initsOnReturn);
138
139                                 // catch var is always set
140                                 catchInfo.markAsDefinitelyAssigned(catchArguments[i].binding);
141                                 /*
142                                 "If we are about to consider an unchecked exception handler, potential inits may have occured inside
143                                 the try block that need to be detected , e.g. 
144                                 try { x = 1; throwSomething();} catch(Exception e){ x = 2} "
145                                 "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index])
146                                 ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]."
147                                 */
148                                 if (tryBlock.statements == null) {
149                                         catchInfo.markAsFakeReachable(true);
150                                 }
151                                 catchInfo =
152                                         catchBlocks[i].analyseCode(
153                                                 currentScope,
154                                                 insideSubContext == null ? flowContext : insideSubContext,
155                                                 catchInfo);
156                                 catchExits[i] =
157                                         ((catchInfo == FlowInfo.DeadEnd) || catchInfo.isFakeReachable());
158                                 tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
159                         }
160                 }
161                 if (subRoutineStartLabel == null) {
162                         mergedInitStateIndex =
163                                 currentScope.methodScope().recordInitializationStates(tryInfo);
164                         return tryInfo;
165                 }
166
167                 // we also need to check potential multiple assignments of final variables inside the finally block
168                 // need to include potential inits from returns inside the try/catch parts - 1GK2AOF
169                 tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn);
170                 finallyContext.complainOnRedundantFinalAssignments(tryInfo, currentScope);
171                 if (subInfo == FlowInfo.DeadEnd) {
172                         mergedInitStateIndex =
173                                 currentScope.methodScope().recordInitializationStates(subInfo);
174                         return subInfo;
175                 } else {
176                         FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo);
177                         mergedInitStateIndex =
178                                 currentScope.methodScope().recordInitializationStates(mergedInfo);
179                         return mergedInfo;
180                 }
181         }
182
183         public boolean cannotReturn() {
184
185                 return subRoutineCannotReturn;
186         }
187
188         /**
189          * Try statement code generation
190          *
191          */
192         public void generateCode(BlockScope currentScope, CodeStream codeStream) {
193
194                 if ((bits & IsReachableMASK) == 0) {
195                         return;
196                 }
197                 if (tryBlock.isEmptyBlock()) {
198                         if (subRoutineStartLabel != null) {
199                                 // since not passing the finallyScope, the block generation will exitUserScope(finallyScope)
200                                 finallyBlock.generateCode(scope, codeStream);
201                         }
202                         // May loose some local variable initializations : affecting the local variable attributes
203                         if (mergedInitStateIndex != -1) {
204                                 codeStream.removeNotDefinitelyAssignedVariables(
205                                         currentScope,
206                                         mergedInitStateIndex);
207                         }
208                         // no local bytecode produced so no need for position remembering
209                         return;
210                 }
211                 int pc = codeStream.position;
212                 Label endLabel = new Label(codeStream);
213                 boolean requiresNaturalJsr = false;
214
215                 // preparing exception labels
216                 int maxCatches;
217                 ExceptionLabel[] exceptionLabels =
218                         new ExceptionLabel[maxCatches =
219                                 catchArguments == null ? 0 : catchArguments.length];
220                 for (int i = 0; i < maxCatches; i++) {
221                         boolean preserveCurrentHandler =
222                                 (preserveExceptionHandler[i
223                                         / ExceptionHandlingFlowContext.BitCacheSize]
224                                                 & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
225                                         != 0;
226                         if (preserveCurrentHandler) {
227                                 exceptionLabels[i] =
228                                         new ExceptionLabel(
229                                                 codeStream,
230                                                 (ReferenceBinding) catchArguments[i].binding.type);
231                         }
232                 }
233                 ExceptionLabel anyExceptionLabel = null;
234                 if (subRoutineStartLabel != null) {
235                         subRoutineStartLabel.codeStream = codeStream;
236                         anyExceptionLabel = new ExceptionLabel(codeStream, null);
237                 }
238                 // generate the try block
239                 tryBlock.generateCode(scope, codeStream);
240                 boolean tryBlockHasSomeCode = codeStream.position != pc;
241                 // flag telling if some bytecodes were issued inside the try block
242
243                 // natural exit: only if necessary
244                 boolean nonReturningSubRoutine =
245                         (subRoutineStartLabel != null) && subRoutineCannotReturn;
246                 if ((!tryBlockExit) && tryBlockHasSomeCode) {
247                         int position = codeStream.position;
248                         if (nonReturningSubRoutine) {
249                                 codeStream.goto_(subRoutineStartLabel);
250                         } else {
251                                 requiresNaturalJsr = true;
252                                 codeStream.goto_(endLabel);
253                         }
254                         codeStream.updateLastRecordedEndPC(position);
255                         //goto is tagged as part of the try block
256                 }
257                 // place end positions of user-defined exception labels
258                 if (tryBlockHasSomeCode) {
259                         for (int i = 0; i < maxCatches; i++) {
260                                 boolean preserveCurrentHandler =
261                                         (preserveExceptionHandler[i
262                                                 / ExceptionHandlingFlowContext.BitCacheSize]
263                                                         & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
264                                                 != 0;
265                                 if (preserveCurrentHandler) {
266                                         exceptionLabels[i].placeEnd();
267                                 }
268                         }
269                         /* generate sequence of handler, all starting by storing the TOS (exception
270                         thrown) into their own catch variables, the one specified in the source
271                         that must denote the handled exception.
272                         */
273                         if (catchArguments == null) {
274                                 if (anyExceptionLabel != null) {
275                                         anyExceptionLabel.placeEnd();
276                                 }
277                         } else {
278                                 for (int i = 0; i < maxCatches; i++) {
279                                         boolean preserveCurrentHandler =
280                                                 (preserveExceptionHandler[i
281                                                         / ExceptionHandlingFlowContext.BitCacheSize]
282                                                                 & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
283                                                         != 0;
284                                         if (preserveCurrentHandler) {
285                                                 // May loose some local variable initializations : affecting the local variable attributes
286                                                 if (preTryInitStateIndex != -1) {
287                                                         codeStream.removeNotDefinitelyAssignedVariables(
288                                                                 currentScope,
289                                                                 preTryInitStateIndex);
290                                                 }
291                                                 exceptionLabels[i].place();
292                                                 codeStream.incrStackSize(1);
293                                                 // optimizing the case where the exception variable is not actually used
294                                                 LocalVariableBinding catchVar;
295                                                 int varPC = codeStream.position;
296                                                 if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) {
297                                                         codeStream.store(catchVar, false);
298                                                         catchVar.recordInitializationStartPC(codeStream.position);
299                                                         codeStream.addVisibleLocalVariable(catchVar);
300                                                 } else {
301                                                         codeStream.pop();
302                                                 }
303                                                 codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart);
304                                                 // Keep track of the pcs at diverging point for computing the local attribute
305                                                 // since not passing the catchScope, the block generation will exitUserScope(catchScope)
306                                                 catchBlocks[i].generateCode(scope, codeStream);
307                                         }
308                                         if (i == maxCatches - 1) {
309                                                 if (anyExceptionLabel != null) {
310                                                         anyExceptionLabel.placeEnd();
311                                                 }
312                                                 if (subRoutineStartLabel != null) {
313                                                         if (!catchExits[i] && preserveCurrentHandler) {
314                                                                 requiresNaturalJsr = true;
315                                                                 codeStream.goto_(endLabel);
316                                                         }
317                                                 }
318                                         } else {
319                                                 if (!catchExits[i] && preserveCurrentHandler) {
320                                                         if (nonReturningSubRoutine) {
321                                                                 codeStream.goto_(subRoutineStartLabel);
322                                                         } else {
323                                                                 requiresNaturalJsr = true;
324                                                                 codeStream.goto_(endLabel);
325                                                         }
326                                                 }
327                                         }
328                                 }
329                         }
330                         // addition of a special handler so as to ensure that any uncaught exception (or exception thrown
331                         // inside catch blocks) will run the finally block
332                         int finallySequenceStartPC = codeStream.position;
333                         if (subRoutineStartLabel != null) {
334                                 // the additional handler is doing: jsr finallyBlock and rethrow TOS-exception
335                                 anyExceptionLabel.place();
336
337                                 if (preTryInitStateIndex != -1) {
338                                         // reset initialization state, as for a normal catch block
339                                         codeStream.removeNotDefinitelyAssignedVariables(
340                                                 currentScope,
341                                                 preTryInitStateIndex);
342                                 }
343
344                                 codeStream.incrStackSize(1);
345                                 if (nonReturningSubRoutine) {
346                                         codeStream.pop();
347                                         // "if subroutine cannot return, no need to jsr/jump to subroutine since it will be entered in sequence
348                                 } else {
349                                         codeStream.store(anyExceptionVariable, false);
350                                         codeStream.jsr(subRoutineStartLabel);
351                                         codeStream.load(anyExceptionVariable);
352                                         codeStream.athrow();
353                                 }
354                         }
355                         // end of catch sequence, place label that will correspond to the finally block beginning, or end of statement
356                         endLabel.place();
357                         if (subRoutineStartLabel != null) {
358                                 if (nonReturningSubRoutine) {
359                                         requiresNaturalJsr = false;
360                                 }
361                                 Label veryEndLabel = new Label(codeStream);
362                                 if (requiresNaturalJsr) {
363                                         codeStream.jsr(subRoutineStartLabel);
364                                         codeStream.goto_(veryEndLabel);
365                                 }
366                                 subRoutineStartLabel.place();
367                                 if (!nonReturningSubRoutine) {
368                                         codeStream.incrStackSize(1);
369                                         codeStream.store(returnAddressVariable, false);
370                                 }
371                                 codeStream.recordPositionsFrom(
372                                         finallySequenceStartPC,
373                                         finallyBlock.sourceStart);
374                                 // entire sequence for finally is associated to finally block
375                                 finallyBlock.generateCode(scope, codeStream);
376                                 if (!nonReturningSubRoutine) {
377                                         int position = codeStream.position;
378                                         codeStream.ret(returnAddressVariable.resolvedPosition);
379                                         codeStream.updateLastRecordedEndPC(position);
380                                         // the ret bytecode is part of the subroutine
381                                 }
382                                 if (requiresNaturalJsr) {
383                                         veryEndLabel.place();
384                                 }
385                         }
386                 } else {
387                         // try block had no effect, only generate the body of the finally block if any
388                         if (subRoutineStartLabel != null) {
389                                 finallyBlock.generateCode(scope, codeStream);
390                         }
391                 }
392                 // May loose some local variable initializations : affecting the local variable attributes
393                 if (mergedInitStateIndex != -1) {
394                         codeStream.removeNotDefinitelyAssignedVariables(
395                                 currentScope,
396                                 mergedInitStateIndex);
397                         codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
398                 }
399                 codeStream.recordPositionsFrom(pc, this.sourceStart);
400         }
401
402         public void resolve(BlockScope upperScope) {
403
404                 // special scope for secret locals optimization.        
405                 this.scope = new BlockScope(upperScope);
406
407                 BlockScope tryScope = new BlockScope(scope);
408                 BlockScope finallyScope = null;
409                 
410                 if (finallyBlock != null
411                         && finallyBlock.statements != null) {
412
413                         finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope
414
415                         // provision for returning and forcing the finally block to run
416                         MethodScope methodScope = scope.methodScope();
417
418                         // the type does not matter as long as its not a normal base type
419                         this.returnAddressVariable =
420                                 new LocalVariableBinding(SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false);
421                         finallyScope.addLocalVariable(returnAddressVariable);
422                         this.returnAddressVariable.constant = NotAConstant; // not inlinable
423                         this.subRoutineStartLabel = new Label();
424
425                         this.anyExceptionVariable =
426                                 new LocalVariableBinding(SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false);
427                         finallyScope.addLocalVariable(this.anyExceptionVariable);
428                         this.anyExceptionVariable.constant = NotAConstant; // not inlinable
429
430                         if (!methodScope.isInsideInitializer()) {
431                                 MethodBinding methodBinding =
432                                         ((AbstractMethodDeclaration) methodScope.referenceContext).binding;
433                                 if (methodBinding != null) {
434                                         TypeBinding methodReturnType = methodBinding.returnType;
435                                         if (methodReturnType.id != T_void) {
436                                                 this.secretReturnValue =
437                                                         new LocalVariableBinding(
438                                                                 SecretLocalDeclarationName,
439                                                                 methodReturnType,
440                                                                 AccDefault,
441                                                                 false);
442                                                 finallyScope.addLocalVariable(this.secretReturnValue);
443                                                 this.secretReturnValue.constant = NotAConstant; // not inlinable
444                                         }
445                                 }
446                         }
447                         finallyBlock.resolveUsing(finallyScope);
448                         // force the finally scope to have variable positions shifted after its try scope and catch ones
449                         finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length+1];
450                         finallyScope.shiftScopes[0] = tryScope;
451                 }
452                 this.tryBlock.resolveUsing(tryScope);
453
454                 // arguments type are checked against JavaLangThrowable in resolveForCatch(..)
455                 if (this.catchBlocks != null) {
456                         int length = this.catchArguments.length;
457                         TypeBinding[] argumentTypes = new TypeBinding[length];
458                         for (int i = 0; i < length; i++) {
459                                 BlockScope catchScope = new BlockScope(scope);
460                                 if (finallyScope != null){
461                                         finallyScope.shiftScopes[i+1] = catchScope;
462                                 }
463                                 // side effect on catchScope in resolveForCatch(..)
464                                 if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null)
465                                         return;
466                                 catchBlocks[i].resolveUsing(catchScope);
467                         }
468
469                         // Verify that the catch clause are ordered in the right way:
470                         // more specialized first.
471                         this.caughtExceptionTypes = new ReferenceBinding[length];
472                         for (int i = 0; i < length; i++) {
473                                 caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i];
474                                 for (int j = 0; j < i; j++) {
475                                         if (BlockScope.areTypesCompatible(caughtExceptionTypes[i], argumentTypes[j])) {
476                                                 scope.problemReporter().wrongSequenceOfExceptionTypesError(this, i, j);
477                                                 return;
478                                         }
479                                 }
480                         }
481                 } else {
482                         caughtExceptionTypes = new ReferenceBinding[0];
483                 }
484                 
485                 if (finallyScope != null){
486                         // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes.
487                         // the shifting is necessary to achieve no overlay in between the finally scope and its
488                         // sibling in term of local variable positions.
489                         this.scope.addSubscope(finallyScope);
490                 }
491         }
492
493         public String toString(int tab) {
494                 String s = tabString(tab);
495                 //try
496                 s = s + "try "; //$NON-NLS-1$
497                 if (tryBlock == Block.None)
498                         s = s + "{}"; //$NON-NLS-1$
499                 else
500                         s = s + "\n" + tryBlock.toString(tab + 1); //$NON-NLS-1$
501
502                 //catches
503                 if (catchBlocks != null)
504                         for (int i = 0; i < catchBlocks.length; i++)
505                                         s = s + "\n" + tabString(tab) + "catch (" //$NON-NLS-2$ //$NON-NLS-1$
506                                                 +catchArguments[i].toString(0) + ") " //$NON-NLS-1$
507                                                 +catchBlocks[i].toString(tab + 1);
508                 //finally
509                 if (finallyBlock != null) {
510                         if (finallyBlock == Block.None)
511                                 s = s + "\n" + tabString(tab) + "finally {}"; //$NON-NLS-2$ //$NON-NLS-1$
512                         else
513                                         s = s + "\n" + tabString(tab) + "finally\n" + //$NON-NLS-2$ //$NON-NLS-1$
514                         finallyBlock.toString(tab + 1);
515                 }
516
517                 return s;
518         }
519
520         public void traverse(
521                 IAbstractSyntaxTreeVisitor visitor,
522                 BlockScope blockScope) {
523
524                 if (visitor.visit(this, blockScope)) {
525                         tryBlock.traverse(visitor, scope);
526                         if (catchArguments != null) {
527                                 for (int i = 0, max = catchBlocks.length; i < max; i++) {
528                                         catchArguments[i].traverse(visitor, scope);
529                                         catchBlocks[i].traverse(visitor, scope);
530                                 }
531                         }
532                         if (finallyBlock != null)
533                                 finallyBlock.traverse(visitor, scope);
534                 }
535                 visitor.endVisit(this, blockScope);
536         }
537 }