"Fossies" - the Fresh Open Source Software Archive

Member "groovy-4.0.12/src/test/org/codehaus/groovy/classgen/asm/AbstractBytecodeTestCase.groovy" (31 Jan 1980, 8207 Bytes) of package /linux/misc/apache-groovy-src-4.0.12.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Java source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /*
    2  *  Licensed to the Apache Software Foundation (ASF) under one
    3  *  or more contributor license agreements.  See the NOTICE file
    4  *  distributed with this work for additional information
    5  *  regarding copyright ownership.  The ASF licenses this file
    6  *  to you under the Apache License, Version 2.0 (the
    7  *  "License"); you may not use this file except in compliance
    8  *  with the License.  You may obtain a copy of the License at
    9  *
   10  *    http://www.apache.org/licenses/LICENSE-2.0
   11  *
   12  *  Unless required by applicable law or agreed to in writing,
   13  *  software distributed under the License is distributed on an
   14  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15  *  KIND, either express or implied.  See the License for the
   16  *  specific language governing permissions and limitations
   17  *  under the License.
   18  */
   19 package org.codehaus.groovy.classgen.asm
   20 
   21 import groovy.test.GroovyTestCase
   22 import org.apache.groovy.io.StringBuilderWriter
   23 import org.codehaus.groovy.control.CompilationUnit
   24 import org.codehaus.groovy.control.CompilerConfiguration
   25 import org.codehaus.groovy.control.Phases
   26 import org.objectweb.asm.ClassReader
   27 import org.objectweb.asm.ClassVisitor
   28 import org.objectweb.asm.FieldVisitor
   29 import org.objectweb.asm.MethodVisitor
   30 import org.objectweb.asm.util.TraceClassVisitor
   31 
   32 import java.security.CodeSource
   33 
   34 /**
   35  * Abstract test case to extend to check the instructions we generate in the bytecode of groovy programs.
   36  */
   37 abstract class AbstractBytecodeTestCase extends GroovyTestCase {
   38 
   39     Class clazz
   40     Map extractionOptions
   41     InstructionSequence sequence
   42 
   43     @Override
   44     protected void setUp() {
   45         super.setUp()
   46         extractionOptions = [method: 'run']
   47     }
   48 
   49     @Override
   50     protected void assertScript(final String script) throws Exception {
   51         CompilationUnit unit = null
   52         GroovyShell shell = new GroovyShell(new GroovyClassLoader() {
   53             @Override
   54             protected CompilationUnit createCompilationUnit(final CompilerConfiguration config, final CodeSource source) {
   55                 unit = super.createCompilationUnit(config, source)
   56             }
   57         })
   58         try {
   59             shell.evaluate(script, testClassName)
   60         } finally {
   61             if (unit != null) {
   62                 try {
   63                     sequence = extractSequence(unit.classes[0].bytes, extractionOptions)
   64                     if (extractionOptions.print) println(sequence)
   65                 } catch (e) {
   66                     // probably an error in the script
   67                 }
   68             }
   69         }
   70     }
   71 
   72     /**
   73      * Compiles a script into bytecode and returns the decompiled string equivalent using ASM.
   74      *
   75      * @param scriptText the script to compile
   76      * @return the decompiled <code>InstructionSequence</code>
   77      */
   78     InstructionSequence compile(Map options = [:], final String scriptText) {
   79         options = [method: 'run', classNamePattern: '.*script', *: options]
   80         sequence = null
   81         clazz = null
   82         def cu = new CompilationUnit()
   83         def su = cu.addSource('script', scriptText)
   84         cu.compile(Phases.CONVERSION)
   85         if (options.conversionAction != null) {
   86             options.conversionAction(su)
   87         }
   88         cu.compile(Phases.CLASS_GENERATION)
   89 
   90         cu.classes.each {
   91             if (it.name ==~ (options.classNamePattern)) {
   92                 sequence = extractSequence(it.bytes, options)
   93             }
   94         }
   95         if (sequence == null && cu.classes) {
   96             sequence = extractSequence(cu.classes[0].bytes, options)
   97         }
   98         cu.classes.each {
   99             try {
  100                 def dep = cu.classLoader.defineClass(it.name, it.bytes)
  101                 if (Script.class.isAssignableFrom(dep)) {
  102                     clazz = dep
  103                 }
  104             } catch (Throwable t) {
  105                 t.printStackTrace()
  106                 System.err.println(sequence)
  107             }
  108         }
  109         sequence
  110     }
  111 
  112     InstructionSequence extractSequence(final byte[] bytes, final Map options = [method: 'run']) {
  113         def out = new StringBuilderWriter()
  114         def tcv
  115         tcv = new TraceClassVisitor(new ClassVisitor(CompilerConfiguration.ASM_API_VERSION) {
  116             @Override
  117             MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String... exceptions) {
  118                 if (options.method == name) {
  119                     // last in "tcv.p.text" is a list that will be filled by "super.visit"
  120                     tcv.p.text.add(tcv.p.text.size() - 2, '--BEGIN--\n')
  121                     try {
  122                         super.visitMethod(access, name, desc, signature, exceptions)
  123                     } finally {
  124                         tcv.p.text.add('--END--\n')
  125                     }
  126                 }
  127             }
  128             @Override
  129             FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
  130                 if (options.field == name) {
  131                     // last in "tcv.p.text" is a list that will be filled by "super.visit"
  132                     tcv.p.text.add(tcv.p.text.size() - 2, '--BEGIN--\n')
  133                     try {
  134                         super.visitField(access, name, desc, signature, value)
  135                     } finally {
  136                         tcv.p.text.add('--END--\n')
  137                     }
  138                 }
  139             }
  140         }, new PrintWriter(out))
  141 
  142         new ClassReader(bytes).accept(tcv, 0)
  143         new InstructionSequence(instructions: out.toString().split('\n')*.trim())
  144     }
  145 }
  146 
  147 /**
  148  * A sequence of instruction with matching and strict matching capabilities
  149  * to find subsequences of bytecode instructions.
  150  */
  151 class InstructionSequence {
  152     List<String> instructions
  153 
  154     /**
  155      * Find a sub-sequence of instructions of the list of instructions.
  156      *
  157      * @param pattern the list of instructions to find in the bytecode
  158      * @param offset at which to find the sub-sequence or remaining sub-sequence (start at offset 0)
  159      * @param strict whether the search should be strict with contiguous instructions (false by default)
  160      * @return true if a match is found
  161      */
  162     boolean hasSequence(final List<String> pattern, final int offset = 0, final boolean strict = false) {
  163         if (pattern.isEmpty()) return true
  164         def idx = offset
  165         while (true) {
  166             idx = indexOf(pattern[0], idx)
  167             if (idx == -1) break
  168             // not the first call with offset 0 and check that the next instruction match
  169             // is the exact following instruction in the pattern and in the bytecode instructions
  170             if (strict && offset > 0 && idx != offset) return false
  171             if (hasSequence(pattern.tail(), idx + 1, strict)) return true
  172             idx += 1
  173         }
  174         return false
  175     }
  176 
  177     /**
  178      * Find a strict sub-sequence of instructions of the list of instructions.
  179      *
  180      * @param pattern the list of instructions to find in the bytecode
  181      * @param offset at which to find the sub-sequence or remaining sub-sequence (start at offset 0)
  182      * @param strict whether the search should be strict with contiguous instructions (true by default)
  183      * @return true if a match is found
  184      */
  185     boolean hasStrictSequence(final List<String> pattern, final int offset = 0) {
  186         hasSequence(pattern, offset, true)
  187     }
  188 
  189     /**
  190      * Finds the index of a single instruction in a list of instructions.
  191      *
  192      * @param singleInst single instruction to find
  193      * @param offset the offset from which to start the search
  194      * @return the index of that single instruction if found, -1 otherwise
  195      */
  196     private int indexOf(final String singleInst, final int offset = 0) {
  197         for (i in offset..<instructions.size()) {
  198             if (instructions[i].startsWith(singleInst)) {
  199                 return i
  200             }
  201         }
  202         return -1
  203     }
  204 
  205     String toSequence() {
  206         def sb = new StringBuilder()
  207         for (insn in instructions) {
  208             sb << "'$insn'," << '\n'
  209         }
  210         sb.toString()
  211     }
  212 
  213     String toString() {
  214         instructions.join('\n')
  215     }
  216 }