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.junit.jupiter.api.Test;
39  
40  /**
41   * Test a number of BCEL issues related to running the Verifier on a bad or malformed .class file and having it die with
42   * an exception rather than report a verification failure.
43   */
44  public class VerifyBadClassesTestCase {
45  
46      private List<String> buildVerifyCommand(final String className, final String testDir) {
47          final List<String> command = new ArrayList<>();
48          command.add("java");
49          command.add("-ea");
50  
51          command.add("-classpath");
52          command.add(System.getProperty("java.class.path") + ":" + testDir);
53  
54          command.add("org.apache.bcel.verifier.Verifier");
55          command.add(className);
56  
57          return command;
58      }
59  
60      /**
61       * Runs the given command synchronously in the given directory. If the command completes normally, returns a
62       * {@link Status} object capturing the command, exit status, and output from the process.
63       *
64       * @param command the command to be run in the process
65       * @return a String capturing the error output of executing the command
66       * @throws ExecuteException if executor fails
67       * @throws IOException if executor fails
68       */
69      private String run(final List<String> command) throws ExecuteException, IOException {
70  
71          /** The process timeout in milliseconds. Defaults to 30 seconds. */
72          final long timeout = 30 * 1000;
73  
74          final String[] args = command.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
75          final CommandLine cmdLine = new CommandLine(args[0]); // constructor requires executable name
76          cmdLine.addArguments(Arrays.copyOfRange(args, 1, args.length));
77  
78          final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
79          final DefaultExecutor executor = new DefaultExecutor();
80  
81          final ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
82          executor.setWatchdog(watchdog);
83  
84          final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
85          final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
86          final PumpStreamHandler streamHandler = new PumpStreamHandler(outStream, errStream);
87          executor.setStreamHandler(streamHandler);
88          executor.execute(cmdLine, resultHandler);
89  
90          int exitValue = -1;
91          try {
92              resultHandler.waitFor();
93              exitValue = resultHandler.getExitValue();
94          } catch (final InterruptedException e) {
95              // Ignore exception, but watchdog.killedProcess() records that the process timed out.
96          }
97          final boolean timedOut = executor.isFailure(exitValue) && watchdog.killedProcess();
98          if (timedOut) {
99              return "Command timed out.";
100         }
101 
102         // return "stdout: " + outStream.toString() + "\nstderr: " + errStream.toString();
103         return errStream.toString();
104     }
105 
106     /**
107      * BCEL-303: AssertionViolatedException in Pass 3A Verification of invoke instructions
108      */
109     @Test
110     public void testB303() {
111         testVerify("issue303/example", "A");
112     }
113 
114     /**
115      * BCEL-307: ClassFormatException thrown in Pass 3A verification
116      */
117     @Test
118     public void testB307() {
119         testVerify("issue307/example", "A");
120     }
121 
122     /**
123      * BCEL-308: NullPointerException in Verifier Pass 3A
124      */
125     @Test
126     public void testB308() {
127         testVerify("issue308", "Hello");
128     }
129 
130     /**
131      * BCEL-309: NegativeArraySizeException when Code attribute length is negative
132      */
133     @Test
134     public void testB309() {
135         testVerify("issue309", "Hello");
136     }
137 
138     /**
139      * BCEL-310: ArrayIndexOutOfBounds in Verifier Pass 3A
140      */
141     @Test
142     public void testB310() {
143         testVerify("issue310", "Hello");
144     }
145 
146     /**
147      * BCEL-311: ClassCastException in Verifier Pass 2
148      */
149     @Test
150     public void testB311() {
151         testVerify("issue311", "Hello");
152     }
153 
154     /**
155      * BCEL-312: AssertionViolation: INTERNAL ERROR Please adapt StringRepresentation to deal with ConstantPackage in
156      * Verifier Pass 2
157      */
158     @Test
159     public void testB312() {
160         testVerify("issue312", "Hello");
161     }
162 
163     /**
164      * BCEL-313: ClassFormatException: Invalid signature: Ljava/lang/String)V in Verifier Pass 3A
165      */
166     @Test
167     public void testB313() {
168         testVerify("issue313", "Hello");
169     }
170 
171     /**
172      * BCEL-337: StringIndexOutOfBounds in Pass 2 Verification of empty method names in the constant pool
173      */
174     @Test
175     public void testB337() {
176         testVerify("issue337/example", "A");
177     }
178 
179     /**
180      * Note that the test classes are bad or malformed and this causes the animal-sniffer-maven-plugin to fail during the
181      * build/verification process. I was not able to figure out the right incantations to get it to ignore these files.
182      * Hence, their file extension is '.classx' to avoid this problem. As part of the test process we rename them to
183      * '.class' and then back to '.classx' after the test. If we can get animal-sniffer to ignore the files, these steps
184      * could be omitted.
185      */
186     private void testVerify(final String directory, final String className) {
187         final String baseDir = "target/test-classes";
188         final String testDir = baseDir + (directory.isEmpty() ? "" : File.separator + directory);
189 
190         final File origFile = new File(testDir, className + ".classx");
191         final File testFile = new File(testDir, className + JavaClass.EXTENSION);
192 
193         if (!origFile.renameTo(testFile)) {
194             fail("Failed to rename orig file");
195         }
196 
197         String result;
198         try {
199             result = run(buildVerifyCommand(className, testDir));
200         } catch (final Exception e) {
201             result = e.getMessage();
202         }
203 
204         if (!testFile.renameTo(origFile)) {
205             fail("Failed to rename test file");
206         }
207 
208         assertTrue(result.isEmpty(), result);
209     }
210 }