mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Candidate release of source code.
This commit is contained in:
parent
db81e6b3b0
commit
79d8f164f8
12449 changed files with 2800756 additions and 16 deletions
465
GhidraBuild/BuildFiles/JsonDoclet/src/main/java/JsonDoclet.java
Normal file
465
GhidraBuild/BuildFiles/JsonDoclet/src/main/java/JsonDoclet.java
Normal file
|
@ -0,0 +1,465 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sun.source.doctree.*;
|
||||
import com.sun.source.util.DocTrees;
|
||||
|
||||
import jdk.javadoc.doclet.*;
|
||||
|
||||
/**
|
||||
* Doclet that outputs javadoc in JSON format (instead of HTML). Things like Python can then
|
||||
* read in the JSON and easily access all of the javadoc elements.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class JsonDoclet implements Doclet {
|
||||
|
||||
private final Set<Modifier> ACCESS_LEVEL = Set.of(Modifier.PUBLIC);
|
||||
|
||||
private Reporter log;
|
||||
private File destDir;
|
||||
|
||||
private DocletEnvironment docEnv;
|
||||
private DocTrees docTrees;
|
||||
|
||||
@Override
|
||||
public void init(Locale locale, Reporter reporter) {
|
||||
this.log = reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.RELEASE_11;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<? extends Option> getSupportedOptions() {
|
||||
Option[] options = { new Option() {
|
||||
@Override
|
||||
public int getArgumentCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "the destination directory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Option.Kind.STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getNames() {
|
||||
return Arrays.asList("-d");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameters() {
|
||||
return "directory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(String option, List<String> arguments) {
|
||||
destDir = new File(arguments.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
} };
|
||||
return new HashSet<>(Arrays.asList(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(DocletEnvironment env) {
|
||||
|
||||
this.docEnv = env;
|
||||
this.docTrees = env.getDocTrees();
|
||||
|
||||
// Create destination directory
|
||||
if (destDir == null) {
|
||||
log.print(Kind.ERROR, "Destination directory not set");
|
||||
return false;
|
||||
}
|
||||
if (!destDir.exists()) {
|
||||
if (!destDir.mkdirs()) {
|
||||
log.print(Kind.ERROR, "Failed to create destination directory at: " + destDir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create JSON for all classes
|
||||
//@formatter:off
|
||||
ElementFilter.typesIn(docEnv.getIncludedElements())
|
||||
.stream()
|
||||
.filter(el -> el.getModifiers().containsAll(ACCESS_LEVEL))
|
||||
.filter(el -> el.getKind().equals(ElementKind.CLASS))
|
||||
.forEach(el -> writeJsonToFile(classToJson(el), el.getQualifiedName()));
|
||||
//@formatter:on
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a class {@link TypeElement} to a {@link JSONObject}.
|
||||
*
|
||||
* @param classElement the class {@link TypeElement} to convert
|
||||
* @return A json object that represents the class.
|
||||
*/
|
||||
private JSONObject classToJson(TypeElement classElement) {
|
||||
JSONObject classObj = new JSONObject();
|
||||
processClassAttributes(classElement, classObj);
|
||||
processFieldAndMethodAttributes(classElement, classObj);
|
||||
return classObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the high-level class attributes to the given json object.
|
||||
|
||||
* @param classElement the class element to parse
|
||||
* @param classObj the json object to populate
|
||||
*/
|
||||
private void processClassAttributes(TypeElement classElement, JSONObject classObj) {
|
||||
classObj.put("name", classElement.getSimpleName().toString());
|
||||
classObj.put("comment", getComment(docTrees.getDocCommentTree(classElement)));
|
||||
classObj.put("javadoc", getJavadoc(docTrees.getDocCommentTree(classElement)));
|
||||
classObj.put("static", classElement.getModifiers().contains(Modifier.STATIC));
|
||||
addInterfaces(classElement, classObj);
|
||||
addSuperClass(classElement, classObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link TypeElement} for any declared interfaces and adds them to the
|
||||
* json object.
|
||||
*
|
||||
* @param typeElement the {@link TypeElement} to parse
|
||||
* @param obj the json object to populate
|
||||
*/
|
||||
private void addInterfaces(TypeElement typeElement, JSONObject obj) {
|
||||
JSONArray interfaceArray = new JSONArray();
|
||||
|
||||
//@formatter:off
|
||||
typeElement.getInterfaces()
|
||||
.stream()
|
||||
.filter(DeclaredType.class::isInstance)
|
||||
.map(DeclaredType.class::cast)
|
||||
.map(declaredType -> declaredType.asElement())
|
||||
.filter(TypeElement.class::isInstance)
|
||||
.map(TypeElement.class::cast)
|
||||
.forEach(ifaceTypeElement -> interfaceArray.add(ifaceTypeElement.getQualifiedName().toString()));
|
||||
//@formatter:on
|
||||
|
||||
obj.put("implements", interfaceArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link TypeElement} for any declared <i>extends</i> relationship and
|
||||
* adds it to the json object
|
||||
*
|
||||
* @param typeElement the {@link TypeElement} to parse
|
||||
* @param obj the json object to populate
|
||||
*/
|
||||
private void addSuperClass(TypeElement typeElement, JSONObject obj) {
|
||||
if (typeElement.getSuperclass() instanceof DeclaredType) {
|
||||
DeclaredType declaredType = (DeclaredType) typeElement.getSuperclass();
|
||||
if (declaredType.asElement() instanceof TypeElement) {
|
||||
TypeElement typeEl = (TypeElement) declaredType.asElement();
|
||||
obj.put("extends", typeEl.getQualifiedName().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts javadoc information for all fields and methods in the given class.
|
||||
*
|
||||
* @param classElement the class to parse
|
||||
* @param classObj the json object to populate
|
||||
*/
|
||||
private void processFieldAndMethodAttributes(TypeElement classElement, JSONObject classObj) {
|
||||
|
||||
JSONArray fieldArray = new JSONArray();
|
||||
JSONArray methodArray = new JSONArray();
|
||||
|
||||
for (Element el : classElement.getEnclosedElements()) {
|
||||
if (!el.getModifiers().containsAll(ACCESS_LEVEL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("name", el.getSimpleName().toString());
|
||||
obj.put("comment", getComment(docTrees.getDocCommentTree(el)));
|
||||
obj.put("javadoc", getJavadoc(docTrees.getDocCommentTree(el)));
|
||||
obj.put("static", el.getModifiers().contains(Modifier.STATIC));
|
||||
|
||||
switch (el.getKind()) {
|
||||
case FIELD:
|
||||
VariableElement varElement = (VariableElement) el;
|
||||
obj.put("type_long", getTypeLong(el.asType()));
|
||||
obj.put("type_short", getTypeShort(el.asType()));
|
||||
Object constantValue = varElement.getConstantValue();
|
||||
if (constantValue instanceof String) {
|
||||
constantValue = "\"" + constantValue + "\"";
|
||||
}
|
||||
obj.put("constant_value", Objects.toString(constantValue, null)); // only applies to 'final'
|
||||
fieldArray.add(obj);
|
||||
break;
|
||||
case CONSTRUCTOR:
|
||||
case METHOD:
|
||||
ExecutableElement execElement = (ExecutableElement) el;
|
||||
addParams(execElement, obj);
|
||||
addReturn(execElement, obj);
|
||||
addExceptions(execElement, obj);
|
||||
methodArray.add(obj);
|
||||
break;
|
||||
case ANNOTATION_TYPE:
|
||||
case CLASS:
|
||||
case ENUM:
|
||||
case ENUM_CONSTANT:
|
||||
case EXCEPTION_PARAMETER:
|
||||
case INSTANCE_INIT:
|
||||
case INTERFACE:
|
||||
case LOCAL_VARIABLE:
|
||||
case MODULE:
|
||||
case OTHER:
|
||||
case PACKAGE:
|
||||
case PARAMETER:
|
||||
case RESOURCE_VARIABLE:
|
||||
case STATIC_INIT:
|
||||
case TYPE_PARAMETER:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
classObj.put("fields", fieldArray);
|
||||
classObj.put("methods", methodArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link ExecutableElement} for any associated parameters and adds them to
|
||||
* the json object.
|
||||
*
|
||||
* @param execElement the element to parse
|
||||
* @param obj the json object
|
||||
*/
|
||||
private void addParams(ExecutableElement execElement, JSONObject obj) {
|
||||
|
||||
JSONArray paramsArray = new JSONArray();
|
||||
for (VariableElement varElement : execElement.getParameters()) {
|
||||
JSONObject paramObj = new JSONObject();
|
||||
paramObj.put("name", varElement.getSimpleName().toString());
|
||||
paramObj.put("type_long", getTypeLong(varElement.asType()));
|
||||
paramObj.put("type_short", getTypeShort(varElement.asType()));
|
||||
String comment = "";
|
||||
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
|
||||
if (commentTree != null) {
|
||||
for (DocTree blockTag : commentTree.getBlockTags()) {
|
||||
if (blockTag.getKind().equals(DocTree.Kind.PARAM)) {
|
||||
ParamTree paramTree = (ParamTree) blockTag;
|
||||
if (paramTree.getName().getName().equals(varElement.getSimpleName())) {
|
||||
comment = getComment(blockTag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paramObj.put("comment", comment);
|
||||
paramsArray.add(paramObj);
|
||||
}
|
||||
obj.put("params", paramsArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link ExecutableElement} for any <code>return</code> information
|
||||
* and adds it to the json object.
|
||||
*
|
||||
* @param execElement the element to parse
|
||||
* @param obj the json object
|
||||
*/
|
||||
private void addReturn(ExecutableElement execElement, JSONObject obj) {
|
||||
TypeMirror returnType = execElement.getReturnType();
|
||||
JSONObject returnObj = new JSONObject();
|
||||
returnObj.put("type_long", getTypeLong(returnType));
|
||||
returnObj.put("type_short", getTypeShort(returnType));
|
||||
String comment = "";
|
||||
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
|
||||
if (commentTree != null) {
|
||||
for (DocTree blockTag : commentTree.getBlockTags()) {
|
||||
if (blockTag.getKind().equals(DocTree.Kind.RETURN)) {
|
||||
comment = getComment(blockTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
returnObj.put("comment", comment);
|
||||
obj.put("return", returnObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link ExecutableElement} for thrown exceptions and adds them to
|
||||
* the json object.
|
||||
*
|
||||
* @param execElement the element to parse
|
||||
* @param obj the json object
|
||||
*/
|
||||
private void addExceptions(ExecutableElement execElement, JSONObject obj) {
|
||||
JSONArray throwsArray = new JSONArray();
|
||||
for (TypeMirror thrownType : execElement.getThrownTypes()) {
|
||||
JSONObject throwObj = new JSONObject();
|
||||
String typeLong = getTypeLong(thrownType);
|
||||
String typeShort = getTypeShort(thrownType);
|
||||
throwObj.put("type_long", typeLong);
|
||||
throwObj.put("type_short", typeShort);
|
||||
String comment = "";
|
||||
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
|
||||
if (commentTree != null) {
|
||||
for (DocTree blockTag : commentTree.getBlockTags()) {
|
||||
if (blockTag.getKind().equals(DocTree.Kind.THROWS)) {
|
||||
ThrowsTree throwsTree = (ThrowsTree) blockTag;
|
||||
if (throwsTree.getExceptionName().toString().equals(typeShort)) {
|
||||
comment = getComment(blockTag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throwObj.put("comment", comment);
|
||||
throwsArray.add(throwObj);
|
||||
}
|
||||
obj.put("throws", throwsArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the long type name of the given {@link TypeMirror type}.
|
||||
*
|
||||
* @param type The type to get the long type name of.
|
||||
* @return The long type name of the given {@link TypeMirror type}.
|
||||
*/
|
||||
private String getTypeLong(TypeMirror type) {
|
||||
return type.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the short type name of the given {@link TypeMirror type}.
|
||||
*
|
||||
* @param type The type to get the short type name of.
|
||||
* @return The short type name of the given {@link TypeMirror type}.
|
||||
*/
|
||||
private String getTypeShort(TypeMirror type) {
|
||||
switch (type.getKind()) {
|
||||
case DECLARED:
|
||||
return ((DeclaredType) type).asElement().getSimpleName().toString();
|
||||
default:
|
||||
return type.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment from the given {@link DocTree}.
|
||||
*
|
||||
* @param docTree The {@link DocTree} to get the comment from.
|
||||
* @return The comment from the given {@link DocTree}.
|
||||
*/
|
||||
private String getComment(DocTree docTree) {
|
||||
switch (docTree.getKind()) {
|
||||
case COMMENT:
|
||||
return ((CommentTree) docTree).getBody();
|
||||
case LINK:
|
||||
return ((LinkTree) docTree).getReference().getSignature();
|
||||
case PARAM:
|
||||
return getComment(((ParamTree) docTree).getDescription());
|
||||
case RETURN:
|
||||
return getComment(((ReturnTree) docTree).getDescription());
|
||||
case TEXT:
|
||||
return ((TextTree) docTree).getBody();
|
||||
case THROWS:
|
||||
return getComment(((ThrowsTree) docTree).getDescription());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment from the given {@link List} of {@link DocTree}s. Each list element
|
||||
* represents a line. The final comment is simply all the lines concatenated.
|
||||
*
|
||||
* @param docTreeList The {@link DocTree} {@link List} to get the comment from.
|
||||
* @return The comment from the given {@link DocTree} {@link List}.
|
||||
*/
|
||||
private String getComment(List<? extends DocTree> docTreeList) {
|
||||
return docTreeList.stream().map(e -> getComment(e)).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment from the given {@link DocCommentTree}.
|
||||
*
|
||||
* @param docCommentTree The {@link DocCommentTree} to get the comment from.
|
||||
* @return The comment from the given {DocCommentTree DocTree}.
|
||||
*/
|
||||
private String getComment(DocCommentTree docCommentTree) {
|
||||
if (docCommentTree != null) {
|
||||
return getComment(docCommentTree.getFullBody());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full unprocessed javadoc from the given {@link DocCommentTree}.
|
||||
*
|
||||
* @param docCommentTree The {@link DocCommentTree} to get the javadoc from.
|
||||
* @return the full unprocessed javadoc from the given {@link DocCommentTree}.
|
||||
*/
|
||||
private String getJavadoc(DocCommentTree docCommentTree) {
|
||||
if (docCommentTree != null) {
|
||||
return docCommentTree.toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given json to a filename based on the given qualified class name.
|
||||
*
|
||||
* @param json The json to write.
|
||||
* @param qualifiedName The qualified class name. This name will get converted into a directory
|
||||
* structure.
|
||||
*/
|
||||
private void writeJsonToFile(JSONObject json, Name qualifiedName) {
|
||||
File jsonFile = new File(destDir, qualifiedName.toString().replace('.', '/') + ".json");
|
||||
jsonFile.getParentFile().mkdirs();
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(jsonFile))) {
|
||||
writer.println(json.toJSONString());
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue