1 /* 2 * Copyright 2018 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.turbine.binder; 18 19 import static com.google.common.base.StandardSystemProperty.JAVA_HOME; 20 import static java.util.Objects.requireNonNull; 21 22 import com.google.common.annotations.VisibleForTesting; 23 import com.google.common.base.Supplier; 24 import com.google.common.collect.ImmutableMap; 25 import com.google.turbine.binder.bound.ModuleInfo; 26 import com.google.turbine.binder.bytecode.BytecodeBinder; 27 import com.google.turbine.binder.bytecode.BytecodeBoundClass; 28 import com.google.turbine.binder.env.Env; 29 import com.google.turbine.binder.env.SimpleEnv; 30 import com.google.turbine.binder.lookup.SimpleTopLevelIndex; 31 import com.google.turbine.binder.lookup.TopLevelIndex; 32 import com.google.turbine.binder.sym.ClassSymbol; 33 import com.google.turbine.binder.sym.ModuleSymbol; 34 import com.google.turbine.zip.Zip; 35 import java.io.IOException; 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.util.HashMap; 40 import java.util.Map; 41 import org.jspecify.annotations.Nullable; 42 43 /** Constructs a platform {@link ClassPath} from the current JDK's ct.sym file. */ 44 public final class CtSymClassBinder { 45 bind(int version)46 public static @Nullable ClassPath bind(int version) throws IOException { 47 Path ctSym; 48 String explicitCtSymPath = System.getProperty("turbine.ctSymPath"); 49 if (explicitCtSymPath == null) { 50 String javaHome = JAVA_HOME.value(); 51 requireNonNull(javaHome, "attempted to use --release, but JAVA_HOME is not set"); 52 ctSym = Paths.get(javaHome).resolve("lib/ct.sym"); 53 if (!Files.exists(ctSym)) { 54 throw new IllegalStateException("lib/ct.sym does not exist in " + javaHome); 55 } 56 } else { 57 ctSym = Paths.get(explicitCtSymPath); 58 if (!Files.exists(ctSym)) { 59 throw new IllegalStateException("ct.sym does not exist at " + ctSym); 60 } 61 } 62 Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>(); 63 Map<ModuleSymbol, ModuleInfo> modules = new HashMap<>(); 64 Env<ClassSymbol, BytecodeBoundClass> benv = 65 new Env<ClassSymbol, BytecodeBoundClass>() { 66 @Override 67 public @Nullable BytecodeBoundClass get(ClassSymbol sym) { 68 return map.get(sym); 69 } 70 }; 71 char releaseChar = formatReleaseVersion(version); 72 for (Zip.Entry ze : new Zip.ZipIterable(ctSym)) { 73 String name = ze.name(); 74 if (!name.endsWith(".sig")) { 75 continue; 76 } 77 int idx = name.indexOf('/'); 78 if (idx == -1) { 79 continue; 80 } 81 // check if the directory matches the desired release 82 if (ze.name().substring(0, idx).indexOf(releaseChar) == -1) { 83 continue; 84 } 85 // JDK >= 12 includes the module name as a prefix 86 idx = name.indexOf('/', idx + 1); 87 if (name.substring(name.lastIndexOf('/') + 1).equals("module-info.sig")) { 88 ModuleInfo moduleInfo = BytecodeBinder.bindModuleInfo(name, ze); 89 modules.put(new ModuleSymbol(moduleInfo.name()), moduleInfo); 90 continue; 91 } 92 ClassSymbol sym = new ClassSymbol(name.substring(idx + 1, name.length() - ".sig".length())); 93 map.putIfAbsent(sym, new BytecodeBoundClass(sym, ze, benv, ctSym + "!" + ze.name())); 94 } 95 if (map.isEmpty()) { 96 // we didn't find any classes for the desired release 97 return null; 98 } 99 SimpleEnv<ClassSymbol, BytecodeBoundClass> env = new SimpleEnv<>(ImmutableMap.copyOf(map)); 100 Env<ModuleSymbol, ModuleInfo> moduleEnv = new SimpleEnv<>(ImmutableMap.copyOf(modules)); 101 TopLevelIndex index = SimpleTopLevelIndex.of(env.asMap().keySet()); 102 return new ClassPath() { 103 @Override 104 public Env<ClassSymbol, BytecodeBoundClass> env() { 105 return env; 106 } 107 108 @Override 109 public Env<ModuleSymbol, ModuleInfo> moduleEnv() { 110 return moduleEnv; 111 } 112 113 @Override 114 public TopLevelIndex index() { 115 return index; 116 } 117 118 @Override 119 public @Nullable Supplier<byte[]> resource(String input) { 120 return null; 121 } 122 }; 123 } 124 125 // ct.sym contains directories whose names are the concatenation of a list of target versions 126 // formatted as a single character 0-9 or A-Z (e.g. 789A) and which contain interface class 127 // files with a .sig extension. 128 // This was updated to use 36 as a radix in https://bugs.openjdk.org/browse/JDK-8245544, 129 // it's not clear what the plan is for JDK 36. 130 @VisibleForTesting 131 static char formatReleaseVersion(int n) { 132 if (n <= 4 || n >= 36) { 133 throw new IllegalArgumentException("invalid release version: " + n); 134 } 135 if (n < 10) { 136 return (char) ('0' + n); 137 } else { 138 return (char) ('A' + n - 10); 139 } 140 } 141 142 private CtSymClassBinder() {} 143 } 144