001package eu.cqse.check.util.clang; 002 003import org.conqat.engine.core.core.ConQATException; 004import org.conqat.lib.commons.assertion.CCSMAssert; 005import org.conqat.lib.commons.system.SystemUtils; 006 007import eu.cqse.check.framework.core.CheckException; 008import eu.cqse.check.framework.core.phase.ECodeViewOption; 009import eu.cqse.check.framework.core.phase.ITokenElementContext; 010import eu.cqse.clang.CXCursor; 011import eu.cqse.clang.CXUnsavedFile; 012import eu.cqse.clang.Clang; 013import eu.cqse.clang.ClangJniLoader; 014import eu.cqse.clang.SWIGTYPE_p_CXTranslationUnitImpl; 015import eu.cqse.clang.SWIGTYPE_p_void; 016 017/** 018 * Wrapper for the CXIndex type in Clang. This wrapper implements disposing the 019 * index (freeing the memory) when this Object is garbage collected. 020 * <p> 021 * An "index" that consists of a set of translation units that would typically 022 * be linked together into an executable or library. 023 */ 024public class ClangTranslationUnitWrapper { 025 026 /** 027 * Clang option constant to allow parsing of incomplete files (see 028 * CXTranslationUnit_Incomplete in clang code). 029 */ 030 private static final int PARSE_OPTION_INCOMPLETE = 0x02; 031 032 /** 033 * Clang option constant to continue parsing even in the presence of errors (see 034 * CXTranslationUnit_KeepGoing in clang code). 035 */ 036 private static final int PARSE_OPTION_KEEP_GOING = 0x200; 037 038 /** 039 * Clang option constant to switch to single file parse mode (see 040 * CXTranslationUnit_SingleFileParse in clang code). 041 */ 042 private static final int PARSE_OPTION_SINGLE_FILE_PARSE = 0x400; 043 044 /** 045 * A fragment of the typical error message that appears if a 32-bit JVM is used 046 * on a 64-bit system, which causes JNI problems. See e.g. 047 * https://stackoverflow.com/a/8113109/3989225 048 */ 049 private static final String BITNESS_MISMATCH_ERROR_MESSAGE = "Can't load IA 32-bit"; 050 051 private SWIGTYPE_p_void clangIndex; 052 private SWIGTYPE_p_CXTranslationUnitImpl mainTranslationUnit; 053 054 private ClangTranslationUnitWrapper(SWIGTYPE_p_void clangIndex, 055 SWIGTYPE_p_CXTranslationUnitImpl mainTranslationUnit) { 056 this.clangIndex = clangIndex; 057 this.mainTranslationUnit = mainTranslationUnit; 058 } 059 060 /** 061 * Returns the current file parsed by clang. 062 * 063 * The clang index might contain more than one file (header files) which maybe 064 * lead to more than one translation unit known to clang. This is the 065 * translation unit corresponding to the file referenced in the current 066 * {@link eu.cqse.check.framework.core.ICheckContext}. 067 */ 068 public SWIGTYPE_p_CXTranslationUnitImpl getMainTranslationUnit() { 069 return mainTranslationUnit; 070 } 071 072 /** 073 * Creates a {@link ClangTranslationUnitWrapper} based on the given 074 * {@link ITokenElementContext}. This means the text content of the context is 075 * parsed by clang. 076 */ 077 public static ClangTranslationUnitWrapper createForContext(ITokenElementContext context) throws ConQATException { 078 String text = null; 079 try { 080 text = context.getTextContent(ECodeViewOption.ETextViewOption.UNFILTERED_CONTENT); 081 } catch (CheckException e) { 082 throw new ConQATException("Could not retrieve text content for clang initialization.", e); 083 } 084 return createForText(context.getUniformPath(), text); 085 } 086 087 /** 088 * Creates a {@link ClangTranslationUnitWrapper} based on the given uniform path 089 * and text content. 090 */ 091 public static ClangTranslationUnitWrapper createForText(String uniformPath, String text) throws ConQATException { 092 try { 093 ClangJniLoader.ensureLoaded(); 094 // the second parameter controls whether diagnostics are printed to stderr or 095 // not. 096 SWIGTYPE_p_void clangIndex = Clang.clang_createIndex(0, 0); 097 if (clangIndex == null) { 098 CCSMAssert.fail("Failed to create a clang index"); 099 } 100 CXUnsavedFile file = new CXUnsavedFile(); 101 try { 102 file.setFilename(uniformPath); 103 file.setContents(text); 104 file.setLength(text.length()); 105 106 int options = PARSE_OPTION_INCOMPLETE | PARSE_OPTION_KEEP_GOING | PARSE_OPTION_SINGLE_FILE_PARSE; 107 SWIGTYPE_p_CXTranslationUnitImpl translationUnit = Clang.clang_parseTranslationUnit(clangIndex, 108 uniformPath, null, 0, new CXUnsavedFile[] { file }, 1, options); 109 110 if (translationUnit == null) { 111 Clang.clang_disposeIndex(clangIndex); 112 CCSMAssert.fail("Translation unit could not be created for file " + uniformPath); 113 } 114 return new ClangTranslationUnitWrapper(clangIndex, translationUnit); 115 } finally { 116 file.delete(); 117 } 118 119 } catch (UnsatisfiedLinkError e) { 120 String errorMessage = SystemUtils.getVisualStudioRuntimeErrorMessage("Clang"); 121 if (e.getMessage().contains(BITNESS_MISMATCH_ERROR_MESSAGE)) { 122 errorMessage += "\nIn addition, ensure that you have installed a 64-bit version of Java if you're on a 64-bit system."; 123 } 124 throw new ConQATException(errorMessage, e); 125 } 126 } 127 128 @Override 129 protected void finalize() { 130 dispose(); 131 } 132 133 /** 134 * Allows to explicitly dispose this wrapper and free native memory. This can be 135 * used to avoid the need to wait for the garbage collector, when the instance 136 * is no longer needed. 137 */ 138 public void dispose() { 139 if (mainTranslationUnit != null) { 140 Clang.clang_disposeTranslationUnit(mainTranslationUnit); 141 mainTranslationUnit = null; 142 } 143 if (clangIndex != null) { 144 Clang.clang_disposeIndex(clangIndex); 145 clangIndex = null; 146 } 147 } 148 149 /** 150 * Returns a new CXCursor pointing at the root node of the current translation 151 * unit ({@link #getMainTranslationUnit()}). 152 */ 153 public CXCursor getMainRootCursor() { 154 return Clang.clang_getTranslationUnitCursor(getMainTranslationUnit()); 155 } 156}