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}