View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.bcel.verifier;
19  
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  
30  import org.apache.bcel.classfile.JavaClass;
31  import org.apache.commons.exec.CommandLine;
32  import org.apache.commons.exec.DefaultExecuteResultHandler;
33  import org.apache.commons.exec.DefaultExecutor;
34  import org.apache.commons.exec.ExecuteException;
35  import org.apache.commons.exec.ExecuteWatchdog;
36  import org.apache.commons.exec.PumpStreamHandler;
37  import org.apache.commons.lang3.ArrayUtils;
38  import org.apache.commons.lang3.SystemProperties;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * Test a number of BCEL issues related to running the Verifier on a bad or malformed .class file and having it die with
43   * an exception rather than report a verification failure.
44   */
45  public class VerifyBadClassesTestCase {
46  
47      private List<String> buildVerifyCommand(final String className, final String testDir) {
48          final List<String> command = new ArrayList<>();
49          command.add("java");
50          command.add("-ea");
51  
52          command.add("-classpath");
53          command.add(SystemProperties.getJavaClassPath() + ":" + testDir);
54  
55          command.add("org.apache.bcel.verifier.Verifier");
56          command.add(className);
57  
58          return command;
59      }
60  
61      /**
62       * Runs the given command synchronously in the given directory. If the command completes normally, returns a
63       * {@link Status} object capturing the command, exit status, and output from the process.
64       *
65       * @param command the command to be run in the process
66       * @return a String capturing the error output of executing the command
67       * @throws ExecuteException if executor fails
68       * @throws IOException if executor fails
69       */
70      private String run(final List<String> command) throws ExecuteException, IOException {
71  
72          /** The process timeout in milliseconds. Defaults to 30 seconds. */
73          final long timeout = 30 * 1000;
74  
75          final String[] args = command.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
76          final CommandLine cmdLine = new CommandLine(args[0]); // constructor requires executable name
77          cmdLine.addArguments(Arrays.copyOfRange(args, 1, args.length));
78  
79          final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
80          final DefaultExecutor executor = new DefaultExecutor();
81  
82          final ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
83          executor.setWatchdog(watchdog);
84  
85          final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
86          final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
87          final PumpStreamHandler streamHandler = new PumpStreamHandler(outStream, errStream);
88          executor.setStreamHandler(streamHandler);
89          executor.execute(cmdLine, resultHandler);
90  
91          int exitValue = -1;
92          try {
93              resultHandler.waitFor();
94              exitValue = resultHandler.getExitValue();
95          } catch (final InterruptedException e) {
96              // Ignore exception, but watchdog.killedProcess() records that the process timed out.
97          }
98          final boolean timedOut = executor.isFailure(exitValue) && watchdog.killedProcess();
99          if (timedOut) {
100             return "Command timed out.";
101         }
102 
103         // return "stdout: " + outStream.toString() + "\nstderr: " + errStream.toString();
104         return errStream.toString();
105     }
106 
107     /**
108      * BCEL-303: AssertionViolatedException in Pass 3A Verification of invoke instructions
109      */
110     @Test
111     public void testB303() {
112         testVerify("issue303/example", "A");
113     }
114 
115     /**
116      * BCEL-307: ClassFormatException thrown in Pass 3A verification
117      */
118     @Test
119     public void testB307() {
120         testVerify("issue307/example", "A");
121     }
122 
123     /**
124      * BCEL-308: NullPointerException in Verifier Pass 3A
125      */
126     @Test
127     public void testB308() {
128         testVerify("issue308", "Hello");
129     }
130 
131     /**
132      * BCEL-309: NegativeArraySizeException when Code attribute length is negative
133      */
134     @Test
135     public void testB309() {
136         testVerify("issue309", "Hello");
137     }
138 
139     /**
140      * BCEL-310: ArrayIndexOutOfBounds in Verifier Pass 3A
141      */
142     @Test
143     public void testB310() {
144         testVerify("issue310", "Hello");
145     }
146 
147     /**
148      * BCEL-311: ClassCastException in Verifier Pass 2
149      */
150     @Test
151     public void testB311() {
152         testVerify("issue311", "Hello");
153     }
154 
155     /**
156      * BCEL-312: AssertionViolation: INTERNAL ERROR Please adapt StringRepresentation to deal with ConstantPackage in
157      * Verifier Pass 2
158      */
159     @Test
160     public void testB312() {
161         testVerify("issue312", "Hello");
162     }
163 
164     /**
165      * BCEL-313: ClassFormatException: Invalid signature: Ljava/lang/String)V in Verifier Pass 3A
166      */
167     @Test
168     public void testB313() {
169         testVerify("issue313", "Hello");
170     }
171 
172     /**
173      * BCEL-337: StringIndexOutOfBounds in Pass 2 Verification of empty method names in the constant pool
174      */
175     @Test
176     public void testB337() {
177         testVerify("issue337/example", "A");
178     }
179 
180     /**
181      * Note that the test classes are bad or malformed and this causes the animal-sniffer-maven-plugin to fail during the
182      * build/verification process. I was not able to figure out the right incantations to get it to ignore these files.
183      * Hence, their file extension is '.classx' to avoid this problem. As part of the test process we rename them to
184      * '.class' and then back to '.classx' after the test. If we can get animal-sniffer to ignore the files, these steps
185      * could be omitted.
186      */
187     private void testVerify(final String directory, final String className) {
188         final String baseDir = "target/test-classes";
189         final String testDir = baseDir + (directory.isEmpty() ? "" : File.separator + directory);
190 
191         final File origFile = new File(testDir, className + ".classx");
192         final File testFile = new File(testDir, className + JavaClass.EXTENSION);
193 
194         if (!origFile.renameTo(testFile)) {
195             fail("Failed to rename orig file");
196         }
197 
198         String result;
199         try {
200             result = run(buildVerifyCommand(className, testDir));
201         } catch (final Exception e) {
202             result = e.getMessage();
203         }
204 
205         if (!testFile.renameTo(origFile)) {
206             fail("Failed to rename test file");
207         }
208 
209         assertTrue(result.isEmpty(), result);
210     }
211 }