1 // ASM: a very small and fast Java bytecode manipulation framework 2 // Copyright (c) 2000-2011 INRIA, France Telecom 3 // All rights reserved. 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions 7 // are met: 8 // 1. Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // 2. Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // 3. Neither the name of the copyright holders nor the names of its 14 // contributors may be used to endorse or promote products derived from 15 // this software without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 // THE POSSIBILITY OF SUCH DAMAGE. 28 package org.objectweb.asm.util; 29 30 import java.util.HashSet; 31 import org.objectweb.asm.ModuleVisitor; 32 import org.objectweb.asm.Opcodes; 33 34 /** 35 * A {@link ModuleVisitor} that checks that its methods are properly used. 36 * 37 * @author Remi Forax 38 */ 39 public class CheckModuleAdapter extends ModuleVisitor { 40 /** Whether the visited module is open. */ 41 private final boolean isOpen; 42 43 /** The fully qualified names of the dependencies of the visited module. */ 44 private final NameSet requiredModules = new NameSet("Modules requires"); 45 46 /** The internal names of the packages exported by the visited module. */ 47 private final NameSet exportedPackages = new NameSet("Module exports"); 48 49 /** The internal names of the packages opened by the visited module. */ 50 private final NameSet openedPackages = new NameSet("Module opens"); 51 52 /** The internal names of the services used by the visited module. */ 53 private final NameSet usedServices = new NameSet("Module uses"); 54 55 /** The internal names of the services provided by the visited module. */ 56 private final NameSet providedServices = new NameSet("Module provides"); 57 58 /** The class version number. */ 59 int classVersion; 60 61 /** Whether the {@link #visitEnd} method has been called. */ 62 private boolean visitEndCalled; 63 64 /** 65 * Constructs a new {@link CheckModuleAdapter}. <i>Subclasses must not use this constructor</i>. 66 * Instead, they must use the {@link #CheckModuleAdapter(int, ModuleVisitor, boolean)} version. 67 * 68 * @param moduleVisitor the module visitor to which this adapter must delegate calls. 69 * @param isOpen whether the visited module is open. Open modules have their {@link 70 * Opcodes#ACC_OPEN} access flag set in {@link org.objectweb.asm.ClassVisitor#visitModule}. 71 * @throws IllegalStateException If a subclass calls this constructor. 72 */ CheckModuleAdapter(final ModuleVisitor moduleVisitor, final boolean isOpen)73 public CheckModuleAdapter(final ModuleVisitor moduleVisitor, final boolean isOpen) { 74 this(/* latest api = */ Opcodes.ASM9, moduleVisitor, isOpen); 75 if (getClass() != CheckModuleAdapter.class) { 76 throw new IllegalStateException(); 77 } 78 } 79 80 /** 81 * Constructs a new {@link CheckModuleAdapter}. 82 * 83 * @param api the ASM API version implemented by this visitor. Must be one of the {@code 84 * ASM}<i>x</i> values in {@link Opcodes}. 85 * @param moduleVisitor the module visitor to which this adapter must delegate calls. 86 * @param isOpen whether the visited module is open. Open modules have their {@link 87 * Opcodes#ACC_OPEN} access flag set in {@link org.objectweb.asm.ClassVisitor#visitModule}. 88 */ CheckModuleAdapter( final int api, final ModuleVisitor moduleVisitor, final boolean isOpen)89 protected CheckModuleAdapter( 90 final int api, final ModuleVisitor moduleVisitor, final boolean isOpen) { 91 super(api, moduleVisitor); 92 this.isOpen = isOpen; 93 } 94 95 @Override visitMainClass(final String mainClass)96 public void visitMainClass(final String mainClass) { 97 // Modules can only appear in V9 or more classes. 98 CheckMethodAdapter.checkInternalName(Opcodes.V9, mainClass, "module main class"); 99 super.visitMainClass(mainClass); 100 } 101 102 @Override visitPackage(final String packaze)103 public void visitPackage(final String packaze) { 104 CheckMethodAdapter.checkInternalName(Opcodes.V9, packaze, "module package"); 105 super.visitPackage(packaze); 106 } 107 108 @Override visitRequire(final String module, final int access, final String version)109 public void visitRequire(final String module, final int access, final String version) { 110 checkVisitEndNotCalled(); 111 CheckClassAdapter.checkFullyQualifiedName(Opcodes.V9, module, "required module"); 112 requiredModules.checkNameNotAlreadyDeclared(module); 113 CheckClassAdapter.checkAccess( 114 access, 115 Opcodes.ACC_STATIC_PHASE 116 | Opcodes.ACC_TRANSITIVE 117 | Opcodes.ACC_SYNTHETIC 118 | Opcodes.ACC_MANDATED); 119 if (classVersion >= Opcodes.V10 120 && module.equals("java.base") 121 && (access & (Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE)) != 0) { 122 throw new IllegalArgumentException( 123 "Invalid access flags: " 124 + access 125 + " java.base can not be declared ACC_TRANSITIVE or ACC_STATIC_PHASE"); 126 } 127 super.visitRequire(module, access, version); 128 } 129 130 @Override visitExport(final String packaze, final int access, final String... modules)131 public void visitExport(final String packaze, final int access, final String... modules) { 132 checkVisitEndNotCalled(); 133 CheckMethodAdapter.checkInternalName(Opcodes.V9, packaze, "package name"); 134 exportedPackages.checkNameNotAlreadyDeclared(packaze); 135 CheckClassAdapter.checkAccess(access, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED); 136 if (modules != null) { 137 for (String module : modules) { 138 CheckClassAdapter.checkFullyQualifiedName(Opcodes.V9, module, "module export to"); 139 } 140 } 141 super.visitExport(packaze, access, modules); 142 } 143 144 @Override visitOpen(final String packaze, final int access, final String... modules)145 public void visitOpen(final String packaze, final int access, final String... modules) { 146 checkVisitEndNotCalled(); 147 if (isOpen) { 148 throw new UnsupportedOperationException("An open module can not use open directive"); 149 } 150 CheckMethodAdapter.checkInternalName(Opcodes.V9, packaze, "package name"); 151 openedPackages.checkNameNotAlreadyDeclared(packaze); 152 CheckClassAdapter.checkAccess(access, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED); 153 if (modules != null) { 154 for (String module : modules) { 155 CheckClassAdapter.checkFullyQualifiedName(Opcodes.V9, module, "module open to"); 156 } 157 } 158 super.visitOpen(packaze, access, modules); 159 } 160 161 @Override visitUse(final String service)162 public void visitUse(final String service) { 163 checkVisitEndNotCalled(); 164 CheckMethodAdapter.checkInternalName(Opcodes.V9, service, "service"); 165 usedServices.checkNameNotAlreadyDeclared(service); 166 super.visitUse(service); 167 } 168 169 @Override visitProvide(final String service, final String... providers)170 public void visitProvide(final String service, final String... providers) { 171 checkVisitEndNotCalled(); 172 CheckMethodAdapter.checkInternalName(Opcodes.V9, service, "service"); 173 providedServices.checkNameNotAlreadyDeclared(service); 174 if (providers == null || providers.length == 0) { 175 throw new IllegalArgumentException("Providers cannot be null or empty"); 176 } 177 for (String provider : providers) { 178 CheckMethodAdapter.checkInternalName(Opcodes.V9, provider, "provider"); 179 } 180 super.visitProvide(service, providers); 181 } 182 183 @Override visitEnd()184 public void visitEnd() { 185 checkVisitEndNotCalled(); 186 visitEndCalled = true; 187 super.visitEnd(); 188 } 189 checkVisitEndNotCalled()190 private void checkVisitEndNotCalled() { 191 if (visitEndCalled) { 192 throw new IllegalStateException("Cannot call a visit method after visitEnd has been called"); 193 } 194 } 195 196 private static class NameSet { 197 198 private final String type; 199 private final HashSet<String> names; 200 NameSet(final String type)201 NameSet(final String type) { 202 this.type = type; 203 this.names = new HashSet<>(); 204 } 205 checkNameNotAlreadyDeclared(final String name)206 void checkNameNotAlreadyDeclared(final String name) { 207 if (!names.add(name)) { 208 throw new IllegalArgumentException(type + " '" + name + "' already declared"); 209 } 210 } 211 } 212 } 213