"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 }