• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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