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