/*
Copyright 2011-2016 Google Inc. 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.security.zynamics.binnavi.disassembly;
import com.google.common.base.Preconditions;
import com.google.security.zynamics.binnavi.CUtilityFunctions;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntDeleteException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntLoadDataException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntSaveDataException;
import com.google.security.zynamics.binnavi.Database.Interfaces.SQLProvider;
import com.google.security.zynamics.binnavi.Gui.GraphWindows.CommentDialogs.Interfaces.IComment;
import com.google.security.zynamics.binnavi.Gui.Users.CUserManager;
import com.google.security.zynamics.zylib.ZyTree.IZyTreeNode;
import com.google.security.zynamics.zylib.disassembly.IAddress;
import com.google.security.zynamics.zylib.disassembly.IOperandTree;
import com.google.security.zynamics.zylib.general.ListenerProvider;
import com.google.security.zynamics.zylib.types.lists.FilledList;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a single instruction in a disassembled program.
*/
public final class CInstruction implements INaviInstruction {
/**
* Address of the instruction. Guaranteed to be non-null.
*/
private final IAddress m_address;
/***
* Mnemonic of the instruction. Guaranteed to be non-null.
*/
private final String m_mnemonic;
/**
* Operands of the instruction. Guaranteed to be non-null but can be empty because not all
* instructions have operands.
*/
private final List<COperandTree> m_operands;
/**
* Module the instruction belongs to.
*/
private final INaviModule m_module;
/**
* Binary data of the instruction.
*/
private final byte[] m_data;
/**
* Architecture identification string of the instruction.
*/
private final String m_architecture;
/**
* SQL provider that is used to update this instruction in the database.
*/
private final SQLProvider m_provider;
/**
* Listeners that are notified when something in the instruction changes.
*/
private final ListenerProvider<IInstructionListener> m_listeners =
new ListenerProvider<IInstructionListener>();
/**
* Used to synchronize the global comment of this instruction.
*/
private final CommentListener m_internalCommentListener = new InternalCommentListener();
private boolean m_saved;
/**
* Creates a new instruction object.
*
* @param module The module the instruction belongs to.
* @param address The address of the instruction.
* @param mnemonic The mnemonic of the instruction.
* @param operands The operands of the instruction.
* @param data Binary data of the instruction.
* @param architecture Architecture identification string of the instruction.
* @param provider The SQL provider that is used to synchronize the instruction with the database.
*/
public CInstruction(final boolean saved, final INaviModule module, final IAddress address,
final String mnemonic, final List<COperandTree> operands, final byte[] data,
final String architecture, final SQLProvider provider) {
m_module = Preconditions.checkNotNull(module, "IE00126: Module argument can not be null");
m_address = Preconditions.checkNotNull(address, "IE00127: Address argument can not be null");
m_mnemonic = Preconditions.checkNotNull(mnemonic, "IE00128: Mnemonic argument can not be null");
Preconditions.checkArgument(!mnemonic.isEmpty(), "IE00129: Mnemonic argument can not be empty");
Preconditions.checkNotNull(operands, "IE00130: Operands argument can not be null");
verifyOperands(operands);
Preconditions.checkNotNull(data, "IE02195: Data argument can not be null");
m_architecture =
Preconditions.checkNotNull(architecture, "IE02196: Architecture argument can not be null");
Preconditions.checkArgument(!architecture.isEmpty(),
"IE02197: Architecture argument can not be empty");
m_provider =
Preconditions.checkNotNull(provider, "IE00132: SQL provider argument can not be null");
m_saved = saved;
m_operands = new ArrayList<COperandTree>(operands);
m_data = data.clone();
CommentManager.get(m_provider).addListener(m_internalCommentListener);
for (final COperandTree operand : operands) {
operand.setNaviInstruction(this);
}
}
/**
* Clones the given operand list.
*
* @param operands The operand list to clone.
*
* @return The cloned operand list.
*/
private static List<COperandTree> clone(final List<COperandTree> operands) {
final List<COperandTree> clonedOperands = new FilledList<COperandTree>();
for (final COperandTree operand : operands) {
clonedOperands.add(operand.cloneTree());
}
return clonedOperands;
}
/**
* Verifies a single node of an operand tree.
*
* @param node The node to verify.
*/
private static void verifyNode(final IZyTreeNode node) {
Preconditions.checkNotNull(node, "IE00133: Operand tree node with value null detected");
for (final IZyTreeNode child : node.getChildren()) {
verifyNode(child);
}
}
/**
* Verifies the validity of the operand trees.
*
* @param operands The operands of the instruction that must be verified.
*/
private static void verifyOperands(final List<COperandTree> operands) {
for (final IOperandTree operandTree : operands) {
verifyNode(operandTree.getRootNode());
}
}
@Override
public void addListener(final IInstructionListener listener) {
m_listeners.addListener(listener);
}
@Override
public List<IComment> appendGlobalComment(final String commentText)
throws CouldntSaveDataException, CouldntLoadDataException {
Preconditions.checkNotNull(commentText, "IE02536: comment argument can not be null");
return CommentManager.get(m_provider).appendGlobalInstructionComment(this, commentText);
}
@Override
public CInstruction cloneInstruction() {
final CInstruction instruction =
new CInstruction(m_saved, m_module, m_address, m_mnemonic, clone(m_operands), m_data,
m_architecture, m_provider);
CommentManager.get(m_provider).initializeGlobalInstructionComment(instruction,
getGlobalComment());
return instruction;
}
@Override
public void close() {
for (final COperandTree operand : m_operands) {
operand.close();
}
CommentManager.get(m_provider).unloadGlobalInstructionComment(this, getGlobalComment());
CommentManager.get(m_provider).removeListener(m_internalCommentListener);
}
@Override
public void deleteGlobalComment(final IComment comment) throws CouldntDeleteException {
Preconditions.checkNotNull(comment, "IE02537: comment argument can not be null");
CommentManager.get(m_provider).deleteGlobalInstructionComment(this, comment);
}
@Override
public IComment editGlobalComment(final IComment comment, final String commentText)
throws CouldntSaveDataException {
Preconditions.checkNotNull(comment, "IE02538: comment argument can not be null");
return CommentManager.get(m_provider).editGlobalInstructionComment(this, comment, commentText);
}
@Override
public IAddress getAddress() {
return m_address;
}
@Override
public String getArchitecture() {
return m_architecture;
}
@Override
public byte[] getData() {
return m_data.clone();
}
@Override
public List<IComment> getGlobalComment() {
return CommentManager.get(m_provider).getGlobalInstructionComment(this);
}
@Override
public String getInstructionString() {
final StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(m_mnemonic);
stringBuffer.append(' ');
for (int i = 0; i < m_operands.size(); ++i) {
stringBuffer.append(m_operands.get(i).toString());
if (i != (m_operands.size() - 1)) {
stringBuffer.append(", ");
}
}
return stringBuffer.toString();
}
@Override
public long getLength() {
return m_data.length;
}
@Override
public String getMnemonic() {
return m_mnemonic;
}
@Override
public Integer getMnemonicCode() {
return m_mnemonic.hashCode();
}
@Override
public INaviModule getModule() {
return m_module;
}
@Override
public int getOperandPosition(final INaviOperandTree operand) {
return m_operands.indexOf(operand);
}
@Override
public List<COperandTree> getOperands() {
return new ArrayList<COperandTree>(m_operands);
}
@Override
public void initializeGlobalComment(final ArrayList<IComment> comment) {
Preconditions.checkNotNull(comment, "IE00134: Instruction comment can not be null");
CommentManager.get(m_provider).initializeGlobalInstructionComment(this, comment);
}
@Override
public boolean inSameDatabase(final IDatabaseObject provider) {
return provider.inSameDatabase(m_provider);
}
@Override
public boolean inSameDatabase(final SQLProvider provider) {
return provider.equals(m_provider);
}
@Override
public boolean isOwner(final IComment comment) {
return CUserManager.get(m_provider).getCurrentActiveUser().equals(comment.getUser());
}
@Override
public boolean isStored() {
return m_saved;
}
@Override
public void removeListener(final IInstructionListener listener) {
m_listeners.removeListener(listener);
}
@Override
public void setSaved(final boolean saved) {
m_saved = saved;
}
@Override
public String toString() {
final StringBuffer stringBuffer = new StringBuffer(m_address.toHexString());
stringBuffer.append(' ');
stringBuffer.append(m_mnemonic);
stringBuffer.append(' ');
for (int i = 0; i < m_operands.size(); ++i) {
stringBuffer.append(m_operands.get(i).toString());
if (i != (m_operands.size() - 1)) {
stringBuffer.append(", ");
}
}
return stringBuffer.toString();
}
/**
* Used to synchronize the global comment of this instruction.
*/
private class InternalCommentListener extends CommentListenerAdapter {
@Override
public void appendedGlobalInstructionComment(final INaviInstruction instruction,
final IComment comment) {
if (instruction.getAddress().equals(getAddress())) {
for (final IInstructionListener listener : m_listeners) {
try {
listener.appendedComment(instruction, comment);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void deletedGlobalInstructionComment(final INaviInstruction instruction,
final IComment comment) {
if (instruction.getAddress().equals(getAddress())) {
for (final IInstructionListener listener : m_listeners) {
try {
listener.deletedComment(instruction, comment);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void editedGlobalInstructionComment(final INaviInstruction instruction,
final IComment comment) {
if (instruction.getAddress().equals(getAddress())) {
for (final IInstructionListener listener : m_listeners) {
try {
listener.editedComment(instruction, comment);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void initializedGlobalInstructionComments(final INaviInstruction instruction,
final List<IComment> comments) {
if (instruction.getAddress().equals(getAddress())) {
for (final IInstructionListener listener : m_listeners) {
try {
listener.initializedComment(instruction, comments);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
}
}