Skip to content

Commit a5d6404

Browse files
lukesandbergBrendan Linn
authored andcommitted
Soy Jbcsrc: Have our in memory classloaders delegate to the classloader that loaded them rather than the system classloader and apply the same protection domain
For most servers the system classloader is sufficient, but on app-engine things are different (the application and thus the compiler are loaded in a sub classloader), so in order to make sure that the compiled classes can access all the runtime libraries they need to delegate to the correct parent loader. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=105452900
1 parent ecb82ab commit a5d6404

File tree

7 files changed

+130
-123
lines changed

7 files changed

+130
-123
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2015 Google Inc.
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.template.soy.jbcsrc;
18+
19+
import com.google.common.base.Throwables;
20+
21+
import java.net.URL;
22+
import java.security.AccessController;
23+
import java.security.PrivilegedAction;
24+
import java.security.ProtectionDomain;
25+
26+
import javax.annotation.Nullable;
27+
28+
/**
29+
* Base class to share code between our custom memory based classloader implementations.
30+
*/
31+
abstract class AbstractMemoryClassLoader extends ClassLoader {
32+
private static final ProtectionDomain DEFAULT_PROTECTION_DOMAIN;
33+
34+
static {
35+
ClassLoader.registerAsParallelCapable();
36+
37+
DEFAULT_PROTECTION_DOMAIN =
38+
AccessController.doPrivileged(
39+
new PrivilegedAction<ProtectionDomain>() {
40+
@Override
41+
public ProtectionDomain run() {
42+
return MemoryClassLoader.class.getProtectionDomain();
43+
}
44+
});
45+
}
46+
47+
AbstractMemoryClassLoader() {
48+
// We want our loaded classes to be a child classloader of ours to make sure they have access
49+
// to the same classes that we do.
50+
super(AbstractMemoryClassLoader.class.getClassLoader());
51+
}
52+
53+
/** Returns a data object for a class with the given name or {@code null} if it doesn't exist. */
54+
@Nullable
55+
abstract ClassData getClassData(String name);
56+
57+
@Override
58+
protected final Class<?> findClass(String name) throws ClassNotFoundException {
59+
ClassData classDef = getClassData(name);
60+
if (classDef == null) {
61+
throw new ClassNotFoundException(name);
62+
}
63+
try {
64+
return super.defineClass(
65+
name, classDef.data(), 0, classDef.data().length, DEFAULT_PROTECTION_DOMAIN);
66+
} catch (Throwable t) {
67+
// Attach additional information in a suppressed exception to make debugging easier.
68+
t.addSuppressed(new RuntimeException("Failed to load generated class:\n" + classDef));
69+
Throwables.propagateIfInstanceOf(t, ClassNotFoundException.class);
70+
throw Throwables.propagate(t);
71+
}
72+
}
73+
74+
@Override
75+
protected final URL findResource(final String name) {
76+
if (!name.endsWith(".class")) {
77+
return null;
78+
}
79+
String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
80+
ClassData classDef = getClassData(className);
81+
if (classDef == null) {
82+
return null;
83+
}
84+
return classDef.asUrl();
85+
}
86+
}
87+

java/src/com/google/template/soy/jbcsrc/BytecodeCompiler.java

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.google.template.soy.soytree.TemplateNode;
2727
import com.google.template.soy.soytree.TemplateRegistry;
2828

29+
import java.util.ArrayList;
30+
import java.util.List;
2931
import java.util.logging.Level;
3032
import java.util.logging.Logger;
3133

