001/* 002 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.compiler.test; 024 025import static java.lang.String.*; 026 027import java.io.*; 028import java.lang.reflect.*; 029import java.nio.file.*; 030import java.util.*; 031import java.util.zip.*; 032 033import jdk.internal.jvmci.options.*; 034import jdk.internal.org.objectweb.asm.*; 035import jdk.internal.org.objectweb.asm.Type; 036 037import org.junit.*; 038 039/** 040 * Verifies a class declaring one or more {@linkplain OptionValue options} has a class initializer 041 * that only initializes the option(s). This sanity check mitigates the possibility of an option 042 * value being used before being set. 043 */ 044public class OptionsVerifierTest { 045 046 @Test 047 public void verifyOptions() throws IOException { 048 try (Classpath cp = new Classpath()) { 049 HashSet<Class<?>> checked = new HashSet<>(); 050 for (Options opts : ServiceLoader.load(Options.class, getClass().getClassLoader())) { 051 for (OptionDescriptor desc : opts) { 052 OptionsVerifier.checkClass(desc.getDeclaringClass(), desc, checked, cp); 053 } 054 } 055 } 056 } 057 058 static class Classpath implements AutoCloseable { 059 private final Map<String, Object> entries = new LinkedHashMap<>(); 060 061 public Classpath() throws ZipException, IOException { 062 String[] names = (System.getProperty("sun.boot.class.path") + File.pathSeparatorChar + System.getProperty("java.class.path")).split(File.pathSeparator); 063 for (String n : names) { 064 File path = new File(n); 065 if (path.exists()) { 066 if (path.isDirectory()) { 067 entries.put(n, path); 068 } else if (n.endsWith(".jar") || n.endsWith(".zip")) { 069 entries.put(n, new ZipFile(path)); 070 } 071 } 072 } 073 } 074 075 public void close() throws IOException { 076 for (Object e : entries.values()) { 077 if (e instanceof ZipFile) { 078 ((ZipFile) e).close(); 079 } 080 } 081 } 082 083 public byte[] getInputStream(String classFilePath) throws IOException { 084 for (Object e : entries.values()) { 085 if (e instanceof File) { 086 File path = new File((File) e, classFilePath.replace('/', File.separatorChar)); 087 if (path.exists()) { 088 return Files.readAllBytes(path.toPath()); 089 } 090 } else if (e instanceof ZipFile) { 091 ZipFile zf = (ZipFile) e; 092 ZipEntry ze = zf.getEntry(classFilePath); 093 if (ze != null) { 094 byte[] res = new byte[(int) ze.getSize()]; 095 DataInputStream dis = new DataInputStream(zf.getInputStream(ze)); 096 dis.readFully(res); 097 dis.close(); 098 return res; 099 } 100 } 101 } 102 return null; 103 } 104 } 105 106 static final class OptionsVerifier extends ClassVisitor { 107 108 public static void checkClass(Class<?> cls, OptionDescriptor option, Set<Class<?>> checked, Classpath cp) throws IOException { 109 if (!checked.contains(cls)) { 110 checked.add(cls); 111 Class<?> superclass = cls.getSuperclass(); 112 if (superclass != null && !superclass.equals(Object.class)) { 113 checkClass(superclass, option, checked, cp); 114 } 115 116 String classFilePath = cls.getName().replace('.', '/') + ".class"; 117 ClassReader cr = new ClassReader(Objects.requireNonNull(cp.getInputStream(classFilePath), "Could not find class file for " + cls.getName())); 118 119 ClassVisitor cv = new OptionsVerifier(cls, option); 120 cr.accept(cv, 0); 121 } 122 } 123 124 /** 125 * The option field context of the verification. 126 */ 127 private final OptionDescriptor option; 128 129 /** 130 * The class in which {@link #option} is declared or a super-class of that class. This is 131 * the class whose {@code <clinit>} method is being verified. 132 */ 133 private final Class<?> cls; 134 135 /** 136 * Source file context for error reporting. 137 */ 138 String sourceFile = null; 139 140 /** 141 * Line number for error reporting. 142 */ 143 int lineNo = -1; 144 145 final Class<?>[] boxingTypes = {Boolean.class, Byte.class, Short.class, Character.class, Integer.class, Float.class, Long.class, Double.class}; 146 147 private static Class<?> resolve(String name) { 148 try { 149 return Class.forName(name.replace('/', '.')); 150 } catch (ClassNotFoundException e) { 151 throw new InternalError(e); 152 } 153 } 154 155 OptionsVerifier(Class<?> cls, OptionDescriptor desc) { 156 super(Opcodes.ASM5); 157 this.cls = cls; 158 this.option = desc; 159 } 160 161 @Override 162 public void visitSource(String source, String debug) { 163 this.sourceFile = source; 164 } 165 166 void verify(boolean condition, String message) { 167 if (!condition) { 168 error(message); 169 } 170 } 171 172 void error(String message) { 173 String errorMessage = format( 174 "%s:%d: Illegal code in %s.<clinit> which may be executed when %s.%s is initialized:%n%n %s%n%n" + "The recommended solution is to move " + option.getName() + 175 " into a separate class (e.g., %s.Options).%n", sourceFile, lineNo, cls.getSimpleName(), option.getDeclaringClass().getSimpleName(), option.getName(), 176 message, option.getDeclaringClass().getSimpleName()); 177 throw new InternalError(errorMessage); 178 179 } 180 181 @Override 182 public MethodVisitor visitMethod(int access, String name, String d, String signature, String[] exceptions) { 183 if (name.equals("<clinit>")) { 184 return new MethodVisitor(Opcodes.ASM5) { 185 186 @Override 187 public void visitLineNumber(int line, Label start) { 188 lineNo = line; 189 } 190 191 @Override 192 public void visitFieldInsn(int opcode, String owner, String fieldName, String fieldDesc) { 193 if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) { 194 verify(resolve(owner).equals(option.getDeclaringClass()), format("store to field %s.%s", resolve(owner).getSimpleName(), fieldName)); 195 verify(opcode != Opcodes.PUTFIELD, format("store to non-static field %s.%s", resolve(owner).getSimpleName(), fieldName)); 196 } 197 } 198 199 private Executable resolveMethod(String owner, String methodName, String methodDesc) { 200 Class<?> declaringClass = resolve(owner); 201 if (methodName.equals("<init>")) { 202 for (Constructor<?> c : declaringClass.getDeclaredConstructors()) { 203 if (methodDesc.equals(Type.getConstructorDescriptor(c))) { 204 return c; 205 } 206 } 207 } else { 208 Type[] argumentTypes = Type.getArgumentTypes(methodDesc); 209 for (Method m : declaringClass.getDeclaredMethods()) { 210 if (m.getName().equals(methodName)) { 211 if (Arrays.equals(argumentTypes, Type.getArgumentTypes(m))) { 212 if (Type.getReturnType(methodDesc).equals(Type.getReturnType(m))) { 213 return m; 214 } 215 } 216 } 217 } 218 } 219 throw new NoSuchMethodError(declaringClass + "." + methodName + methodDesc); 220 } 221 222 /** 223 * Checks whether a given method is allowed to be called. 224 */ 225 private boolean checkInvokeTarget(Executable method) { 226 Class<?> holder = method.getDeclaringClass(); 227 if (method instanceof Constructor) { 228 if (OptionValue.class.isAssignableFrom(holder)) { 229 return true; 230 } 231 } else if (Arrays.asList(boxingTypes).contains(holder)) { 232 return method.getName().equals("valueOf"); 233 } else if (method.getDeclaringClass().equals(Class.class)) { 234 return method.getName().equals("desiredAssertionStatus"); 235 } 236 return false; 237 } 238 239 @Override 240 public void visitMethodInsn(int opcode, String owner, String methodName, String methodDesc, boolean itf) { 241 Executable callee = resolveMethod(owner, methodName, methodDesc); 242 verify(checkInvokeTarget(callee), "invocation of " + callee); 243 } 244 }; 245 } else { 246 return null; 247 } 248 } 249 } 250 251}