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