001    /* Copyright (c) 2005-2008, Torbjorn Ekman
002     * All rights reserved.
003     *
004     * Redistribution and use in source and binary forms, with or without
005     * modification, are permitted provided that the following conditions are met:
006     *
007     * 1. Redistributions of source code must retain the above copyright notice,
008     * this list of conditions and the following disclaimer.
009     *
010     * 2. Redistributions in binary form must reproduce the above copyright notice,
011     * this list of conditions and the following disclaimer in the documentation
012     * and/or other materials provided with the distribution.
013     *
014     * 3. Neither the name of the copyright holder nor the names of its
015     * contributors may be used to endorse or promote products derived from this
016     * software without specific prior written permission.
017     *
018     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
022     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028     * POSSIBILITY OF SUCH DAMAGE.
029     */
030    
031    aspect ExceptionHandling {
032    
033      /**
034       * @return <code>true</code> if this TyStmt has a non-empty finally block
035       */
036      syn lazy boolean TryStmt.hasNonEmptyFinally() =
037          hasFinally() && getFinally().getNumStmt() > 0;
038    
039      // exception types
040      eq Program.getChild().typeException() = lookupType("java.lang", "Exception");
041      inh lazy TypeDecl TypeDecl.typeException();
042      eq Program.getChild().typeRuntimeException() = lookupType("java.lang", "RuntimeException");
043      inh lazy TypeDecl TypeDecl.typeRuntimeException();
044      eq Program.getChild().typeError() = lookupType("java.lang", "Error");
045      inh lazy TypeDecl TypeDecl.typeError();
046      eq Program.getChild().typeNullPointerException() = lookupType("java.lang", "NullPointerException");
047      inh lazy TypeDecl ThrowStmt.typeNullPointerException();
048      eq Program.getChild().typeThrowable() = lookupType("java.lang", "Throwable");
049      inh lazy TypeDecl BodyDecl.typeThrowable();
050    
051      syn lazy boolean TypeDecl.isException() = instanceOf(typeException());
052    
053      /**
054       * Checked exceptions must be declared thrown or caught in
055       * an enclosing try-statement.
056       *
057       * Note that this attribute is the opposite of isUncheckedException, i.e.
058       * the type is not tested for being a subclass of java.lang.Exception.
059       *
060       * @return {@code true} if this type is not a subtype of java.lang.RuntimException
061       * or java.lang.Error
062       */
063      syn lazy boolean TypeDecl.isCheckedException() =
064          !(instanceOf(typeRuntimeException()) || instanceOf(typeError()));
065    
066      /**
067       * Unchecked exceptions need not be declared thrown or caught in
068       * an enclosing try-statement.
069       *
070       * @return {@code true} if this type is a subtype of java.lang.RuntimException
071       * or java.lang.Error
072       */
073      syn lazy boolean TypeDecl.isUncheckedException() =
074          instanceOf(typeRuntimeException()) || instanceOf(typeError());
075    
076      inh boolean MethodAccess.handlesException(TypeDecl exceptionType);
077      inh boolean ConstructorAccess.handlesException(TypeDecl exceptionType);
078      inh lazy boolean ThrowStmt.handlesException(TypeDecl exceptionType);
079      inh lazy boolean InstanceInitializer.handlesException(TypeDecl exceptionType);
080      inh lazy boolean StaticInitializer.handlesException(TypeDecl exceptionType);
081      inh boolean FieldDeclaration.handlesException(TypeDecl exceptionType);
082      inh lazy boolean TryStmt.handlesException(TypeDecl exceptionType);
083      inh lazy boolean ConstructorDecl.handlesException(TypeDecl exceptionType);
084      inh lazy boolean MethodDecl.handlesException(TypeDecl exceptionType);
085      inh boolean ClassInstanceExpr.handlesException(TypeDecl exceptionType);
086    
087      public void ASTNode.exceptionHandling() {
088      }
089    
090      public void MethodAccess.exceptionHandling() {
091        for (Iterator iter = exceptionCollection().iterator(); iter.hasNext(); ) {
092          TypeDecl exceptionType = (TypeDecl) iter.next();
093          if (exceptionType.isCheckedException() && !handlesException(exceptionType)) {
094            errorf("%s.%s invoked in %s may throw uncaught exception %s",
095                decl().hostType().fullName(), this.name(),
096                hostType().fullName(), exceptionType.fullName());
097          }
098        }
099      }
100    
101      syn lazy Collection MethodAccess.exceptionCollection() {
102        //System.out.println("Computing exceptionCollection for " + name());
103        HashSet set = new HashSet();
104        Iterator iter = decls().iterator();
105        if (!iter.hasNext()) {
106          return set;
107        }
108    
109        MethodDecl m = (MethodDecl) iter.next();
110        //System.out.println("Processing first found method " + m.signature() + " in " + m.hostType().fullName());
111    
112        for (int i = 0; i < m.getNumException(); i++) {
113          TypeDecl exceptionType = m.getException(i).type();
114          set.add(exceptionType);
115        }
116        while (iter.hasNext()) {
117          HashSet first = new HashSet();
118          first.addAll(set);
119          HashSet second = new HashSet();
120          m = (MethodDecl) iter.next();
121          //System.out.println("Processing the next method " + m.signature() + " in " + m.hostType().fullName());
122          for (int i = 0; i < m.getNumException(); i++) {
123            TypeDecl exceptionType = m.getException(i).type();
124            second.add(exceptionType);
125          }
126          set = new HashSet();
127          for (Iterator i1 = first.iterator(); i1.hasNext(); ) {
128            TypeDecl firstType = (TypeDecl) i1.next();
129            for (Iterator i2 = second.iterator(); i2.hasNext(); ) {
130              TypeDecl secondType = (TypeDecl) i2.next();
131              if (firstType.instanceOf(secondType)) {
132                set.add(firstType);
133              } else if (secondType.instanceOf(firstType)) {
134                set.add(secondType);
135              }
136            }
137          }
138        }
139        return set;
140      }
141    
142      public void ConstructorAccess.exceptionHandling() {
143        for (Access exception : decl().getExceptionList()) {
144          TypeDecl exceptionType = exception.type();
145          if (exceptionType.isCheckedException() && !handlesException(exceptionType)) {
146            errorf("%s may throw uncaught exception %s",
147                this.prettyPrint(), exceptionType.fullName());
148          }
149        }
150      }
151    
152      public void ClassInstanceExpr.exceptionHandling() {
153        for (Access exception : decl().getExceptionList()) {
154          TypeDecl exceptionType = exception.type();
155          if (exceptionType.isCheckedException() && !handlesException(exceptionType)) {
156            errorf("%s may throw uncaught exception %s; it must be caught or declared as being thrown",
157                this.prettyPrint(), exceptionType.fullName());
158          }
159        }
160      }
161    
162      public void ThrowStmt.exceptionHandling() {
163        TypeDecl exceptionType = getExpr().type();
164        if (exceptionType == typeNull()) {
165          exceptionType = typeNullPointerException();
166        }
167        // 8.4.4
168        if (exceptionType.isCheckedException() && !handlesException(exceptionType)) {
169          errorf("%s throws uncaught exception %s", this.prettyPrint(), exceptionType.fullName());
170        }
171      }
172    
173      eq Program.getChild().handlesException(TypeDecl exceptionType) {
174        throw new Error("Operation handlesException not supported");
175      }
176    
177      eq CompilationUnit.getTypeDecl().handlesException(TypeDecl exceptionType) = false;
178    
179      eq MethodDecl.getBlock().handlesException(TypeDecl exceptionType) =
180          throwsException(exceptionType);
181    
182      syn lazy boolean MethodDecl.throwsException(TypeDecl exceptionType) {
183        for (Access exception : getExceptionList()) {
184          if (exceptionType.instanceOf(exception.type())) {
185            return true;
186          }
187        }
188        return false;
189      }
190    
191      eq ConstructorDecl.getBlock().handlesException(TypeDecl exceptionType) =
192          throwsException(exceptionType);
193    
194      eq ConstructorDecl.getParsedConstructorInvocation().handlesException(TypeDecl exceptionType) =
195          throwsException(exceptionType);
196    
197      eq ConstructorDecl.getImplicitConstructorInvocation().handlesException(TypeDecl exceptionType) =
198          throwsException(exceptionType);
199    
200      syn lazy boolean ConstructorDecl.throwsException(TypeDecl exceptionType) {
201        for (Access exception : getExceptionList()) {
202          if (exceptionType.instanceOf(exception.type())) {
203            return true;
204          }
205        }
206        return false;
207      }
208    
209      eq FieldDeclaration.getInit().handlesException(TypeDecl exceptionType) {
210        if (hostType().isAnonymous()) {
211          return true;
212        }
213        for (Iterator iter = hostType().constructors().iterator(); iter.hasNext(); ) {
214          ConstructorDecl decl = (ConstructorDecl) iter.next();
215          if (!decl.throwsException(exceptionType)) {
216            return false;
217          }
218        }
219        return true;
220      }
221    
222      // 8.6
223      eq InstanceInitializer.getBlock().handlesException(TypeDecl exceptionType) {
224        if (hostType().isAnonymous()) {
225          return true;
226        }
227        for (Iterator iter = hostType().constructors().iterator(); iter.hasNext(); ) {
228          ConstructorDecl decl = (ConstructorDecl) iter.next();
229          if (!decl.throwsException(exceptionType)) {
230            return false;
231          }
232        }
233        return true;
234      }
235    
236      eq StaticInitializer.getBlock().handlesException(TypeDecl exceptionType) =
237          hostType().isAnonymous() && handlesException(exceptionType);
238    
239      eq TryStmt.getCatchClause().handlesException(TypeDecl exceptionType) {
240        if (hasNonEmptyFinally() && !getFinally().canCompleteNormally()) {
241          return true;
242        }
243        return handlesException(exceptionType);
244      }
245    
246      eq TryStmt.getBlock().handlesException(TypeDecl exceptionType) {
247        for (int i = 0; i < getNumCatchClause(); i++) {
248          if (getCatchClause(i).handles(exceptionType)) {
249            return true;
250          }
251        }
252        if (hasNonEmptyFinally() && !getFinally().canCompleteNormally()) {
253          return true;
254        }
255        return handlesException(exceptionType);
256      }
257    
258      // the catch clause catches the exception
259      syn boolean CatchClause.handles(TypeDecl exceptionType) = false;
260      eq BasicCatch.handles(TypeDecl exceptionType) =
261          !getParameter().type().isUnknown()
262          && exceptionType.instanceOf(getParameter().type());
263    
264      /**
265       * The block of the try statement can throw an exception of
266       * a type assignable to the given type.
267       */
268      syn lazy boolean TryStmt.catchableException(TypeDecl type) =
269          getBlock().reachedException(type);
270    
271      protected boolean ASTNode.reachedException(TypeDecl type) {
272        for (int i = 0; i < getNumChild(); i++) {
273          if (getChild(i).reachedException(type)) {
274            return true;
275          }
276        }
277        return false;
278      }
279    
280      protected boolean TryStmt.reachedException(TypeDecl type) {
281        boolean found = false;
282        // found is true if the exception type is caught by a catch clause
283        for (int i = 0; i < getNumCatchClause() && !found; i++) {
284          if (getCatchClause(i).handles(type)) {
285            found = true;
286          }
287        }
288        // if an exception is thrown in the block and the exception is not caught and
289        // either there is no finally block or the finally block can complete normally
290        if (!found && (!hasNonEmptyFinally() || getFinally().canCompleteNormally()) ) {
291          if (getBlock().reachedException(type)) {
292            return true;
293          }
294        }
295        // even if the exception is caught by the catch clauses they may
296        // throw new exceptions
297        for (int i = 0; i < getNumCatchClause(); i++) {
298          if (getCatchClause(i).reachedException(type)) {
299            return true;
300          }
301        }
302        return hasNonEmptyFinally() && getFinally().reachedException(type);
303      }
304    
305      syn lazy boolean TypeDecl.mayCatch(TypeDecl thrownType) =
306          thrownType.instanceOf(this) || this.instanceOf(thrownType);
307    
308      protected boolean MethodAccess.reachedException(TypeDecl catchType) {
309        for (Iterator iter = exceptionCollection().iterator(); iter.hasNext(); ) {
310          TypeDecl exceptionType = (TypeDecl) iter.next();
311          if (catchType.mayCatch(exceptionType)) {
312            return true;
313          }
314        }
315        return super.reachedException(catchType);
316      }
317    
318      protected boolean ThrowStmt.reachedException(TypeDecl catchType) {
319        TypeDecl exceptionType = getExpr().type();
320        if (exceptionType == typeNull()) {
321          exceptionType = typeNullPointerException();
322        }
323        if (catchType.mayCatch(exceptionType)) {
324          return true;
325        }
326        return super.reachedException(catchType);
327      }
328    
329      // 8.8.4 (8.4.4)
330      protected boolean ConstructorAccess.reachedException(TypeDecl catchType) {
331        for (Access exception : decl().getExceptionList()) {
332          TypeDecl exceptionType = exception.type();
333          if (catchType.mayCatch(exceptionType)) {
334            return true;
335          }
336        }
337        return super.reachedException(catchType);
338      }
339    
340      protected boolean ClassInstanceExpr.reachedException(TypeDecl catchType) {
341        ConstructorDecl decl = decl();
342        for (Access exception : decl().getExceptionList()) {
343          TypeDecl exceptionType = exception.type();
344          if (catchType.mayCatch(exceptionType)) {
345            return true;
346          }
347        }
348        for (int i = 0; i < getNumArg(); i++) {
349          if (getArg(i).reachedException(catchType)) {
350            return true;
351          }
352        }
353        return false;
354      }
355    
356    }