001    /* Copyright (c) 2011, Jesper Öqvist <jesper.oqvist@cs.lth.se>
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 TryWithResources {
032    
033      /**
034       * An NTA which is used for code generation.
035       *
036       * This NTA will handle the recursive nature of code
037       * generation for try-with-resources statements.
038       */
039      syn nta Stmt TryWithResources.getTransformed() {
040        if (getNumCatchClause() == 0 && !hasNonEmptyFinally()) {
041          // transform to BasicTWR
042          Block block;
043          if (getNumResource() == 1) {
044            block = (Block) getBlock().treeCopyNoTransform();
045          } else {
046            block = new Block();
047            List<ResourceDeclaration> resourceListTail = new List<ResourceDeclaration>();
048            for (int i = 1; i < getNumResource(); ++i) {
049              resourceListTail.add((ResourceDeclaration)
050                  getResource(i).treeCopyNoTransform());
051            }
052            block.addStmt(new TryWithResources(
053                  resourceListTail,
054                  (Block) getBlock().treeCopyNoTransform(),
055                  new List<CatchClause>(),
056                  new Opt<Block>()));
057          }
058          return new BasicTWR(
059              (ResourceDeclaration)
060              getResource(0).treeCopyNoTransform(),
061              block);
062        } else {
063          // transform to surrounding try stmt
064          Block block = new Block();
065          block.addStmt(new TryWithResources(
066                (List<ResourceDeclaration>)
067                getResourceList().treeCopyNoTransform(),
068                (Block) getBlock().treeCopyNoTransform(),
069                new List<CatchClause>(),
070                new Opt<Block>()));
071    
072          return new TryStmt(block,
073              (List<CatchClause>) getCatchClauseList().treeCopyNoTransform(),
074              (Opt<Block>) getFinallyOpt().treeCopyNoTransform());
075        }
076      }
077    
078      /**
079       * Code generation for the try-with-resources statement.
080       */
081      public void TryWithResources.createBCode(CodeGeneration gen) {
082    
083        getTransformed().createBCode(gen);
084      }
085    
086      /**
087       * The general structure of the basic try-with-resources:
088       *
089       * <pre><code>
090       * RESOURCE
091       * BLOCK
092       *
093       * Primary Exception Handler
094       *   Automatic Closing of Resource
095       *     Suppressed Exception Handler
096       *   re-throw primary exception
097       * Automatic Closing of Resource
098       * </pre></code>
099       *
100       * Pseudocode for basic try-with-resources:
101       *
102       * <pre><code>
103       * 0  .resourceBegin
104       * 1  emit RESOURCE
105       * 0  store resource
106       * 0  .resourceEnd
107       *
108       * 0  .blockBegin
109       * 0  emit BLOCK
110       * 0  .blockEnd
111       * 0  goto outerFinally
112       *
113       * 1  .resourceException
114       * 1  throw
115       *
116       * #if BLOCK is not empty:
117       *
118       * 1  .catchPrimary
119       * 0  store primary
120       *
121       * 0  .tryCloseBegin
122       * 1  load resource
123       * 0  ifnull innerFinally
124       * 1  load resource
125       * 0  invoke java.lang.AutoCloseable.close()
126       * 0  .tryCloseEnd
127       *
128       * 0  goto innerFinally
129       *
130       * 1  .catchSuppressed
131       * 0  store suppressed
132       * 1  load primary
133       * 2  load suppressed
134       * 0  invoke java.lang.Throwable.addSuppressed(Throwable)
135       *
136       * 0  .innerFinally
137       * 1  load primary
138       * 1  throw
139       *
140       * #endif BLOCK is not empty
141       *
142       * 0  .outerFinally
143       * 1  load resource
144       * 0  ifnull tryEnd
145       * 1  load resource
146       * 0  invoke java.lang.AutoCloseable.close()
147       *
148       * 0  .tryEnd
149       *
150       * Exception Table:
151       * resourceBegin .. resourceEnd : resourceException
152       * blockBegin .. blockEnd : catchPrimary
153       * tryCloseBegin .. tryCloseEnd : catchSuppressed
154       * </pre></code>
155       *
156       */
157      public void BasicTWR.createBCode(CodeGeneration gen) {
158        ResourceDeclaration resource = getResource();
159    
160        int resourceBeginLbl = gen.constantPool().newLabel();
161        int resourceEndLbl = gen.constantPool().newLabel();
162        int blockBeginLbl = gen.constantPool().newLabel();
163        int blockEndLbl = gen.constantPool().newLabel();
164        int tryCloseBeginLbl = gen.constantPool().newLabel();
165        int tryCloseEndLbl = gen.constantPool().newLabel();
166    
167        int resourceExceptionLbl = gen.constantPool().newLabel();
168        int catchPrimaryLbl = gen.constantPool().newLabel();
169        int catchSuppressedLbl = gen.constantPool().newLabel();
170        int innerFinallyLbl = gen.constantPool().newLabel();
171        int outerFinallyLbl = gen.constantPool().newLabel();
172        int tryEndLbl = gen.constantPool().newLabel();
173    
174        TypeDecl throwableType = lookupType("java.lang", "Throwable");
175        TypeDecl resourceType = resource.type();
176        TypeDecl autoCloseableType = lookupType("java.lang", "AutoCloseable");
177    
178        gen.changeStackDepth(3);
179        int resourceIndex = resource.localNum();
180        int primaryIndex = resourceIndex+resourceType.variableSize();
181        int suppressedIndex = primaryIndex+throwableType.variableSize();
182    
183        // store the resource in local
184        gen.addLabel(resourceBeginLbl);
185        resource.createBCode(gen);
186        gen.addLabel(resourceEndLbl);
187        gen.emit(Bytecode.NOP);
188    
189        gen.addLabel(blockBeginLbl);
190        getBlock().createBCode(gen);
191        gen.addLabel(blockEndLbl);
192        gen.emitGoto(outerFinallyLbl);
193    
194        // If there was an exception when initializing the resource
195        // we need to directly rethrow the exception
196        gen.addLabel(resourceExceptionLbl);
197        gen.emitThrow();
198        gen.addException(
199          resourceBeginLbl,
200          resourceEndLbl,
201          resourceExceptionLbl,
202          CodeGeneration.ExceptionEntry.CATCH_ALL);
203    
204        if (gen.addressOf(blockBeginLbl) != gen.addressOf(blockEndLbl)) {
205    
206          // catch primary exception
207          // operand stack: .., #primary
208          gen.addLabel(catchPrimaryLbl);
209          throwableType.emitStoreLocal(gen, primaryIndex);
210    
211          // try-close resource
212          gen.addLabel(tryCloseBeginLbl);
213          {
214            // if resource != null
215            resourceType.emitLoadLocal(gen, resourceIndex);
216            gen.emitCompare(Bytecode.IFNULL, innerFinallyLbl);
217            resourceType.emitLoadLocal(gen, resourceIndex);
218            closeMethod().emitInvokeMethod(gen, autoCloseableType);
219          }
220          gen.addLabel(tryCloseEndLbl);
221          gen.emitGoto(innerFinallyLbl);
222    
223          // catch suppressed exception
224          // operand stack: .., #primary, #suppressed
225          gen.addLabel(catchSuppressedLbl);
226          throwableType.emitStoreLocal(gen, suppressedIndex);
227          throwableType.emitLoadLocal(gen, primaryIndex);
228          throwableType.emitLoadLocal(gen, suppressedIndex);
229          addSuppressedMethod().emitInvokeMethod(gen, throwableType);
230    
231          // inner finally
232          // operand stack: .., #primary
233          gen.addLabel(innerFinallyLbl);
234          throwableType.emitLoadLocal(gen, primaryIndex);
235          gen.emitThrow();
236    
237          // If there was an exception during the block of the try
238          // statement, then we should try to close the resource
239          gen.addException(
240            blockBeginLbl,
241            blockEndLbl,
242            catchPrimaryLbl,
243            CodeGeneration.ExceptionEntry.CATCH_ALL);
244    
245          // If an exception occurrs during the automatic closing
246          // of a resource after an exception in the try block...
247          gen.addException(
248            tryCloseBeginLbl,
249            tryCloseEndLbl,
250            catchSuppressedLbl,
251            CodeGeneration.ExceptionEntry.CATCH_ALL);
252        }
253    
254        // outer finally
255        gen.addLabel(outerFinallyLbl);
256        {
257          // if resource != null
258          resourceType.emitLoadLocal(gen, resourceIndex);
259          gen.emitCompare(Bytecode.IFNULL, tryEndLbl);
260          resourceType.emitLoadLocal(gen, resourceIndex);
261          closeMethod().emitInvokeMethod(gen, autoCloseableType);
262        }
263    
264        gen.addLabel(tryEndLbl);
265        gen.emit(Bytecode.NOP);
266      }
267    
268      /**
269       * Lookup the java.lang.Throwable.close() method.
270       */
271      private MethodDecl BasicTWR.closeMethod() {
272        TypeDecl autoCloseableType = lookupType("java.lang", "AutoCloseable");
273        if (autoCloseableType.isUnknown()) {
274          throw new Error("Could not find java.lang.AutoCloseable");
275        }
276        for (MethodDecl method : (Collection<MethodDecl>)
277            autoCloseableType.memberMethods("close")) {
278          if (method.getNumParameter() == 0) {
279            return method;
280          }
281        }
282        throw new Error("Could not find java.lang.AutoCloseable.close()");
283      }
284    
285      /**
286       * Lookup the java.lang.Throwable.addSuppressed(Throwable) method.
287       */
288      private MethodDecl BasicTWR.addSuppressedMethod() {
289        TypeDecl throwableType = lookupType("java.lang", "Throwable");
290        if (throwableType.isUnknown()) {
291          throw new Error("Could not find java.lang.Throwable");
292        }
293        for (MethodDecl method : (Collection<MethodDecl>)
294            throwableType.memberMethods("addSuppressed")) {
295          if (method.getNumParameter() == 1 &&
296              method.getParameter(0).getTypeAccess().type() == throwableType) {
297            return method;
298          }
299        }
300        throw new Error("Could not find java.lang.Throwable.addSuppressed()");
301      }
302    
303      /**
304       * Local indices to store the resources.
305       */
306      eq BasicTWR.getResource().localNum() = localNum();
307    
308      eq TryWithResources.getResource(int index).localNum() = 50;
309      eq TryWithResources.getBlock().localNum() = 100;
310    
311      /**
312       * Local indices for the block are adjusted to account for the resource
313       * variables.
314       */
315      eq BasicTWR.getBlock().localNum() =
316          getResource().localNum()
317          + getResource().type().variableSize()
318          + 2 * lookupType("java.lang", "Throwable").variableSize();
319    }