mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
fix github 1484 to allow python block comment docstrings
Delegate the definition of block comment start and end patterns to ScriptProvider classes instead of ScriptInfo. Move the functionality to handle Java block comments out of the base `GhidraScriptProvider` class into the `JavaScriptProvider` subclass. Default behavior is now to not support block comments and rely on extensions to implement these themselves. Add a number of tests for Java and Python `ScriptInfo` generation to ensure that scripts with no block comments, single-line block comments, and multi-line block comments are all handled appropriately. Add ScriptInfo tests for detailed Java and Python scripts including multiline descriptions and additional metadata flags.
This commit is contained in:
parent
da94eb86bd
commit
829a837a44
7 changed files with 464 additions and 15 deletions
|
@ -16,6 +16,7 @@
|
|||
package ghidra.python;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
|
@ -23,6 +24,8 @@ import ghidra.app.script.GhidraScriptProvider;
|
|||
|
||||
public class PythonScriptProvider extends GhidraScriptProvider {
|
||||
|
||||
private static final Pattern BLOCK_COMMENT = Pattern.compile("'''");
|
||||
|
||||
@Override
|
||||
public void createNewScript(ResourceFile newScript, String category) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)));
|
||||
|
@ -33,26 +36,31 @@ public class PythonScriptProvider extends GhidraScriptProvider {
|
|||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Pattern that matches block comment openings.
|
||||
* In Python this is a triple single quote sequence, "'''".
|
||||
* @return the Pattern for Python block comment openings
|
||||
*/
|
||||
@Override
|
||||
public Pattern getBlockCommentStart() {
|
||||
return BLOCK_COMMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Pattern that matches block comment closings.
|
||||
* In Python this is a triple single quote sequence, "'''".
|
||||
* @return the Pattern for Python block comment openings
|
||||
*/
|
||||
@Override
|
||||
public Pattern getBlockCommentEnd() {
|
||||
return BLOCK_COMMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentCharacter() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertifyHeaderStart() {
|
||||
return "## ###";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertifyHeaderEnd() {
|
||||
return "##";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertificationBodyPrefix() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Python";
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.python;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.ScriptInfo;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
if (Files.notExists(userScriptDir)) {
|
||||
Files.createDirectories(userScriptDir);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetailedPythonScript() {
|
||||
String descLine1 = "This script exists to check that the info on";
|
||||
String descLine2 = "a script that has extensive documentation is";
|
||||
String descLine3 = "properly parsed and represented.";
|
||||
String author = "Fake Name";
|
||||
String categoryTop = "Test";
|
||||
String categoryBottom = "ScriptInfo";
|
||||
String keybinding = "ctrl shift COMMA";
|
||||
String menupath = "File.Run.Detailed Script";
|
||||
String importPackage = "detailStuff";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + descLine1,
|
||||
"#" + descLine2,
|
||||
"#" + descLine3,
|
||||
"#@author " + author,
|
||||
"#@category " + categoryTop + "." + categoryBottom,
|
||||
"#@keybinding " + keybinding,
|
||||
"#@menupath " + menupath,
|
||||
"#@importpackage " + importPackage,
|
||||
"print('for a blank class, it sure is well documented!')");
|
||||
//@formatter:on
|
||||
} catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
|
||||
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
|
||||
assertEquals(expectedDescription, info.getDescription());
|
||||
|
||||
assertEquals(author, info.getAuthor());
|
||||
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
|
||||
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
|
||||
assertEquals(importPackage, info.getImportPackage());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(2, actualCategory.length);
|
||||
assertEquals(categoryTop, actualCategory[0]);
|
||||
assertEquals(categoryBottom, actualCategory[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonScriptWithBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
} catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonScriptWithoutBlockComment() {
|
||||
String description = "Script without a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
} catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonScriptWithSingleLineBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''This is a test block comment. It will be ignored.'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
} catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException {
|
||||
File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir);
|
||||
tempFile.deleteOnExit();
|
||||
ResourceFile tempResourceFile = new ResourceFile(tempFile);
|
||||
|
||||
PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream());
|
||||
for (String line : lines) {
|
||||
writer.println(line);
|
||||
}
|
||||
writer.close();
|
||||
|
||||
return tempResourceFile;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue