blob: 1783e0ea9d3bede30c3b0478d0fbdaea2c6dfdcc [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.buildjar.javac;
import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin;
import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.CompileStates.CompileState;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
/**
* An extended version of the javac compiler, providing support for composable static analyses via a
* plugin mechanism. BlazeJavaCompiler keeps a list of plugins and calls callback methods in those
* plugins after certain compiler phases. The plugins perform the actual static analyses.
*/
public class BlazeJavaCompiler extends JavaCompiler {
private int skippedFlowEvents = 0;
private int flowEvents = 0;
/** A list of plugins to run at particular points in the compile */
private final List<BlazeJavaCompilerPlugin> plugins = new ArrayList<>();
private BlazeJavaCompiler(Context context, Iterable<BlazeJavaCompilerPlugin> plugins) {
super(context);
BlazeJavacStatistics.Builder statisticsBuilder =
context.get(BlazeJavacStatistics.Builder.class);
// initialize all plugins
for (BlazeJavaCompilerPlugin plugin : plugins) {
plugin.init(context, log, this, statisticsBuilder);
this.plugins.add(plugin);
}
}
/**
* Adds an initialization hook to the Context, such that requests for a JavaCompiler (i.e., a
* lookup for 'compilerKey' of our superclass, JavaCompiler) will actually construct and return
* BlazeJavaCompiler.
*
* <p>This is the preferred way for extending behavior within javac, per the documentation in
* {@link Context}.
*
* <p>Prior to JDK-8038455 additional JavaCompilers were created for annotation processing rounds,
* but we now expect a single compiler instance per compilation. The factory is still seems to be
* necessary to avoid context-ordering issues, but we assert that the factory is only called once,
* and save the output after its call for introspection.
*/
public static void preRegister(
final Context context, final Iterable<BlazeJavaCompilerPlugin> plugins) {
context.put(
compilerKey,
new Context.Factory<JavaCompiler>() {
boolean first = true;
@Override
public JavaCompiler make(Context c) {
if (!first) {
throw new AssertionError("Expected a single creation of BlazeJavaCompiler.");
}
first = false;
return new BlazeJavaCompiler(c, plugins);
}
});
}
@Override
public Env<AttrContext> attribute(Env<AttrContext> env) {
Env<AttrContext> result = super.attribute(env);
// don't run plugins if there were compilation errors
boolean errors = errorCount() > 0;
// Iterate over all plugins, calling their postAttribute methods
for (BlazeJavaCompilerPlugin plugin : plugins) {
if (!errors || plugin.runOnAttributionErrors()) {
plugin.postAttribute(result);
}
}
return result;
}
@Override
protected void flow(Env<AttrContext> env, Queue<Env<AttrContext>> results) {
boolean isDone = compileStates.isDone(env, CompileState.FLOW);
super.flow(env, results);
if (isDone) {
return;
}
// don't run plugins if there were compilation errors
if (errorCount() > 0) {
skippedFlowEvents++;
return;
}
flowEvents++;
// Iterate over all plugins, calling their postFlow methods
for (BlazeJavaCompilerPlugin plugin : plugins) {
plugin.postFlow(env);
}
}
@Override
public void close() {
for (BlazeJavaCompilerPlugin plugin : plugins) {
plugin.finish();
}
plugins.clear();
super.close();
}
/** The number of flow events that were skipped. */
public int skippedFlowEvents() {
return skippedFlowEvents;
}
/** The number of error-free flow events that were passed to plugins. */
public int flowEvents() {
return flowEvents;
}
/**
* Testing purposes only. Returns true if the collection of plugins in this instance contains one
* of the provided type.
*/
boolean pluginsContain(Class<? extends BlazeJavaCompilerPlugin> klass) {
for (BlazeJavaCompilerPlugin plugin : plugins) {
if (klass.isInstance(plugin)) {
return true;
}
}
return false;
}
}