PageRenderTime 29ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/netbeans-7.3/nbbuild/antsrc/org/netbeans/nbbuild/VerifyClassLinkage.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 367 lines | 250 code | 18 blank | 99 comment | 59 complexity | 6241e1d27ba8fc988df4ea20a7824aab MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.nbbuild;
  45. import java.io.ByteArrayInputStream;
  46. import java.io.ByteArrayOutputStream;
  47. import java.io.DataInput;
  48. import java.io.DataInputStream;
  49. import java.io.File;
  50. import java.io.IOException;
  51. import java.io.InputStream;
  52. import java.util.Arrays;
  53. import java.util.Collections;
  54. import java.util.Enumeration;
  55. import java.util.HashMap;
  56. import java.util.HashSet;
  57. import java.util.Map;
  58. import java.util.Set;
  59. import java.util.TreeMap;
  60. import java.util.TreeSet;
  61. import java.util.concurrent.atomic.AtomicInteger;
  62. import java.util.jar.Attributes;
  63. import java.util.jar.JarEntry;
  64. import java.util.jar.JarFile;
  65. import java.util.jar.Manifest;
  66. import java.util.regex.Pattern;
  67. import org.apache.tools.ant.AntClassLoader;
  68. import org.apache.tools.ant.BuildException;
  69. import org.apache.tools.ant.Project;
  70. import org.apache.tools.ant.Task;
  71. import org.apache.tools.ant.types.Path;
  72. /**
  73. * Verifies linkage between classes in a JAR (typically a module).
  74. * @author Jesse Glick
  75. * @see "#71675"
  76. * @see <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">Class file spec</a>
  77. */
  78. //-------------------------
  79. //jglick: when considering rewrites please check https://github.com/jenkinsci/constant-pool-scanner
  80. //-------------------------
  81. public class VerifyClassLinkage extends Task {
  82. public VerifyClassLinkage() {}
  83. /*
  84. private boolean verifyMainJar = true;
  85. private boolean verifyClassPathExtensions = true;
  86. public void setVerifyClassPathExtensions(boolean verifyClassPathExtensions) {
  87. this.verifyClassPathExtensions = verifyClassPathExtensions;
  88. }
  89. public void setVerifyMainJar(boolean verifyMainJar) {
  90. this.verifyMainJar = verifyMainJar;
  91. }
  92. */
  93. private File jar;
  94. private boolean failOnError = true;
  95. private boolean warnOnDefaultPackage = true;
  96. private Path classpath = new Path(getProject());
  97. private String ignores;
  98. private int maxWarnings = Integer.MAX_VALUE;
  99. /**
  100. * Intended static classpath for this JAR.
  101. * Any classes loaded in this JAR (and its Class-Path extensions)
  102. * must be linkable against this classpath plus the JAR (and extensions) itself.
  103. */
  104. public Path createClasspath() {
  105. return classpath.createPath();
  106. }
  107. /**
  108. * Specify the main JAR file.
  109. * Automatically searches in Class-Path extensions too.
  110. */
  111. public void setJar(File jar) {
  112. this.jar = jar;
  113. }
  114. /**
  115. * If true (default), halt build on error, rather than just
  116. * reporting a warning.
  117. */
  118. public void setFailOnError(boolean failOnError) {
  119. this.failOnError = failOnError;
  120. }
  121. /**
  122. * Sets the pattern for classes that are not verified.
  123. * Allows to skip linkage verification of some classes.
  124. */
  125. public void setIgnores(String ignores) {
  126. this.ignores = ignores;
  127. }
  128. /**
  129. * If true (default), warn if any classes are found in the default
  130. * package. Never halts the build even if {@link #setFailOnError} true.
  131. */
  132. public void setWarnOnDefaultPackage(boolean warnOnDefaultPackage) {
  133. this.warnOnDefaultPackage = warnOnDefaultPackage;
  134. }
  135. /**
  136. * Limit the number of warnings that will be generated in one task run.
  137. * If there are more warnings than this, they will not be reported.
  138. */
  139. public void setMaxWarnings(int maxWarnings) {
  140. if (maxWarnings <= 0) {
  141. throw new IllegalArgumentException();
  142. }
  143. this.maxWarnings = maxWarnings;
  144. }
  145. public @Override void execute() throws BuildException {
  146. if (jar == null) {
  147. throw new BuildException("Must specify a JAR file", getLocation());
  148. }
  149. try {
  150. // Map from class name (foo/Bar format) to true (found), false (not found), null (as yet unknown):
  151. Map<String,Boolean> loadable = new HashMap<String,Boolean>();
  152. Map<String,byte[]> classfiles = new TreeMap<String,byte[]>();
  153. JarFile jf = new JarFile(jar);
  154. try {
  155. read(jf, classfiles, new HashSet<File>(Collections.singleton(jar)), this, ignores);
  156. } finally {
  157. jf.close();
  158. }
  159. for (String clazz: classfiles.keySet()) {
  160. // All classes we define are obviously loadable:
  161. loadable.put(clazz, Boolean.TRUE);
  162. if (warnOnDefaultPackage && clazz.indexOf('.') == -1) {
  163. log("Warning: class '" + clazz + "' found in default package", Project.MSG_WARN);
  164. }
  165. }
  166. // XXX should use a load-nothing parent and require nbjdk.bootclasspath to be added explicitly to CP
  167. // otherwise e.g. libs.jsr223 dep can be removed from contrib/java.hints.scripting without warning if building on JDK 6
  168. ClassLoader loader = new AntClassLoader(ClassLoader.getSystemClassLoader().getParent(), getProject(), classpath, true);
  169. AtomicInteger max = new AtomicInteger(maxWarnings);
  170. for (Map.Entry<String, byte[]> entry: classfiles.entrySet()) {
  171. String clazz = entry.getKey();
  172. byte[] data = entry.getValue();
  173. verify(clazz, data, loadable, loader, max);
  174. if (max.get() < 0) {
  175. break;
  176. }
  177. }
  178. } catch (IOException e) {
  179. throw new BuildException("While verifying " + jar + " or its Class-Path extensions: " + e, e, getLocation());
  180. }
  181. }
  182. static void read(JarFile jf, Map<String, byte[]> classfiles, Set<File> alreadyRead, Task task, String ignores) throws IOException {
  183. File jar = new File(jf.getName());
  184. task.log("Reading " + jar, Project.MSG_VERBOSE);
  185. Pattern p = (ignores != null)? Pattern.compile(ignores): null;
  186. Enumeration<JarEntry> e = jf.entries();
  187. while (e.hasMoreElements()) {
  188. JarEntry entry = e.nextElement();
  189. String name = entry.getName();
  190. if (!name.endsWith(".class")) {
  191. continue;
  192. }
  193. String clazz = name.substring(0, name.length() - 6).replace('/', '.');
  194. if (p != null && p.matcher(clazz).matches()) {
  195. continue;
  196. }
  197. ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.max((int) entry.getSize(), 0));
  198. InputStream is = jf.getInputStream(entry);
  199. try {
  200. byte[] buf = new byte[4096];
  201. int read;
  202. while ((read = is.read(buf)) != -1) {
  203. baos.write(buf, 0, read);
  204. }
  205. } finally {
  206. is.close();
  207. }
  208. classfiles.put(clazz, baos.toByteArray());
  209. }
  210. Manifest mf = jf.getManifest();
  211. if (mf != null) {
  212. String cp = mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
  213. if (cp != null) {
  214. String[] uris = cp.trim().split("[, ]+");
  215. for (int i = 0; i < uris.length; i++) {
  216. File otherJar = new File(jar.toURI().resolve(uris[i]));
  217. if (alreadyRead.add(otherJar)) {
  218. if (otherJar.isFile()) {
  219. JarFile otherJF = new JarFile(otherJar);
  220. try {
  221. read(otherJF, classfiles, alreadyRead, task, ignores);
  222. } finally {
  223. otherJF.close();
  224. }
  225. }
  226. } else {
  227. task.log("Already read " + jar, Project.MSG_VERBOSE);
  228. }
  229. }
  230. }
  231. }
  232. }
  233. private void verify(String clazz, byte[] data, Map<String,Boolean> loadable, ClassLoader loader, AtomicInteger maxWarn)
  234. throws IOException, BuildException {
  235. //log("Verifying linkage of " + clazz.replace('/', '.'), Project.MSG_DEBUG);
  236. Set<String> dependencies = dependencies(data);
  237. //System.err.println(clazz + " -> " + dependencies);
  238. for (String clazz2 : dependencies) {
  239. Boolean exists = loadable.get(clazz2);
  240. if (exists == null) {
  241. exists = loader.getResource(clazz2.replace('.', '/') + ".class") != null;
  242. loadable.put(clazz2, exists);
  243. }
  244. if (!exists) {
  245. String message = clazz + " cannot access " + clazz2;
  246. if (failOnError) {
  247. throw new BuildException(message, getLocation());
  248. } else if (maxWarn.getAndDecrement() > 0) {
  249. log("Warning: " + message, Project.MSG_WARN);
  250. } else {
  251. log("(additional warnings not reported)", Project.MSG_WARN);
  252. return;
  253. }
  254. } else {
  255. //log("Working reference to " + clazz2, Project.MSG_DEBUG);
  256. }
  257. }
  258. }
  259. private static void skip(DataInput input, int bytes) throws IOException {
  260. int skipped = input.skipBytes(bytes);
  261. if (skipped != bytes) {
  262. throw new IOException("Truncated class file");
  263. }
  264. }
  265. static Set<String> dependencies(byte[] data) throws IOException {
  266. Set<String> result = new TreeSet<String>();
  267. DataInput input = new DataInputStream(new ByteArrayInputStream(data));
  268. skip(input, 8); // magic, minor_version, major_version
  269. int size = input.readUnsignedShort() - 1; // constantPoolCount
  270. String[] utf8Strings = new String[size];
  271. boolean[] isClassName = new boolean[size];
  272. boolean[] isDescriptor = new boolean[size];
  273. for (int i = 0; i < size; i++) {
  274. byte tag = input.readByte();
  275. switch (tag) {
  276. case 1: // CONSTANT_Utf8
  277. utf8Strings[i] = input.readUTF();
  278. break;
  279. case 7: // CONSTANT_Class
  280. int index = input.readUnsignedShort() - 1;
  281. if (index >= size) {
  282. throw new IOException("@" + i + ": CONSTANT_Class_info.name_index " + index + " too big for size of pool " + size);
  283. }
  284. //log("Class reference at " + index, Project.MSG_DEBUG);
  285. isClassName[index] = true;
  286. break;
  287. case 3: // CONSTANT_Integer
  288. case 4: // CONSTANT_Float
  289. case 9: // CONSTANT_Fieldref
  290. case 10: // CONSTANT_Methodref
  291. case 11: // CONSTANT_InterfaceMethodref
  292. skip(input, 4);
  293. break;
  294. case 12: // CONSTANT_NameAndType
  295. skip(input, 2);
  296. index = input.readUnsignedShort() - 1;
  297. if (index >= size || index < 0) {
  298. throw new IOException("@" + i + ": CONSTANT_NameAndType_info.descriptor_index " + index + " too big for size of pool " + size);
  299. }
  300. isDescriptor[index] = true;
  301. break;
  302. case 8: // CONSTANT_String
  303. skip(input, 2);
  304. break;
  305. case 5: // CONSTANT_Long
  306. case 6: // CONSTANT_Double
  307. skip(input, 8);
  308. i++; // weirdness in spec
  309. break;
  310. default:
  311. throw new IOException("Unrecognized constant pool tag " + tag + " at index " + i +
  312. "; running UTF-8 strings: " + Arrays.asList(utf8Strings));
  313. }
  314. }
  315. //task.log("UTF-8 strings: " + Arrays.asList(utf8Strings), Project.MSG_DEBUG);
  316. for (int i = 0; i < size; i++) {
  317. String s = utf8Strings[i];
  318. if (isClassName[i]) {
  319. while (s.charAt(0) == '[') {
  320. // array type
  321. s = s.substring(1);
  322. }
  323. if (s.length() == 1) {
  324. // primitive
  325. continue;
  326. }
  327. String c;
  328. if (s.charAt(s.length() - 1) == ';' && s.charAt(0) == 'L') {
  329. // Uncommon but seems sometimes this happens.
  330. c = s.substring(1, s.length() - 1);
  331. } else {
  332. c = s;
  333. }
  334. result.add(c.replace('/', '.'));
  335. } else if (isDescriptor[i]) {
  336. int idx = 0;
  337. while ((idx = s.indexOf('L', idx)) != -1) {
  338. int semi = s.indexOf(';', idx);
  339. if (semi == -1) {
  340. throw new IOException("Invalid type or descriptor: " + s);
  341. }
  342. result.add(s.substring(idx + 1, semi).replace('/', '.'));
  343. idx = semi;
  344. }
  345. }
  346. }
  347. return result;
  348. }
  349. }