@@ -69,10 +71,9 @@ public static Optional<CompiledTemplates> compile(
6971
if (reporter.errorsSince(checkpoint)) {
7072
return Optional.absent();
7173
}
72-
CompiledTemplates templates =
74+
CompiledTemplates templates =
7375
new CompiledTemplates(
74-
compilerRegistry.getTemplateNames(),
75-
results.loader());
76+
compilerRegistry.getTemplateNames(), new MemoryClassLoader(results.classes()));
7677
logger.log(
7778
Level.INFO,
7879
"Compilation took {0}\n"
@@ -83,8 +84,8 @@ public static Optional<CompiledTemplates> compile(
8384
+ " detachStates: {5}",
8485
new Object[] {
8586
stopwatch.toString(),
86-
results.numTemplates(),
87-
results.numClasses(),
87+
registry.getAllTemplates().size(),
88+
results.classes().size(),
8889
results.numBytes(),
8990
results.numFields(),
9091
results.numDetachStates()
@@ -107,11 +108,7 @@ private static void checkForUnsupportedFeatures(TemplateRegistry registry,
107108

108109
@AutoValue
109110
abstract static class CompilationResult {
110-
abstract MemoryClassLoader loader();
111-
112-
abstract int numTemplates();
113-
114-
abstract int numClasses();
111+
abstract List<ClassData> classes();
115112

116113
abstract int numBytes();
117114

@@ -128,17 +125,14 @@ private static CompilationResult compileTemplates(
128125
TemplateRegistry registry,
129126
CompiledTemplateRegistry compilerRegistry,
130127
ErrorReporter errorReporter) {
131-
int numTemplates = 0;
132-
int numClasses = 0;
128+
List<ClassData> classes = new ArrayList<>();
133129
int numBytes = 0;
134130
int numFields = 0;
135131
int numDetachStates = 0;
136-
MemoryClassLoader.Builder builder = new MemoryClassLoader.Builder();
137132
// We generate all the classes and then start loading them. This 2 phase process ensures that
138133
// we don't have to worry about ordering (where a class we have generated references a class we
139134
// haven't generated yet), because none of the classes are loadable until they all are.
140135
for (TemplateNode template : registry.getAllTemplates()) {
141-
numTemplates++;
142136
String name = template.getTemplateName();
143137
logger.log(Level.FINE, "Compiling template: {0}", name);
144138
try {
@@ -153,14 +147,13 @@ private static CompilationResult compileTemplates(
153147
new Object[] {clazz.type().className(), clazz.data().length,
154148
clazz.numberOfFields(), clazz.numberOfDetachStates()});
155149
}
156-
numClasses++;
157150
numBytes += clazz.data().length;
158151
numFields += clazz.numberOfFields();
159152
numDetachStates += clazz.numberOfDetachStates();
160153
if (Flags.DEBUG) {
161154
clazz.checkClass();
162155
}
163-
builder.add(clazz);
156+
classes.add(clazz);
164157
}
165158
// Report unexpected errors and keep going to try to collect more.
166159
} catch (UnexpectedCompilerFailureException e) {
@@ -179,7 +172,7 @@ private static CompilationResult compileTemplates(
179172
}
180173
}
181174
return new AutoValue_BytecodeCompiler_CompilationResult(
182-
builder.build(), numTemplates, numClasses, numBytes, numFields, numDetachStates);
175+
classes, numBytes, numFields, numDetachStates);
183176
}
184177

185178
private BytecodeCompiler() {}

java/src/com/google/template/soy/jbcsrc/CompilingClassLoader.java

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@
1616

1717
package com.google.template.soy.jbcsrc;
1818

19-
import com.google.common.base.Throwables;
20-
21-
import java.net.URL;
2219
import java.util.Collections;
2320
import java.util.HashMap;
2421
import java.util.Map;
2522

2623
/**
2724
* A classloader that can compile templates on demand.
2825
*/
29-
final class CompilingClassLoader extends ClassLoader {
26+
final class CompilingClassLoader extends AbstractMemoryClassLoader {
3027
static {
3128
// See http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html
3229
ClassLoader.registerAsParallelCapable();
@@ -40,48 +37,18 @@ final class CompilingClassLoader extends ClassLoader {
4037
private final CompiledTemplateRegistry registry;
4138

4239
CompilingClassLoader(CompiledTemplateRegistry registry) {
43-
super(ClassLoader.getSystemClassLoader());
4440
this.registry = registry;
4541
}
46-
47-
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
42+
43+
@Override
44+
ClassData getClassData(String name) {
4845
ClassData classDef = classesByName.get(name);
49-
if (classDef == null) {
50-
// We haven't already compiled it (and haven't already loaded it) so try to find the matching
51-
// template.
52-
classDef = compile(name);
53-
if (classDef == null) {
54-
throw new ClassNotFoundException(name);
55-
}
46+
if (classDef != null) {
47+
return classDef;
5648
}
57-
try {
58-
return super.defineClass(name, classDef.data(), 0, classDef.data().length);
59-
} catch (Throwable t) {
60-
// Attach additional information in a suppressed exception to make debugging easier.
61-
t.addSuppressed(new RuntimeException("Failed to load generated class:\n" + classDef));
62-
Throwables.propagateIfInstanceOf(t, ClassNotFoundException.class);
63-
throw Throwables.propagate(t);
64-
}
65-
}
66-
67-
@Override protected URL findResource(final String name) {
68-
if (!name.endsWith(".class")) {
69-
return null;
70-
}
71-
String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
72-
ClassData classDef = classesByName.get(className);
73-
if (classDef == null) {
74-
// We haven't already compiled it (and haven't already loaded it) so try to find the matching
75-
// template.
76-
classDef = compile(name);
77-
if (classDef == null) {
78-
return null;
79-
}
80-
}
81-
return classDef.asUrl();
82-
}
49+
// We haven't already compiled it (and haven't already loaded it) so try to find the matching
50+
// template.
8351

84-
private ClassData compile(String name) {
8552
// For each template we compile there are only two 'public' classes that could be loaded prior
8653
// to compiling the template. The CompiledTemplate.Factory class and the CompiledTemplate itself
8754
boolean isFactory = name.endsWith("$" + StandardNames.FACTORY_CLASS);

java/src/com/google/template/soy/jbcsrc/MemoryClassLoader.java

Lines changed: 11 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,19 @@
1616

1717
package com.google.template.soy.jbcsrc;
1818

19-
import com.google.common.base.Throwables;
2019
import com.google.common.collect.ImmutableMap;
2120

22-
import java.net.URL;
23-
import java.util.LinkedHashMap;
24-
import java.util.Map;
25-
2621
/**
27-
* A {@link ClassLoader} that can load classes from a configured set of {@code byte[]}s.
22+
* A {@link ClassLoader} that can load classes from a configured set of {@code byte[]}s.
2823
*/
29-
final class MemoryClassLoader extends ClassLoader {
24+
final class MemoryClassLoader extends AbstractMemoryClassLoader {
3025
static {
3126
// Since we only override findClass(), we can call this method to get fine grained locking
3227
// support with no additional work. Our superclass will lock all calls to findClass with a per
3328
// class to load lock, so we will never see concurrent loads of a single class.
3429
// See http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html
3530
ClassLoader.registerAsParallelCapable();
3631
}
37-
38-
static final class Builder {
39-
private final Map<String, ClassData> generatedClasses = new LinkedHashMap<>();
40-
41-
Builder addAll(Iterable<ClassData> classes) {
42-
for (ClassData item : classes) {
43-
add(item);
44-
}
45-
return this;
46-
}
47-
48-
Builder add(ClassData classData) {
49-
ClassData prev = generatedClasses.put(classData.type().className(), classData);
50-
if (prev != null) {
51-
throw new IllegalStateException("multiple classes generated named: " + classData.type());
52-
}
53-
return this;
54-
}
55-
56-
MemoryClassLoader build() {
57-
return new MemoryClassLoader(generatedClasses);
58-
}
59-
}
6032

6133
/**
6234
* We store all the classes in this map and rely on normal classloading resolution.
@@ -65,36 +37,16 @@ MemoryClassLoader build() {
6537
*/
6638
private final ImmutableMap<String, ClassData> classesByName;
6739

68-
private MemoryClassLoader(Map<String, ClassData> generatedClasses) {
69-
super(ClassLoader.getSystemClassLoader());
70-
this.classesByName = ImmutableMap.copyOf(generatedClasses);
71-
}
72-
73-
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
74-
// replace so we don't hang onto the bytes for no reason
75-
ClassData classDef = classesByName.get(name);
76-
if (classDef == null) {
77-
throw new ClassNotFoundException(name);
78-
}
79-
try {
80-
return super.defineClass(name, classDef.data(), 0, classDef.data().length);
81-
} catch (Throwable t) {
82-
// Attach additional information in a suppressed exception to make debugging easier.
83-
t.addSuppressed(new RuntimeException("Failed to load generated class:\n" + classDef));
84-
Throwables.propagateIfInstanceOf(t, ClassNotFoundException.class);
85-
throw Throwables.propagate(t);
40+
MemoryClassLoader(Iterable<ClassData> classes) {
41+
ImmutableMap.Builder<String, ClassData> builder = ImmutableMap.builder();
42+
for (ClassData classData : classes) {
43+
builder.put(classData.type().className(), classData);
8644
}
45+
this.classesByName = builder.build();
8746
}
88-
89-
@Override protected URL findResource(final String name) {
90-
if (!name.endsWith(".class")) {
91-
return null;
92-
}
93-
String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
94-
ClassData classDef = classesByName.get(className);
95-
if (classDef == null) {
96-
return null;
97-
}
98-
return classDef.asUrl();
47+
48+
@Override
49+
ClassData getClassData(String name) {
50+
return classesByName.get(name);
9951
}
10052
}

java/tests/com/google/template/soy/jbcsrc/ExpressionTester.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.common.base.Joiner;
2222
import com.google.common.base.Objects;
23+
import com.google.common.collect.ImmutableList;
2324
import com.google.common.truth.FailureStrategy;
2425
import com.google.common.truth.Subject;
2526
import com.google.common.truth.SubjectFactory;
@@ -232,7 +233,7 @@ static <T> T createInvoker(Class<T> clazz, Expression expr) {
232233
}
233234

234235
private static <T> T load(Class<T> clazz, ClassData data) {
235-
MemoryClassLoader loader = new MemoryClassLoader.Builder().add(data).build();
236+
MemoryClassLoader loader = new MemoryClassLoader(ImmutableList.of(data));
236237
Class<?> generatedClass;
237238
try {
238239
generatedClass = loader.loadClass(data.type().className());

java/tests/com/google/template/soy/jbcsrc/TemplateTester.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,9 @@ private void compile() {
297297
compilerRegistry,
298298
compilerRegistry.getTemplateInfoByTemplateName(templateName)).compile();
299299
checkClasses(classData);
300-
factory = new CompiledTemplates(
301-
compilerRegistry.getTemplateNames(),
302-
new MemoryClassLoader.Builder().addAll(classData).build())
300+
factory =
301+
new CompiledTemplates(
302+
compilerRegistry.getTemplateNames(), new MemoryClassLoader(classData))
303303
.getTemplateFactory(templateName);
304304
}
305305
}

0 commit comments

Comments
 (0)