GP-516 Added the "Variadic Function Signature Override" analyzer

This commit is contained in:
ghidra3
2020-11-19 08:36:08 -05:00
committed by ghidra1
parent fb5ab7569d
commit 28e0480f9d
7 changed files with 2012 additions and 0 deletions
@@ -450,6 +450,18 @@
<P><U>Started By:</U> Importing or adding to a program, Auto Analyze command</P>
</BLOCKQUOTE>
<H3><A name="Format_String_Analyzer"></A>Format String Analyzer</H3>
<BLOCKQUOTE>
<P>This analyzer detects variadic function calls in the bodies of each function that intersect
the current selection. It then parses their format string arguments to infer the correct function
call signatures. Currently, this analyzer only supports printf, scanf, and their variants (e.g., snprintf, fscanf).
If the current selection is emtpy, it searches through every function within the binary. Once
the signatures are inferred, they are overridden.</P>
<P><U>Started By:</U> Importing or adding to a program, Auto Analyze command</P>
</BLOCKQUOTE>
<H3><A name="Image"></A>Image Analyzer</H3>
@@ -0,0 +1,63 @@
/* ###
* 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.app.plugin.core.string.variadic;
/**
* This class represents a single argument of a variadic function
*/
public class FormatArgument {
private String lengthModifier;
private String conversionSpecifier;
/**
* Constructor for a FormatArg
*
* @param lengthModifier length modifier of a format argument
* @param conversionSpec conversion specifier of a format argument
*/
public FormatArgument(String lengthModifier, String conversionSpec) {
this.lengthModifier = lengthModifier;
this.conversionSpecifier = conversionSpec;
}
/**
* lenghtModifier getter
*
* @return lengthModifier
*/
public String getLengthModifier() {
return this.lengthModifier;
}
/**
* convertionSpec getter
*
* @return conversionSpecifier
*/
public String getConversionSpecifier() {
return this.conversionSpecifier;
}
/**
* Converts FormatArg to String
*
* @return FormatArgument as String
*/
public String toString() {
return String.format("[%s, %s]", this.lengthModifier, this.conversionSpecifier);
}
}
@@ -0,0 +1,392 @@
/* ###
* 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.app.plugin.core.string.variadic;
import java.util.*;
import org.apache.commons.collections4.IteratorUtils;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.parallel.*;
import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.program.util.DefinedDataIterator;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class FormatStringAnalyzer extends AbstractAnalyzer {
// Array of substrings of variadic function names that are searched for
private static final String[] VARIADIC_SUBSTRINGS = { "printf", "scanf" };
private static final String NAME = "Variadic Function Signature Override";
private static final String DESCRIPTION =
"Detects variadic function calls in the bodies of each function that intersect the" +
"current selection and parses their format string arguments to infer the correct " +
"signatures. Currently, this analyzer only supports printf, scanf, and thier variants " +
"(e.g., snprintf, fscanf). If the current selection is empty, it searches through " +
"every function. Once the correct signatures are inferred, they are overridden.";
private final static boolean OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED = false;
private final static String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks";
private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS =
"Select this check box if you want this analyzer to create analysis bookmarks " +
"when items of interest are created/identified by the analyzer.";
private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED;
// Any function name containing this substring is determined to be an input type function
private static final String INPUT_FUNCTION_SUBSTRING = "scanf";
private Program currentProgram = null;
private FormatStringParser parser;
public FormatStringAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_SIGNATURES_ANALYZER);
setSupportsOneTimeAnalysis();
setPriority(AnalysisPriority.LOW_PRIORITY);
setDefaultEnablement(false);
setPrototype();
}
@Override
public boolean canAnalyze(Program program) {
return true;
}
private synchronized FormatStringParser getParser() {
if (parser == null) {
parser = new FormatStringParser(currentProgram);
}
return parser;
}
private synchronized void disposeParser() {
parser = null;
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
this.currentProgram = program;
try {
run(set, monitor);
}
catch (CancelledException e) {
// User cancelled analysis
}
finally {
disposeParser();
}
return true;
}
private void run(AddressSetView selection, TaskMonitor monitor)
throws CancelledException {
DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(currentProgram);
Map<Address, Data> stringsByAddress = new HashMap<>();
for (Data data : dataIterator) {
String s = data.getDefaultValueRepresentation();
if (s.contains("%")) {
stringsByAddress.put(data.getAddress(), data);
}
monitor.checkCanceled();
}
FunctionIterator functionIterator = currentProgram.getListing().getFunctions(true);
FunctionIterator externalIterator = currentProgram.getListing().getExternalFunctions();
Iterator<Function> programFunctionIterator = IteratorUtils.chainedIterator(functionIterator,externalIterator);
Map<String, List<DataType>> namesToParameters = new HashMap<>();
Map<String, DataType> namesToReturn = new HashMap<>();
Set<Function> toDecompile = new HashSet<>();
Set<String> variadicFunctionNames = new HashSet<>();
// Find variadic function names and their parameter data types
for (Function function : IteratorUtils.asIterable(programFunctionIterator)) {
String name = function.getName().strip();
if (usesVariadicFormatString(function)) {
for (String variadicSubstring : VARIADIC_SUBSTRINGS) {
if (name.contains(variadicSubstring)) {
variadicFunctionNames.add(name);
namesToParameters.put(name, getParameters(function));
namesToReturn.put(name, function.getReturnType());
break;
}
}
}
monitor.checkCanceled();
}
Iterator<Function> functionsToSearchIterator = selection != null
? currentProgram.getFunctionManager()
.getFunctionsOverlapping(selection)
: currentProgram.getFunctionManager().getFunctionsNoStubs(true);
// Find functions that call variadic functions
while (functionsToSearchIterator.hasNext()) {
Function function = functionsToSearchIterator.next();
Set<Function> calledFunctions = function.getCalledFunctions(monitor);
for (Function calledFunction : calledFunctions) {
// If this function calls a variadic function, add it to functions to decompile
if (namesToParameters.containsKey(calledFunction.getName())) {
toDecompile.add(function);
break;
}
}
monitor.checkCanceled();
}
decompile(currentProgram, monitor, stringsByAddress, variadicFunctionNames,
namesToParameters,
namesToReturn,
toDecompile);
}
private void decompile(Program program, TaskMonitor monitor,
Map<Address, Data> stringsByAddress,
Set<String> variadicFunctionNames,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn,
Set<Function> toDecompile) {
DecompilerCallback<Void> callback = initDecompilerCallback(program, stringsByAddress,
variadicFunctionNames, namesToParameters, namesToReturn);
if (toDecompile.isEmpty()) {
Msg.info(this, "No functions detected that make variadic function calls with " +
"format strings containing format specifiers");
return;
}
try {
ParallelDecompiler.decompileFunctions(callback, toDecompile, monitor);
}
catch (Exception e) {
Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e);
}
finally {
callback.dispose();
}
}
private DecompilerCallback<Void> initDecompilerCallback(Program program,
Map<Address, Data> stringsByAddress,
Set<String> variadicFuncNames, Map<String, List<DataType>> namesToParameters,
Map<String, DataType> namesToReturn) {
return new DecompilerCallback<>(program,
new VariadicSignatureDecompileConfigurer()) {
@Override
public Void process(DecompileResults results, TaskMonitor tMonitor) throws Exception {
if (results == null) {
return null;
}
Function function = results.getFunction();
PcodeFunctionParser pcodeParser = new PcodeFunctionParser(program);
if (results.getHighFunction() == null ||
results.getHighFunction().getPcodeOps() == null) {
return null;
}
Iterator<PcodeOpAST> pcodeOpASTIterator = results.getHighFunction().getPcodeOps();
List<PcodeOpAST> pcodeOpASTs = new ArrayList<>();
if ((results.getHighFunction() != null) && pcodeOpASTIterator != null) {
while (pcodeOpASTIterator.hasNext()) {
PcodeOpAST pcodeAST = pcodeOpASTIterator.next();
pcodeOpASTs.add(pcodeAST);
}
}
List<FunctionCallData> functionCallDataList = pcodeParser.parseFunctionForCallData(
pcodeOpASTs, stringsByAddress, variadicFuncNames);
if (functionCallDataList != null && functionCallDataList.size() > 0) {
overrideCallList(program, function, functionCallDataList, namesToParameters,
namesToReturn);
}
tMonitor.checkCanceled();
return null;
}
};
}
private List<DataType> getParameters(Function function) {
// NOTE: Currently only considers variadic functions with format string
// arguments.
List<DataType> dataTypes = new ArrayList<>();
for (ParameterDefinition pd : function.getSignature().getArguments()) {
dataTypes.add(pd.getDataType());
}
return dataTypes;
}
private boolean usesVariadicFormatString(Function function) {
int paramCount = function.getParameterCount();
return function.hasVarArgs() && paramCount > 0 &&
isCharPointer(function.getParameters()[paramCount - 1].getDataType());
}
private boolean isCharPointer(DataType dataType) {
if (dataType instanceof TypeDef) {
dataType = ((TypeDef) dataType).getBaseDataType();
}
if (!(dataType instanceof Pointer)) {
return false;
}
DataType dt = ((Pointer) dataType).getDataType();
return dt instanceof CharDataType || dt instanceof WideCharDataType ||
dt instanceof WideChar16DataType || dt instanceof WideChar32DataType;
}
private class VariadicSignatureDecompileConfigurer implements DecompileConfigurer {
// DecompInterface allows for control of decompilation processes
@Override
public void configure(DecompInterface decompiler) {
decompiler.toggleCCode(true); // Produce C code
decompiler.toggleSyntaxTree(true); // Produce syntax tree
decompiler.openProgram(currentProgram);
decompiler.setSimplificationStyle("normalize");
DecompileOptions options = new DecompileOptions();
options.grabFromProgram(currentProgram);
decompiler.setOptions(options);
}
}
private ParameterDefinition[] parseParameters(Function function,
Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters) {
Program functionProgram = function.getProgram();
FormatStringParser parser = getParser();
// DataTypes of arguments are treated differently when the variadic function
// looks like scanf since it takes in inputs. We need this information
// so that the correct DataType arguments are generated
boolean isOutputType = !callFunctionName.contains(INPUT_FUNCTION_SUBSTRING);
List<FormatArgument> formatArguments =
parser.convertToFormatArgumentList(formatString, isOutputType);
DataType[] dataTypes = isOutputType ? parser.convertToOutputDataTypes(formatArguments)
: parser.convertToInputDataTypes(formatArguments);
if (dataTypes == null) {
currentProgram.getBookmarkManager()
.setBookmark(address, BookmarkType.ANALYSIS, "Unrecognized format string",
"Format string could not be parsed: " + formatString);
return null;
}
ParameterDefinition[] paramDefs =
createParameters(callFunctionName, dataTypes, functionProgram, namesToParameters);
return paramDefs;
}
private ParameterDefinition[] createParameters(String callFunctionName, DataType[] dataTypes,
Program program, Map<String, List<DataType>> namesToParameters) {
List<DataType> initialFunctionParameters = namesToParameters.get(callFunctionName);
int numberOfParameters = initialFunctionParameters.size() + dataTypes.length;
if (numberOfParameters == 0) {
return null; // Invalid function
}
ParameterDefinition[] parameterDefinitions = new ParameterDefinition[numberOfParameters];
for (int i = 0; i < numberOfParameters; i++) {
if (i < initialFunctionParameters.size()) {
parameterDefinitions[i] =
new ParameterDefinitionImpl("param" + i, initialFunctionParameters.get(i), "");
}
else {
parameterDefinitions[i] = new ParameterDefinitionImpl("param" + i,
dataTypes[i - initialFunctionParameters.size()], "");
}
}
return parameterDefinitions;
}
private FunctionSignature initSignature(Function function, Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn) {
ParameterDefinition[] parameterDefinitions =
parseParameters(function, address, callFunctionName, formatString, namesToParameters);
if (parameterDefinitions == null || parameterDefinitions.length == 0) {
return null;
}
FunctionDefinitionDataType signature = new FunctionDefinitionDataType(callFunctionName);
signature.setArguments(parameterDefinitions);
signature.setReturnType(namesToReturn.get(callFunctionName));
return signature;
}
private void overrideCallList(Program program, Function function,
List<FunctionCallData> functionCallDataList,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn) {
if (function == null || functionCallDataList == null) {
return;
}
for (FunctionCallData data : functionCallDataList) {
overrideFunctionCall(program, function, data.getAddressOfCall(), data.getCallFuncName(),
data.getFormatString(), namesToParameters, namesToReturn);
}
}
private void overrideFunctionCall(Program program, Function function, Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters,
Map<String, DataType> namesToReturn) {
if (formatString == null) {
return;
}
FunctionSignature functionSignature = initSignature(function, address, callFunctionName,
formatString, namesToParameters, namesToReturn);
if (functionSignature == null || function == null || address == null) {
return;
}
try {
if (createBookmarksEnabled) {
BookmarkManager bookmark = program.getBookmarkManager();
bookmark.setBookmark(address, BookmarkType.ANALYSIS,
"Function Signature Override",
"Override for call to function " + callFunctionName);
}
HighFunctionDBUtil.writeOverride(function, address, functionSignature);
}
catch (InvalidInputException e) {
Msg.error(this, "Error: invalid input given to writeOverride()", e);
}
}
@Override
public boolean removed(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
return false;
}
@Override
public void registerOptions(Options options, Program program) {
options.registerOption(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled, null,
OPTION_DESCRIPTION_CREATE_BOOKMARKS);
}
@Override
public void optionsChanged(Options options, Program program) {
createBookmarksEnabled =
options.getBoolean(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled);
}
}
@@ -0,0 +1,68 @@
/* ###
* 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.app.plugin.core.string.variadic;
import ghidra.program.model.address.*;
/**
* Class for encapsulating a variadic function call
*/
public class FunctionCallData {
private Address addressOfCall;
private String callFunctionName;
private String formatString;
/**
* Constructore for FuncCallData
*
* @param addressOfCall Address of function call
* @param callFunctionName variadic function name
* @param formatString format String
*/
public FunctionCallData(Address addressOfCall, String callFunctionName, String formatString) {
this.addressOfCall = addressOfCall;
this.callFunctionName = callFunctionName;
this.formatString = formatString;
}
/**
* addressOfCall getter
*
* @return addressOfCall
*/
public Address getAddressOfCall() {
return this.addressOfCall;
}
/**
* callFunctionName getter
*
* @return callFunctionName
*/
public String getCallFuncName() {
return this.callFunctionName;
}
/**
* formatString getter
*
* @return formatString
*/
public String getFormatString() {
return this.formatString;
}
}
@@ -0,0 +1,184 @@
/* ###
* 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.app.plugin.core.string.variadic;
import java.util.*;
import ghidra.docking.settings.SettingsImpl;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBufferImpl;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.program.model.pcode.Varnode;
/**
* Class for parsing functions' Pcode representations and finding variadic
* functions being called
*
*/
public class PcodeFunctionParser {
// All values within the range [32, 126] are ascii readable
private static final int READABLE_ASCII_LOWER_BOUND = 32;
private static final int READABLE_ASCII_UPPER_BOUND = 126;
// How many bytes to read from a memory address when initial format
// String cannot be found. This normally only happens for short format
// Strings with lengths less than 5
private static final int BUFFER_LENGTH = 20;
private static final String CALL_INSTRUCTION = "CALL";
private Program program;
public PcodeFunctionParser(Program program) {
this.program = program;
}
/**
* Takes pcode ops of a function and parses them to determine whether there are
* any calls to variadic functions that use format Strings.
*
* @param pcodeOps List of PcodeOpAST for a function
* @param addressToCandidateData map of Addresses to format String data
* @param variadicFunctionNames Set of variadic functions to look for
* @return List of variadic functions that the current function calls
*/
public List<FunctionCallData> parseFunctionForCallData(List<PcodeOpAST> pcodeOps,
Map<Address, Data> addressToCandidateData, Set<String> variadicFunctionNames) {
if (pcodeOps == null || addressToCandidateData == null || variadicFunctionNames == null ||
this.program == null) {
return null;
}
List<FunctionCallData> functionCallDataList = new ArrayList<>();
for (PcodeOpAST ast : pcodeOps) {
Varnode firstNode = ast.getInput(0);
if (firstNode == null) {
continue;
}
if (ast.getMnemonic().contentEquals(CALL_INSTRUCTION)) {
FunctionManager functionManager = this.program.getFunctionManager();
Function function = functionManager.getFunctionAt(firstNode.getAddress());
if (function == null) {
return null;
}
String functionName = function.getName();
if (variadicFunctionNames.contains(functionName)) {
Varnode[] inputs = ast.getInputs();
if (inputs.length > 0) {
boolean hasDefinedFormatString = searchForVariadicCallData(ast,
addressToCandidateData, functionCallDataList, functionName);
if (!hasDefinedFormatString) {
searchForHiddenFormatStrings(ast, functionCallDataList, functionName);
}
}
}
}
}
return functionCallDataList;
}
private boolean searchForVariadicCallData(PcodeOpAST ast,
Map<Address, Data> addressToCandidateData, List<FunctionCallData> functionCallDataList,
String functionName) {
boolean hasDefinedFormatString = false;
Varnode[] inputs = ast.getInputs();
for (int i = 1; i < inputs.length; i++) {
Varnode v = inputs[i];
Data data = null;
Address ramSpaceAddress = convertAddressToRamSpace(v.getAddress());
if (addressToCandidateData.containsKey(ramSpaceAddress)) {
data = addressToCandidateData.get(ramSpaceAddress);
functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(),
functionName, data.getDefaultValueRepresentation()));
hasDefinedFormatString = true;
}
}
return hasDefinedFormatString;
}
// If addrToCandidateData doesn't have format String data for this call
// and we are calling a variadic function, parse the String to determine
// whether it's a format String.
private void searchForHiddenFormatStrings(PcodeOpAST ast,
List<FunctionCallData> functionCallDataList, String functionName) {
Varnode[] inputs = ast.getInputs();
// Initialize i = 1 to skip first input
for (int i = 1; i < inputs.length; ++i) {
Varnode v = inputs[i];
String formatStringCandidate = findFormatString(v.getAddress());
if (formatStringCandidate == null) {
continue;
}
if (formatStringCandidate.contains("%")) {
functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(),
functionName, formatStringCandidate));
}
break;
}
}
private Address convertAddressToRamSpace(Address address) {
String addressString = address.toString(false);
return this.program.getAddressFactory().getAddress(addressString);
}
/**
* Looks at bytes at given address and converts to format String
*
* @param address Address of format String
* @return format String
*/
private String findFormatString(Address address) {
if (!address.getAddressSpace().isConstantSpace()) {
return null;
}
// Old address associated with constant space which doesn't work
Address ramSpaceAddress = convertAddressToRamSpace(address);
MemoryBufferImpl memoryBuffer =
new MemoryBufferImpl(this.program.getMemory(), ramSpaceAddress);
SettingsImpl settings = new SettingsImpl();
StringDataInstance stringDataInstance = StringDataInstance
.getStringDataInstance(new StringDataType(), memoryBuffer, settings, BUFFER_LENGTH);
String stringValue = stringDataInstance.getStringValue();
if (stringValue == null) {
return null;
}
String formatStringCandidate = "";
for (int i = 0; i < stringValue.length(); i++) {
if (!isAsciiReadable(stringValue.charAt(i))) {
break;
}
formatStringCandidate += stringValue.charAt(i);
}
return formatStringCandidate;
}
private boolean isAsciiReadable(char c) {
return c >= READABLE_ASCII_LOWER_BOUND && c <= READABLE_ASCII_UPPER_BOUND;
}
}
@@ -0,0 +1,332 @@
/* ###
* 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.app.plugin.core.string.variadic;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.*;
public class FormatStringParserTest extends AbstractGenericTest {
private ProgramBuilder builder;
private ProgramDB program;
@Before
public void setUp() throws Exception {
builder = new ProgramBuilder("FormatStringParserTest", ProgramBuilder._TOY, this);
assertNotNull(builder);
program = builder.getProgram();
assertNotNull(program);
}
// Determines whether null is properly returned for
// invalid format Strings. Each String is invalid due to
// either (1) invalid conversion specifier, (2) invalid
// length modifier, or (3) placeholder incorrectly used
@Test
public void testInvalidFormatString() {
runFormatTest("%r", null, true); // r is not a conversion specifier
runFormatTest("%%%lw", null, true); // w is not a conversion specifier
runFormatTest("%#0*.*ld", null, false); // scanf doesn't use flags or period
runFormatTest("%d::%%%ld%z", null, true); // z is not a conversion specifier
runFormatTest("thisisatest%%%#**u", null, true); // two consecutive astericks
runFormatTest("%#0'*rd", null, true); // r is not length modifier
runFormatTest("%%%#'*md", null, true); // m is not length modifier
runFormatTest("%*.**d", null, true); // two consecutive astericks
runFormatTest("%lD", null, true); // D is not a conversion specifier
runFormatTest("%-0+**d", null, false); // scanf doesn't use flags, two consecutive astericks
runFormatTest("%-0+*.*d", null, false); // scanf doesn't use flags or period
runFormatTest("%2.3d", null, false); // scanf doesn't use period
runFormatTest("%*1$d %d\n", null, true); // If one placeholder specifies parameter, the others must too
runFormatTest("%2$d %d\n", null, true); // If one placeholder specifies parameter, the others must too
}
// Tests format strings for scanf which have expected types of pointers instead
// of standard format strings
@Test
public void testScanfFormatString() {
DataType[] expectedTypes1 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()) };
runFormatTest("%d", expectedTypes1, false);
DataType[] expectedTypes2 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()),
program.getDataTypeManager().getPointer(new ShortDataType()) };
runFormatTest("%d%hi", expectedTypes2, false);
DataType[] expectedTypes3 =
{ program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%p%*d%s", expectedTypes3, false);
DataType[] expectedTypes4 =
{ program.getDataTypeManager().getPointer(new LongDoubleDataType()),
program.getDataTypeManager().getPointer(new CharDataType()),
program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)) };
runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false);
}
// Tests format strings that are more complex, containing less commonly
// used format patterns and more '%' characters
@Test
public void testComplexFormatString() {
DataType[] expectedTypes1 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()), };
runFormatTest("#12%n\nd2", expectedTypes1, true);
DataType[] expectedTypes2 =
{ program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() };
runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true);
DataType[] expectedTypes3 =
{ new PointerDataType(DataType.VOID), new LongDoubleDataType(),
new UnsignedCharDataType() };
runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true);
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
new UnsignedCharDataType(), new IntegerDataType(), new LongDoubleDataType() };
runFormatTest("%0#+-*.*hhX%%%.*La", expectedTypes4, true);
DataType[] expectedTypes5 = { new IntegerDataType(),
program.getDataTypeManager().getPointer(new IntegerDataType()), new IntegerDataType(),
program.getDataTypeManager().getPointer(new WideCharDataType()), new IntegerDataType(),
new LongDoubleDataType() };
runFormatTest("%.*n%*C%%%%%.*LE", expectedTypes5, true);
}
// Tests format strings that use astericks to add another int
// argument to determine field width or precision
@Test
public void testAsterickFormatString() {
DataType[] expectedTypes1 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%*d", expectedTypes1, true);
DataType[] expectedTypes2 = { new IntegerDataType(), new LongDataType() };
runFormatTest("%.*ld", expectedTypes2, true);
DataType[] expectedTypes3 =
{ new IntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("%*.*d", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new IntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("*%%%+-*.*d", expectedTypes4, true);
}
// Test simple format strings with different length modifiers
@Test
public void testLengthModifierFormatString() {
DataType[] expectedTypes1 =
{ new LongDataType(), new PointerDataType(LongDataType.dataType) };
runFormatTest("%ld %ln", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new ShortDataType(), new CharDataType(), new PointerDataType(ShortDataType.dataType),
new PointerDataType(CharDataType.dataType) };
runFormatTest("%hd %hhi %hn %hhn", expectedTypes2, true);
DataType[] expectedTypes3 = { new UnsignedShortDataType(), new UnsignedCharDataType() };
runFormatTest("%hx %hhu", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new UnsignedLongDataType(), new LongLongDataType(), new UnsignedLongLongDataType(),
new PointerDataType(LongLongDataType.dataType) };
runFormatTest("%lX %lld %llx %lln", expectedTypes4, true);
DataType[] expectedTypes5 =
{ new LongDoubleDataType(), new LongLongDataType(), new UnsignedLongLongDataType(),
new UnsignedShortDataType(), new UnsignedCharDataType() };
runFormatTest("%LE %lli %llX %hu %hhX", expectedTypes5, true);
}
// Test simple format strings with different special length modifiers
// using generated default typedefs
@Test
public void testSpecialLengthModifierFormatStringDefault() {
DataType[] expectedTypes1 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%zd", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%zu", expectedTypes2, true);
DataType[] expectedTypes3 = { new TypedefDataType("ptrdiff_t", LongDataType.dataType) };
runFormatTest("%td", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%tu", expectedTypes4, true);
DataType[] expectedTypes5 = { new TypedefDataType("intmax_t", LongLongDataType.dataType) };
runFormatTest("%jd", expectedTypes5, true);
DataType[] expectedTypes6 =
{ new TypedefDataType("uintmax_t", UnsignedLongLongDataType.dataType) };
runFormatTest("%ju", expectedTypes6, true);
DataType[] expectedTypes7 =
{ new PointerDataType(new TypedefDataType("intmax_t", LongLongDataType.dataType)) };
runFormatTest("%jn", expectedTypes7, true);
}
// Test simple format strings with different special length modifiers
// using predefined typedefs
@Test
public void testSpecialLengthModifierFormatStringPredefined() {
int txId = program.startTransaction("Add TypeDefs");
try {
ProgramDataTypeManager dtm = program.getDataTypeManager();
DataType sizetDt =
dtm.resolve(new TypedefDataType("size_t", UnsignedLongLongDataType.dataType), null);
DataType ptrdiftDt =
dtm.resolve(new TypedefDataType("ptrdiff_t", LongLongDataType.dataType), null);
DataType intmaxtDt =
dtm.resolve(new TypedefDataType("intmax_t", LongDataType.dataType), null);
DataType uintmaxtDt =
dtm.resolve(new TypedefDataType("uintmax_t", UnsignedLongDataType.dataType), null);
DataType[] expectedTypes1 = { sizetDt };
runFormatTest("%zd", expectedTypes1, true);
DataType[] expectedTypes2 = { sizetDt };
runFormatTest("%zu", expectedTypes2, true);
DataType[] expectedTypes3 = { ptrdiftDt };
runFormatTest("%td", expectedTypes3, true);
DataType[] expectedTypes4 = { sizetDt };
runFormatTest("%tu", expectedTypes4, true);
DataType[] expectedTypes5 = { intmaxtDt };
runFormatTest("%jd", expectedTypes5, true);
DataType[] expectedTypes6 = { uintmaxtDt };
runFormatTest("%ju", expectedTypes6, true);
DataType[] expectedTypes7 = { new PointerDataType(intmaxtDt) };
runFormatTest("%jn", expectedTypes7, true);
}
finally {
program.endTransaction(txId, true);
}
}
// Test simple format Strings with different conversion specifiers
@Test
public void testConversionSpecFormatString() {
DataType[] expectedTypes1 = { new IntegerDataType() };
runFormatTest("%d", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new IntegerDataType(), new IntegerDataType(), new UnsignedIntegerDataType(),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%i %i %x %s", expectedTypes2, true);
DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType(),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%d %d %s", expectedTypes3, true);
DataType[] expectedTypes4 = { new DoubleDataType(), new DoubleDataType(),
new DoubleDataType(), new DoubleDataType(), new UnsignedCharDataType() };
runFormatTest("%e %f %E %G %c", expectedTypes4, true);
DataType[] expectedTypes5 = { new UnsignedIntegerDataType(), new UnsignedIntegerDataType(),
new UnsignedIntegerDataType(), new DoubleDataType(), new DoubleDataType() };
runFormatTest("%u %x %X %e %g", expectedTypes5, true);
DataType[] expectedTypes6 = { new IntegerDataType() };
runFormatTest("%.d", expectedTypes6, true);
}
// Format Strings with field widths indicated by the sequence "*m$"
// where m is an integer that determines the position in the argument
// list of an integer argument
@Test
public void testFormatParameters() {
DataType[] expectedTypes1 = { new IntegerDataType() };
runFormatTest("%1$d", expectedTypes1, true);
DataType[] expectedTypes2 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$*2$d", expectedTypes2, true);
DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$.*2$d", expectedTypes3, true);
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$d:%2$.*3$d:%4$.*3$d\n", expectedTypes4, true);
DataType[] expectedTypes5 =
{ new UnsignedIntegerDataType(), new UnsignedIntegerDataType() };
runFormatTest("%2$d %2$#x; %1$d %1$#x", expectedTypes5, true);
DataType[] expectedTypes6 =
{ new UnsignedIntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("%2$+#*3$d:%2$#x;0-:'.~%1$0*2$d:!2%1$#x", expectedTypes6, true);
DataType[] expectedTypes7 =
{ new UnsignedLongLongDataType(), new DoubleDataType(), new IntegerDataType() };
runFormatTest("%2$+#*3$f:*;`2!%1$#qu", expectedTypes7, true);
}
private void runFormatTest(String testString, DataType[] expected, boolean runOutputAnalyzer) {
FormatStringParser parser = new FormatStringParser(program);
List<FormatArgument> formatArguments =
parser.convertToFormatArgumentList(testString, runOutputAnalyzer);
DataType[] dataTypes = runOutputAnalyzer ? parser.convertToOutputDataTypes(formatArguments)
: parser.convertToInputDataTypes(formatArguments);
assertEquivalent(dataTypes, expected);
}
private void assertEquivalent(DataType[] actual, DataType[] expected) {
if (expected == null) {
assertNull(actual);
return;
}
assertNotNull("Expected args were not produced", actual);
assertNotNull("Unexpected args were produced", expected);
assertEquals("Expected arg count differs from actual", actual.length, expected.length);
for (int i = 0; i < actual.length; i++) {
assertNotNull("Unexpected null arg returned", actual[i]);
if (!actual[i].isEquivalent(expected[i])) {
fail("Expected: " + expected[i] + ", Actual: " + actual[i]);
}
}
}
}