8217047: Provide a way to inject missing parameter names

Adding a way to provide parameter names that are missing in the classfiles.

Reviewed-by: darcy, jjg
This commit is contained in:
Jan Lahoda 2019-04-11 17:55:18 +02:00
parent ab051f87d4
commit 28dd6d8496
9 changed files with 366 additions and 21 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,6 +29,7 @@ import java.io.IOException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@ -136,6 +137,27 @@ public abstract class JavacTask implements CompilationTask {
*/
public abstract void removeTaskListener(TaskListener taskListener);
/**
* Sets the specified {@link ParameterNameProvider}. It may be used when
* {@link VariableElement#getSimpleName()} is called for a method parameter
* for which an authoritative name is not found. The given
* {@code ParameterNameProvider} may infer a user-friendly name
* for the method parameter.
*
* Setting a new {@code ParameterNameProvider} will clear any previously set
* {@code ParameterNameProvider}, which won't be queried any more.
*
* When no {@code ParameterNameProvider} is set, or when it returns null from
* {@link ParameterNameProvider#getParameterName(javax.lang.model.element.VariableElement)},
* an automatically synthesized name is returned from {@code VariableElement.getSimpleName()}.
*
* @implSpec The default implementation of this method does nothing.
*
* @param provider the provider.
* @since 13
*/
public void setParameterNameProvider(ParameterNameProvider provider) {}
/**
* Returns a type mirror of the tree node determined by the specified path.
* This method has been superceded by methods on

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.source.util;
import javax.lang.model.element.VariableElement;
/**
* A provider for parameter names when the parameter names are not determined from
* a reliable source, like a classfile.
*
* @since 13
*/
public interface ParameterNameProvider {
/**
* Infer a parameter name for the given parameter. The implementations of this method
* should infer parameter names in such a way that the parameter names are distinct
* for any given owning method.
*
* If the implementation of this method returns null, an automatically synthesized name is used.
*
* @param parameter the parameter for which the name should be inferred.
* @return a user-friendly name for the parameter, or null if unknown
*/
public CharSequence getParameterName(VariableElement parameter);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,9 +43,11 @@ import javax.tools.JavaFileObject;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.ParameterNameProvider;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskListener;
import com.sun.tools.doclint.DocLint;
import com.sun.tools.javac.code.MissingInfoHandler;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.model.JavacTypes;
@ -123,6 +125,11 @@ public class BasicJavacTask extends JavacTask {
mtl.remove(taskListener);
}
@Override
public void setParameterNameProvider(ParameterNameProvider handler) {
MissingInfoHandler.instance(context).setDelegate(handler);
}
public Collection<TaskListener> getTaskListeners() {
MultiTaskListener mtl = MultiTaskListener.instance(context);
return mtl.getTaskListeners();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -319,6 +319,11 @@ public class Flags {
*/
public static final long BODY_ONLY_FINALIZE = 1L<<17; //blocks only
/**
* Flag to indicate the given ParamSymbol has a user-friendly name filled.
*/
public static final long NAME_FILLED = 1L<<58; //ParamSymbols only
/** Modifier masks.
*/
public static final int
@ -435,7 +440,8 @@ public class Flags {
DEPRECATED_REMOVAL(Flags.DEPRECATED_REMOVAL),
HAS_RESOURCE(Flags.HAS_RESOURCE),
POTENTIALLY_AMBIGUOUS(Flags.POTENTIALLY_AMBIGUOUS),
ANONCONSTR_BASED(Flags.ANONCONSTR_BASED);
ANONCONSTR_BASED(Flags.ANONCONSTR_BASED),
NAME_FILLED(Flags.NAME_FILLED);
Flag(long flag) {
this.value = flag;

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.code;
import com.sun.source.util.ParameterNameProvider;
import com.sun.tools.javac.code.Symbol.ParamSymbol;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
/**
* A Context class, that can return additional useful information for Symbols, currently
* parameter names. It does so by calling user-supplied {@link ParameterNameProvider}.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class MissingInfoHandler {
protected static final Context.Key<MissingInfoHandler> missingInfoHandlerWrapperKey = new Context.Key<>();
public static MissingInfoHandler instance(Context context) {
MissingInfoHandler instance = context.get(missingInfoHandlerWrapperKey);
if (instance == null)
instance = new MissingInfoHandler(context);
return instance;
}
private final Names names;
private ParameterNameProvider parameterNameProvider;
protected MissingInfoHandler(Context context) {
context.put(missingInfoHandlerWrapperKey, this);
names = Names.instance(context);
}
public Name getParameterName(ParamSymbol parameter) {
if (parameterNameProvider != null) {
CharSequence name = parameterNameProvider.getParameterName(parameter);
if (name != null) {
return names.fromString(name.toString());
}
}
return null;
}
public void setDelegate(ParameterNameProvider delegate) {
this.parameterNameProvider = delegate;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -65,9 +65,12 @@ import com.sun.tools.javac.util.Name;
import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import com.sun.tools.javac.code.MissingInfoHandler;
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.code.Symbol;
import static com.sun.tools.javac.code.Symbol.OperatorSymbol.AccessCode.FIRSTASGOP;
import com.sun.tools.javac.code.Type;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.code.TypeTag.FORALL;
import static com.sun.tools.javac.code.TypeTag.TYPEVAR;
@ -76,6 +79,7 @@ import static com.sun.tools.javac.jvm.ByteCodes.ishll;
import static com.sun.tools.javac.jvm.ByteCodes.lushrl;
import static com.sun.tools.javac.jvm.ByteCodes.lxor;
import static com.sun.tools.javac.jvm.ByteCodes.string_add;
import com.sun.tools.javac.util.Name;
/** Root class for Java symbols. It contains subclasses
* for specific sorts of symbols, such as variables, methods and operators,
@ -1189,6 +1193,16 @@ public abstract class Symbol extends AnnoConstruct implements Element {
}
public static class RootPackageSymbol extends PackageSymbol {
public final MissingInfoHandler missingInfoHandler;
public RootPackageSymbol(Name name, Symbol owner, MissingInfoHandler missingInfoHandler) {
super(name, owner);
this.missingInfoHandler = missingInfoHandler;
}
}
/** A class for class symbols
*/
public static class ClassSymbol extends TypeSymbol implements TypeElement {
@ -1634,6 +1648,32 @@ public abstract class Symbol extends AnnoConstruct implements Element {
}
}
public static class ParamSymbol extends VarSymbol {
public ParamSymbol(long flags, Name name, Type type, Symbol owner) {
super(flags, name, type, owner);
}
@Override
public Name getSimpleName() {
if ((flags_field & NAME_FILLED) == 0) {
flags_field |= NAME_FILLED;
Symbol rootPack = this;
while (rootPack != null && !(rootPack instanceof RootPackageSymbol)) {
rootPack = rootPack.owner;
}
if (rootPack != null) {
Name inferredName =
((RootPackageSymbol) rootPack).missingInfoHandler.getParameterName(this);
if (inferredName != null) {
this.name = inferredName;
}
}
}
return super.getSimpleName();
}
}
/** A class for method symbols.
*/
public static class MethodSymbol extends Symbol implements ExecutableElement {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -42,6 +42,7 @@ import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.RootPackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type.BottomType;
@ -382,7 +383,9 @@ public class Symtab {
messages = JavacMessages.instance(context);
rootPackage = new PackageSymbol(names.empty, null);
MissingInfoHandler missingInfoHandler = MissingInfoHandler.instance(context);
rootPackage = new RootPackageSymbol(names.empty, null, missingInfoHandler);
// create the basic builtin symbols
unnamedModule = new ModuleSymbol(names.empty, null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -2557,14 +2557,12 @@ public class ClassReader {
firstParam += skip;
}
}
List<Name> paramNames = List.nil();
Set<Name> paramNames = new HashSet<>();
ListBuffer<VarSymbol> params = new ListBuffer<>();
int nameIndex = firstParam;
int annotationIndex = 0;
for (Type t: sym.type.getParameterTypes()) {
Name name = parameterName(nameIndex, paramNames);
paramNames = paramNames.prepend(name);
VarSymbol param = new VarSymbol(PARAMETER, name, t, sym);
VarSymbol param = parameter(nameIndex, t, sym, paramNames);
params.append(param);
if (parameterAnnotations != null) {
ParameterAnnotations annotations = parameterAnnotations[annotationIndex];
@ -2589,18 +2587,24 @@ public class ClassReader {
// Returns the name for the parameter at position 'index', either using
// names read from the MethodParameters, or by synthesizing a name that
// is not on the 'exclude' list.
private Name parameterName(int index, List<Name> exclude) {
private VarSymbol parameter(int index, Type t, MethodSymbol owner, Set<Name> exclude) {
long flags = PARAMETER;
Name argName;
if (parameterNameIndices != null && index < parameterNameIndices.length
&& parameterNameIndices[index] != 0) {
return readName(parameterNameIndices[index]);
}
String prefix = "arg";
while (true) {
Name argName = names.fromString(prefix + exclude.size());
if (!exclude.contains(argName))
return argName;
prefix += "$";
argName = readName(parameterNameIndices[index]);
flags |= NAME_FILLED;
} else {
String prefix = "arg";
while (true) {
argName = names.fromString(prefix + exclude.size());
if (!exclude.contains(argName))
break;
prefix += "$";
}
}
exclude.add(argName);
return new ParamSymbol(flags, argName, t, owner);
}
/**

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8217047
* @summary Verify the parameter names can be injected using ParameterNameProvider.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox
* @run main LoadParameterNamesLazily
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import com.sun.source.util.*;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.TestRunner;
import toolbox.ToolBox;
public class LoadParameterNamesLazily extends TestRunner {
public static void main(String... args) throws Exception {
LoadParameterNamesLazily t = new LoadParameterNamesLazily();
t.runTests();
}
private static final String libClass =
"package lib;" +
"/**Lib javadoc.*/" +
"public class Lib {" +
" /**Lib method javadoc.*/" +
" public static void m(int param, int other) {}" +
"}";
private final ToolBox tb = new ToolBox();
LoadParameterNamesLazily() throws IOException {
super(System.err);
}
@Test
public void testLoadTreesLazily() throws IOException {
Path libSrc = Paths.get("lib-src");
tb.writeJavaFiles(libSrc, libClass);
Path libClasses = Paths.get("lib-classes");
Files.createDirectories(libClasses);
new JavacTask(tb)
.outdir(libClasses)
.options()
.files(tb.findJavaFiles(libSrc))
.run(Task.Expect.SUCCESS)
.writeAll();
Path src = Paths.get("src");
tb.writeJavaFiles(src,
"class Use {" +
" lib.Lib lib;" +
"}");
Path classes = Paths.get("classes");
Files.createDirectories(classes);
new JavacTask(tb)
.outdir(classes)
.options("-classpath", libClasses.toString())
.files(tb.findJavaFiles(src))
.callback(task -> {
task.setParameterNameProvider(parameter -> {
ExecutableElement method = (ExecutableElement) parameter.getEnclosingElement();
TypeElement clazz =
(TypeElement) method.getEnclosingElement();
if (clazz.getQualifiedName().contentEquals("lib.Lib")) {
if (method.getParameters().indexOf(parameter) == 0) {
return "testName";
} else {
return null;
}
}
return null;
});
task.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent e) {
if (e.getKind() == TaskEvent.Kind.ANALYZE) {
TypeElement lib = task.getElements().getTypeElement("lib.Lib");
lib.getClass(); //not null
ExecutableElement method =
ElementFilter.methodsIn(lib.getEnclosedElements()).get(0);
Name paramName0 = method.getParameters().get(0).getSimpleName();
if (!paramName0.contentEquals("testName")) {
throw new IllegalStateException("Unexpected parameter name: " +
paramName0);
}
Name paramName1 = method.getParameters().get(1).getSimpleName();
if (!paramName1.contentEquals("arg1")) {
throw new IllegalStateException("Unexpected parameter name: " +
paramName1);
}
}
}
});
})
.run(Task.Expect.SUCCESS)
.writeAll();
}
}