From 2279927623d6d393db81fba7ceca12f047935bf8 Mon Sep 17 00:00:00 2001 From: Andrew Eidsness Date: Tue, 19 Feb 2013 20:40:15 -0500 Subject: Bug 400020: Allow tagging of IBindings This new extension point allows contributors to put their own information into the PDOM and to later retrieve it for their own purposes. There are many details in the bug. The idea is that contributors provide an implementation of IBindingTagger, which is given a chance to examine IBindings when they are created. The ITagWriter interface allows the contributor to create a new tag which can then have data written to it. The ITagService interface (accessible from CCorePlugin.getTagService() provides a way for the contributor to later get an instance of ITagReader to retrieve tags from bindings. ITags are copied to the PDOM when the associated binding is persisteed. Contributors use a unique id (based on their plugin id), so that multiple contributors are able to independently tag a given binding. In-memory tags are not cached. I've done some timing tests using my sample implementation and found no measurable difference. The full log lines look like: !MESSAGE Indexed 'simple-01' (2 sources, 184 headers) in sec: 21,550 declarations; 35,394 references; 0 unresolved inclusions; 1 syntax errors; 0 unresolved names (0.00%) I did 5 tests using the current master (no tagging-related code), the times were: 18.86 sec 9.17 sec 5.91 sec 4.79 sec 4.83 sec And then I ran the same sequence of tests using the code in this commit: 18.73 sec 9.39 sec 6.50 sec 4.78 sec 5.27 sec If performance does become a problem, then caching could be introduced with a new implementation of ITaggableService. The two problems are finding a key other than the identity of the IBinding (since IBindings are re-created often) and properly evicting stale entries when the binding is no longer valid. The process of copying tags from an in-memory IBinding to a PDOMBinding, is a synchronization. This means that tags that are no longer applicable, will be removed from the persistent store. While developing this I found that PDOMBindings are not deleted from the Database (only the names that reference them are deleted), so there is no provision for deleting all tags at once. New database locks are not needed. By the time the persistent tags are accessed, higher levels of code have already taken a read or write lock as appropriate. There are new unit tests covering the changes to the PDOM. Change-Id: I8da1bf5eeba7e1fc2ca7ec308ed8e212629986a4 Reviewed-on: https://git.eclipse.org/r/10407 IP-Clean: Doug Schaefer Tested-by: Doug Schaefer Reviewed-by: Doug Schaefer --- .../internal/pdom/tests/PDOMStringSetTests.java | 78 ++++++ .../cdt/internal/pdom/tests/PDOMTagIndexTests.java | 135 +++++++++ .../eclipse/cdt/internal/pdom/tests/PDOMTests.java | 4 +- core/org.eclipse.cdt.core/META-INF/MANIFEST.MF | 4 + .../cdt/core/dom/ast/tag/IBindingTagger.java | 34 +++ .../org/eclipse/cdt/core/dom/ast/tag/ITag.java | 41 +++ .../eclipse/cdt/core/dom/ast/tag/ITagReader.java | 33 +++ .../eclipse/cdt/core/dom/ast/tag/ITagService.java | 30 ++ .../eclipse/cdt/core/dom/ast/tag/ITagWriter.java | 34 +++ .../eclipse/cdt/core/dom/ast/tag/IWritableTag.java | 31 ++ .../core/dom/ast/tag/NonCachedTaggable.java | 96 +++++++ .../eclipse/cdt/internal/core/dom/ast/tag/Tag.java | 78 ++++++ .../cdt/internal/core/dom/ast/tag/TagManager.java | 107 +++++++ .../cdt/internal/core/dom/ast/tag/TagService.java | 35 +++ .../core/dom/ast/tag/TaggerDescriptor.java | 202 +++++++++++++ .../core/dom/parser/cpp/semantics/CPPVisitor.java | 6 +- .../internal/core/pdom/AbstractIndexerTask.java | 5 + .../org/eclipse/cdt/internal/core/pdom/PDOM.java | 19 +- .../cdt/internal/core/pdom/db/PDOMStringSet.java | 205 ++++++++++++++ .../cdt/internal/core/pdom/dom/PDOMBinding.java | 10 + .../cdt/internal/core/pdom/dom/c/PDOMCLinkage.java | 14 +- .../internal/core/pdom/dom/cpp/PDOMCPPLinkage.java | 24 +- .../cdt/internal/core/pdom/tag/BTreeIterable.java | 78 ++++++ .../cdt/internal/core/pdom/tag/PDOMTag.java | 312 +++++++++++++++++++++ .../cdt/internal/core/pdom/tag/PDOMTagIndex.java | 279 ++++++++++++++++++ .../core/pdom/tag/PDOMTagSynchronizer.java | 104 +++++++ .../cdt/internal/core/pdom/tag/PDOMTaggable.java | 51 ++++ core/org.eclipse.cdt.core/plugin.properties | 1 + core/org.eclipse.cdt.core/plugin.xml | 1 + core/org.eclipse.cdt.core/schema/tagger.exsd | 139 +++++++++ .../src/org/eclipse/cdt/core/CCorePlugin.java | 11 + 31 files changed, 2190 insertions(+), 11 deletions(-) create mode 100644 core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMStringSetTests.java create mode 100644 core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTagIndexTests.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IBindingTagger.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITag.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagReader.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagService.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagWriter.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IWritableTag.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/NonCachedTaggable.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/Tag.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagManager.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagService.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TaggerDescriptor.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/PDOMStringSet.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/BTreeIterable.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTag.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagIndex.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagSynchronizer.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTaggable.java create mode 100644 core/org.eclipse.cdt.core/schema/tagger.exsd diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMStringSetTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMStringSetTests.java new file mode 100644 index 00000000000..1ba161f81ad --- /dev/null +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMStringSetTests.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.pdom.tests; + +import java.io.File; + +import junit.framework.Test; + +import org.eclipse.cdt.core.testplugin.util.BaseTestCase; +import org.eclipse.cdt.internal.core.pdom.db.ChunkCache; +import org.eclipse.cdt.internal.core.pdom.db.Database; +import org.eclipse.cdt.internal.core.pdom.db.PDOMStringSet; + +// copy/pasted from BTreeTests +public class PDOMStringSetTests extends BaseTestCase +{ + protected File dbFile; + protected Database db; + protected PDOMStringSet stringSet; + protected int rootRecord; + + public static Test suite() + { + return suite( PDOMStringSetTests.class ); + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + dbFile = File.createTempFile( "pdomstringsettest", "db" ); + db = new Database( dbFile, new ChunkCache(), 0, false ); + db.setExclusiveLock(); + rootRecord = Database.DATA_AREA; + stringSet = new PDOMStringSet( db, rootRecord ); + } + + @Override + protected void tearDown() throws Exception + { + db.close(); + dbFile.deleteOnExit(); + + super.tearDown(); + } + + // Quick tests to exercise the basic functionality. + public void testInterface() throws Exception + { + long val1_rec_a = stringSet.add( "val1" ); + long val2_rec_a = stringSet.add( "val2" ); + long val1_rec_b = stringSet.add( "val1" ); + assertTrue( val1_rec_a != 0 ); + assertTrue( val2_rec_a != 0 ); + assertEquals( val1_rec_a, val1_rec_b ); + + long val1_find = stringSet.find( "val1" ); + long val2_find = stringSet.find( "val2" ); + assertEquals( val1_rec_a, val1_find ); + assertEquals( val2_rec_a, val2_find ); + + long val1_rm = stringSet.remove( "val1" ); + assertEquals( val1_rec_a, val1_rm ); + assertEquals( 0, stringSet.find( "val1" ) ); + assertEquals( val2_rec_a, stringSet.find( "val2" ) ); + + stringSet.clearCaches(); + assertEquals( val2_rec_a, stringSet.find( "val2" ) ); + assertEquals( 0, stringSet.find( "val1" ) ); + } +} diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTagIndexTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTagIndexTests.java new file mode 100644 index 00000000000..34df365853e --- /dev/null +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTagIndexTests.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.pdom.tests; + +import java.io.File; +import java.util.Arrays; + +import junit.framework.Test; + +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.index.IIndexFileLocation; +import org.eclipse.cdt.core.index.IIndexLocationConverter; +import org.eclipse.cdt.core.model.LanguageManager; +import org.eclipse.cdt.core.testplugin.util.BaseTestCase; +import org.eclipse.cdt.internal.core.dom.ast.tag.Tag; +import org.eclipse.cdt.internal.core.pdom.PDOM; +import org.eclipse.cdt.internal.core.pdom.WritablePDOM; +import org.eclipse.cdt.internal.core.pdom.tag.PDOMTag; +import org.eclipse.cdt.internal.core.pdom.tag.PDOMTagIndex; + +// copy/pasted from BTreeTests +public class PDOMTagIndexTests extends BaseTestCase +{ + private PDOM pdom; + + public static Test suite() + { + return suite( PDOMTagIndexTests.class ); + } + + private static class MockIndexLocationConverter implements IIndexLocationConverter + { + @Override public IIndexFileLocation fromInternalFormat( String raw ) { return null; } + @Override public String toInternalFormat( IIndexFileLocation location ) { return null; } + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + File tmpFile = File.createTempFile( getClass().getSimpleName() + '.' + Double.toString( Math.random() ).substring( 2 ), null ); + pdom = new WritablePDOM( tmpFile, new MockIndexLocationConverter(), LanguageManager.getInstance().getPDOMLinkageFactoryMappings() ); + } + + @Override + protected void tearDown() throws Exception + { + pdom.close(); + super.tearDown(); + } + + // return the nearest valid record that is less than the specified base + private static long lastRecordBase = 1000; + private static long computeValidRecord() + { + lastRecordBase += 1000; + return ( lastRecordBase & ~7L ) | 2; + } + + // A quick sanity test to confirm basic functionality. + public void testSimple() throws Exception + { + String tagger = "tagger_a"; + long rec = computeValidRecord(); + + assertNotNull( PDOMTagIndex.createTag( pdom, rec, tagger, 1 ) ); + assertNotNull( PDOMTagIndex.getTag( pdom, rec, tagger ) ); + } + + public void testMultipleTaggers() throws Exception + { + String tagger_a = "tagger_a"; + String tagger_b = "tagger_b"; + long rec1 = computeValidRecord(); + long rec2 = computeValidRecord(); + + assertNotNull( PDOMTagIndex.createTag( pdom, rec1, tagger_a, 1 ) ); + assertNotNull( PDOMTagIndex.createTag( pdom, rec1, tagger_b, 1 ) ); + assertNotNull( PDOMTagIndex.createTag( pdom, rec2, tagger_a, 1 ) ); + + assertNotNull( PDOMTagIndex.getTag( pdom, rec2, tagger_a ) ); + assertNull( PDOMTagIndex.getTag( pdom, rec2, tagger_b ) ); + + Iterable tags1 = PDOMTagIndex.getTags( pdom, rec1 ); + int tag_count = 0; + for( ITag tag : tags1 ) + { + ++tag_count; + assertTrue( tag.getTaggerId().equals( tagger_a ) || tag.getTaggerId().equals( tagger_b ) ); + assertEquals( 1, tag.getDataLen() ); + } + assertEquals( 2, tag_count ); + } + + public void testReplaceTags() throws Exception + { + String tagger_a = "tagger_a"; + String tagger_b = "tagger_b"; + long rec = computeValidRecord(); + + ITag taga = PDOMTagIndex.createTag( pdom, rec, tagger_a, 2 ); + assertNotNull( taga ); + assertTrue( taga instanceof PDOMTag ); + PDOMTag taga_pdom = (PDOMTag)taga; + ITag tagb = PDOMTagIndex.createTag( pdom, rec, tagger_a, 2 ); + assertNotNull( tagb ); + + // replacement should delete tags for taggers that are no longer present and shorter tags + // should be modified in place + PDOMTagIndex.setTags( pdom, rec, Arrays.asList( new Tag( tagger_a, 1 ) ) ); + assertNull( PDOMTagIndex.getTag( pdom, rec, tagger_b ) ); + ITag shorter_ = PDOMTagIndex.getTag( pdom, rec, tagger_a ); + assertNotNull( shorter_ ); + assertTrue( shorter_ instanceof PDOMTag ); + PDOMTag shorter_pdom = (PDOMTag)shorter_; + assertEquals( taga_pdom.getRecord(), shorter_pdom.getRecord() ); + + // longer tags should create a new record + PDOMTagIndex.setTags( pdom, rec, Arrays.asList( new Tag( tagger_a, 4 ) ) ); + ITag longer_ = PDOMTagIndex.getTag( pdom, rec, tagger_a ); + assertNotNull( longer_ ); + assertTrue( longer_ instanceof PDOMTag ); + PDOMTag longer_pdom = (PDOMTag)longer_; + assertTrue( taga_pdom.getRecord() != longer_pdom.getRecord() ); + + // TODO figure out how to confirm that the original tag was free'd + } +} diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTests.java index f244ec3925c..3640bbe7339 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTests.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/PDOMTests.java @@ -38,7 +38,9 @@ public class PDOMTests extends TestSuite { suite.addTest(OverloadsWithinSingleTUTests.suite()); suite.addTest(OverloadsWithinCommonHeaderTests.suite()); suite.addTest(BTreeTests.suite()); - suite.addTest(FilesOnReindexTests.suite()); + suite.addTest(PDOMStringSetTests.suite()); + suite.addTest(PDOMTagIndexTests.suite()); + suite.addTest(FilesOnReindexTests.suite()); suite.addTest(GeneratePDOMApplicationTest.suite()); suite.addTest(CPPFieldTests.suite()); diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index 9130967645b..b4de8014640 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -16,6 +16,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.core.dom.ast.gnu, org.eclipse.cdt.core.dom.ast.gnu.c, org.eclipse.cdt.core.dom.ast.gnu.cpp, + org.eclipse.cdt.core.dom.ast.tag, org.eclipse.cdt.core.dom.parser, org.eclipse.cdt.core.dom.parser.c, org.eclipse.cdt.core.dom.parser.cpp, @@ -52,6 +53,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.internal.core.browser;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.internal.core.cdtvariables;x-internal:=true, org.eclipse.cdt.internal.core.dom;x-internal:=true, + org.eclipse.cdt.internal.core.dom.ast.tag;x-internal:=true, org.eclipse.cdt.internal.core.dom.parser;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.internal.core.dom.parser.c;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.internal.core.dom.parser.cpp;x-friends:="org.eclipse.cdt.ui", @@ -84,6 +86,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.internal.core.pdom.dom.cpp;x-internal:=true, org.eclipse.cdt.internal.core.pdom.export;x-internal:=true, org.eclipse.cdt.internal.core.pdom.indexer;x-friends:="org.eclipse.cdt.ui", + org.eclipse.cdt.internal.core.pdom.tag;x-internal:=true, org.eclipse.cdt.internal.core.resources;x-friends:="org.eclipse.cdt.ui,org.eclipse.cdt.make.core,org.eclipse.cdt.codan.ui.cxx", org.eclipse.cdt.internal.core.settings.model;x-internal:=true, org.eclipse.cdt.internal.core.util;x-internal:=true, @@ -111,6 +114,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.utils.xcoff, org.eclipse.cdt.utils.xcoff.parser Require-Bundle: org.eclipse.core.contenttype;bundle-version="[3.3.0,4.0.0)", + org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)", org.eclipse.core.filebuffers;bundle-version="[3.2.0,4.0.0)", org.eclipse.core.filesystem;bundle-version="[1.1.0,2.0.0)", org.eclipse.core.resources;bundle-version="[3.2.0,4.0.0)", diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IBindingTagger.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IBindingTagger.java new file mode 100644 index 00000000000..e5a31f14b72 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IBindingTagger.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IBinding; + +/** + * Implementations are contributed with the org.eclipse.cdt.core.tagger extension-point. The implementation + * is responsible for populating the tag's data using a given input binding. + * + * @see #process(ITagWriter, IBinding, IASTName) + * @since 5.5 + */ +public interface IBindingTagger +{ + /** + * Examine the given input binding to decide if a tag should be created. Use the given tagWriter + * to create data if needed. Return the tag if one was created and null otherwise. A tagger (as + * identified by it's unique id string) is allowed to create only one tag for each binding. + * + * @param tagWriter the writer to use for creating new tags + * @param binding the binding to examine when populating the tag (if needed) + * @param ast the AST name from which the binding was created + * @return the tag if one was created and null otherwise + */ + public ITag process( ITagWriter tagWriter, IBinding binding, IASTName ast ); +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITag.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITag.java new file mode 100644 index 00000000000..00c0ec9fcf5 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITag.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + +/** + * Tags are used to annotate {@link ITagReader}'s with extra information. They are created + * by implementations of {@link IBindingTagger} which are contributed using the + * org.eclipse.cdt.core.tagger extension point. The base tag interface is read-only, it + * is extended by the writable {@link IWritableTag}. + * + * @see IBindingTagger + * @see ITagReader + * @see IWritableTag + * @since 5.5 + */ +public interface ITag +{ + /** A constant that is returned to indicate a read failure. */ + public static final int Fail = -1; + + /** Return the number of bytes in the tag's data payload. */ + public int getDataLen(); + + /** Return the globally unique id of the tagger that created the receiver. */ + public String getTaggerId(); + + /** Return the byte from the specified offset or {@link #Fail} on failure. */ + public int getByte( int offset ); + + /** Return the specified number of bytes from the specified offset. Specify len of -1 to read all + * bytes from the specified offset to the end of the payload. Return null on failure. */ + public byte[] getBytes( int offset, int len ); + + // TODO read methods for all the other types +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagReader.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagReader.java new file mode 100644 index 00000000000..fb9d6ca3f58 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagReader.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + + +/** + * An interface that provides read-only access to the tags associated with a particular binding. + * + * @see ITag + * @see ITagService + * @since 5.5 + */ +public interface ITagReader +{ + /** + * Look for a tag for the receiver, returns null if there is no such tag. + * + * @param id A string that uniquely identifies the tag to be returned. This value was provided by the contributor + * when the tag was created (see {@link ITagWriter#createTag(String, int)}). + */ + public ITag getTag( String id ); + + /** + * Return all tags known to the receiver. Does not return null. + */ + public Iterable getTags(); +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagService.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagService.java new file mode 100644 index 00000000000..05e12af7a4b --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagService.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + +import org.eclipse.cdt.core.dom.ast.IBinding; + +/** + * Provides ITagReaders for specific bindings. The kind of the reader will vary based on + * the kind of the input binding. + * + * @see ITag + * @see ITagReader + * @since 5.5 + */ +public interface ITagService +{ + /** + * Finds or creates a tag reader for the specified binding or null if a reader cannot + * be associated with this binding. + * + * @param binding could be null + */ + public ITagReader findTagReader( IBinding binding ); +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagWriter.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagWriter.java new file mode 100644 index 00000000000..34849ba59fe --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/ITagWriter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + +/** + * An interface that allows tag creation and modification. + * + * @see ITag + * @see ITagService + * @since 5.5 + */ +public interface ITagWriter +{ + /** + * Create and return a new tag for the receiver. E.g., if this writer is associated with a persistent binding, + * then returned tag will read and write from the PDOM database. + * + * @param id A string that uniquely identifies the tag to be returned. This value will be used by the contributor + * when to find the tag (see {@link ITagReader#getTag(String)}). + * @param len The number of bytes that should be allocated to store the tag's data. + */ + public IWritableTag createTag( String id, int len ); + + /** + * Sets the receiver's tags to only the ones provided. Deletes existing tags that are not in the argument list. + */ + public boolean setTags( Iterable tags ); +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IWritableTag.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IWritableTag.java new file mode 100644 index 00000000000..bb6a8eecb33 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/ast/tag/IWritableTag.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.core.dom.ast.tag; + +/** + * Tags are used to annotate {@link ITagWriter}'s with extra information. They are created + * by implementations of {@link IBindingTagger} which are contributed using the + * org.eclipse.cdt.core.tagger extension point. + * + * @see IBindingTagger + * @see ITagReader + * @see ITagWriter + * @since 5.5 + */ +public interface IWritableTag extends ITag +{ + /** Write the given byte to the given offset in the tag. Return true if successful and false otherwise. */ + public boolean putByte( int offset, byte data ); + + /** Write the argument buffer into the receiver's payload starting at the specified offset. Write the specified + * number of bytes or the full buffer when len is -1. Return true if successful and false otherwise. */ + public boolean putBytes( int offset, byte data[], int len ); + + // TODO write for all types +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/NonCachedTaggable.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/NonCachedTaggable.java new file mode 100644 index 00000000000..c74ea936709 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/NonCachedTaggable.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.dom.ast.tag; + +import org.eclipse.cdt.core.dom.ast.IASTDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.ITagReader; +import org.eclipse.cdt.core.dom.ast.tag.ITagWriter; +import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; +import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding; + +public class NonCachedTaggable implements ITagReader, ITagWriter +{ + private final IBinding binding; + private IASTName ast; + + public NonCachedTaggable( IBinding binding ) + { + this.binding = binding; + } + + @Override + public IWritableTag createTag( String id, int len ) + { + return new Tag( id, len ); + } + + @Override + public ITag getTag( String id ) + { + return TagManager.getInstance().process( id, this, binding, getAST() ); + } + + @Override + public Iterable getTags() + { + return TagManager.getInstance().process( this, binding, getAST() ); + } + + @Override + public boolean setTags( Iterable tags ) + { + // this non-caching implementation has nothing to set, the tags will be regenerated + // when they are queried + return true; + } + + private IASTName getAST() + { + if( ast != null ) + return ast; + + if( ! ( binding instanceof ICPPInternalBinding ) ) + return null; + + IASTNode node = getPhysicalNode( (ICPPInternalBinding)binding ); + if( node == null ) + return null; + + return ast = getName( node ); + } + + private static IASTNode getPhysicalNode( ICPPInternalBinding binding ) + { + IASTNode node = binding.getDefinition(); + if( node != null ) + return node; + + IASTNode[] nodes = binding.getDeclarations(); + if( nodes == null + || nodes.length <= 0 ) + return null; + return nodes[0]; + } + + private static IASTName getName( IASTNode node ) + { + if( node instanceof IASTName ) + return (IASTName)node; + if( node instanceof ICPPASTCompositeTypeSpecifier ) + return ( (ICPPASTCompositeTypeSpecifier)node ).getName(); + if( node instanceof IASTDeclarator ) + return ( (IASTDeclarator)node ).getName(); + return null; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/Tag.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/Tag.java new file mode 100644 index 00000000000..d0979f421dd --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/Tag.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.dom.ast.tag; + +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; + +/** + * A trivial implementation that stores all data in memory. + * + * @see NonCachedTaggable + */ +public class Tag implements IWritableTag +{ + private final String taggerId; + private final byte[] buff; + + public Tag( String taggerId, int dataLen ) + { + this.taggerId = taggerId; + this.buff = new byte[dataLen]; + } + + @Override public String getTaggerId() { return taggerId; } + @Override public int getDataLen() { return buff.length; } + + private boolean isInBounds( int offset, int len ) + { + return offset >= 0 + && offset < buff.length + && ( offset + len ) <= buff.length; + } + + @Override + public boolean putByte( int offset, byte b ) + { + if( ! isInBounds( offset, 1 ) ) + return false; + + buff[offset] = b; + return true; + } + + @Override + public boolean putBytes( int offset, byte[] data, int len ) + { + len = len >= 0 ? len : data.length; + if( ! isInBounds( offset, len ) ) + return false; + + System.arraycopy( data, 0, buff, offset, len ); + return true; + } + + @Override + public int getByte( int offset ) + { + return isInBounds( offset, 1 ) ? buff[offset] : ITag.Fail; + } + + @Override + public byte[] getBytes( int offset, int len ) + { + len = len >= 0 ? len : buff.length - offset; + if( ! isInBounds( offset, len ) ) + return null; + + byte[] data = new byte[len]; + System.arraycopy( buff, offset, data, 0, len ); + return data; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagManager.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagManager.java new file mode 100644 index 00000000000..c302fae7e28 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagManager.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.dom.ast.tag; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.tag.IBindingTagger; +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.ITagReader; +import org.eclipse.cdt.core.dom.ast.tag.ITagWriter; +import org.eclipse.cdt.internal.core.pdom.dom.IPDOMBinding; +import org.eclipse.cdt.internal.core.pdom.tag.PDOMTaggable; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; + +public class TagManager +{ + private static TagManager INSTANCE; + + private Map taggers; + + public static TagManager getInstance() + { + if( INSTANCE == null ) + INSTANCE = new TagManager(); + return INSTANCE; + } + + private TagManager() + { + taggers = loadExtensions(); + } + + private static final String ExtensionPoint = "tagger"; //$NON-NLS-1$ + + private static Map loadExtensions() + { + Map taggers = new HashMap(); + + // load the extensions + IConfigurationElement[] elements + = Platform.getExtensionRegistry().getConfigurationElementsFor( CCorePlugin.PLUGIN_ID, ExtensionPoint ); + for (IConfigurationElement element : elements) + { + TaggerDescriptor desc = new TaggerDescriptor( element ); + taggers.put( desc.getId(), desc ); + } + + return taggers; + } + + /** Provide an opportunity for the specified tagger to process the given values. The tagger will only + * run if its enablement expression returns true for the arguments. */ + public ITag process( String taggerId, ITagWriter tagWriter, IBinding binding, IASTName ast ) + { + TaggerDescriptor desc = taggers.get( taggerId ); + if( desc == null ) + return null; + + IBindingTagger tagger = desc.getBindingTaggerFor( binding, ast ); + return tagger == null ? null : tagger.process( tagWriter, binding, ast ); + } + + /** Provide an opportunity for all enabled taggers to process the given values. */ + public Iterable process( ITagWriter tagWriter, IBinding binding, IASTName ast ) + { + List tags = new LinkedList(); + for( TaggerDescriptor desc : taggers.values() ) + { + IBindingTagger tagger = desc.getBindingTaggerFor( binding, ast ); + if( tagger != null ) + { + ITag tag = tagger.process( tagWriter, binding, ast ); + if( tag != null ) + tags.add( tag ); + } + } + + return tags; + } + + /** Add or remove tags from the destination to ensure that it has the same tag information as the source. */ + public void syncTags( IPDOMBinding dst, IBinding src ) + { + if( dst == null ) + return; + + ITagReader tagReader = CCorePlugin.getTagService().findTagReader( src ); + if( tagReader == null ) + return; + + ITagWriter tagWriter = new PDOMTaggable( dst.getPDOM(), dst.getRecord() ); + tagWriter.setTags( tagReader.getTags() ); + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagService.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagService.java new file mode 100644 index 00000000000..dad4d60895c --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TagService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.dom.ast.tag; + +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.tag.ITagReader; +import org.eclipse.cdt.core.dom.ast.tag.ITagService; + +public class TagService implements ITagService +{ + /** + * First gives the IBinding instance a chance to convert itself, by calling IAdaptable#getAdapter( ITagReader.class ) + * on the binding. If the binding doesn't provide an implementation then a simple, in-memory, non-cached + * implementation is created and returned. + */ + @Override + public ITagReader findTagReader( IBinding binding ) + { + if( binding == null ) + return null; + + // let the binding adapt to its own tag reader + ITagReader tagReader = (ITagReader)binding.getAdapter( ITagReader.class ); + if( tagReader != null ) + return tagReader; + + return new NonCachedTaggable( binding ); + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TaggerDescriptor.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TaggerDescriptor.java new file mode 100644 index 00000000000..c0386142f80 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/ast/tag/TaggerDescriptor.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.dom.ast.tag; + +import java.util.Arrays; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.tag.IBindingTagger; +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.ITagWriter; +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.core.model.ILanguage; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.core.expressions.EvaluationContext; +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.ExpressionConverter; +import org.eclipse.core.expressions.ExpressionTagNames; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +/** + * Internal container for extensions of org.eclipse.cdt.core.tagger. The implementation of the + * tagger is instantiated only after checking the enablement expression (if present) for the + * specified binding. This avoids activating the contributing plugin until it is actually needed. + */ +public class TaggerDescriptor +{ + private static final String Attr_LocalId = "local-id"; //$NON-NLS-1$ + private static final String Attr_Class = "class"; //$NON-NLS-1$ + + private final IConfigurationElement element; + private final Expression enablementExpression; + private Boolean fStatus = null; + + private String id; + private IBindingTagger tagger; + + private static final String Var_projectNature = "projectNatures"; //$NON-NLS-1$ + private static final String Var_languageId = "languageId"; //$NON-NLS-1$ + + /** An empty implementation of the tagger used as a placeholder in descriptors that are unable to + * load the contributed class. */ + private static final IBindingTagger NullTagger = new IBindingTagger() + { + @Override public ITag process(ITagWriter tagWriter, IBinding binding, IASTName ast) { return null; } + }; + + public TaggerDescriptor( IConfigurationElement element ) + { + this.element = element; + + Expression expr = null; + IConfigurationElement[] children = element.getChildren( ExpressionTagNames.ENABLEMENT ); + switch (children.length) { + case 0: + fStatus = Boolean.TRUE; + break; + case 1: + try { + ExpressionConverter parser = ExpressionConverter.getDefault(); + expr = parser.perform( children[0] ); + } catch (CoreException e) { + CCorePlugin.log( "Error in enablement expression of " + id, e ); //$NON-NLS-1$ + } + break; + default: + CCorePlugin.log( "Too many enablement expressions for " + id ); //$NON-NLS-1$ + fStatus = Boolean.FALSE; + break; + } + enablementExpression = expr; + } + + public String getId() + { + if( id != null ) + return id; + + String globalId = element.getContributor().getName(); + String localId = element.getAttribute( Attr_LocalId ); + + // there must be a valid local id + if( localId == null ) + { + String extId = element.getDeclaringExtension().getSimpleIdentifier(); + CCorePlugin.log( "Invalid extension " + globalId + '.' + extId + " must provide tagger's local-id" ); //$NON-NLS-1$ //$NON-NLS-2$ + return null; + } + + // the extension should not include the plugin id, but return immediately if it does + if( localId.startsWith( globalId ) + && localId.length() > globalId.length() ) + return localId; + + // make sure the local id has real content + if( localId.isEmpty() ) + { + String extId = element.getDeclaringExtension().getSimpleIdentifier(); + CCorePlugin.log( "Invalid extension " + globalId + '.' + extId + " must provide value for tagger's local-id" ); //$NON-NLS-1$ //$NON-NLS-2$ + return null; + } + + // otherwise prepend with the globalId, and ensure a dot between them + if( localId.charAt( 0 ) == '.' ) + return globalId + localId; + return globalId + '.' + localId; + } + + private boolean matches( ITranslationUnit tu ) + { + // if the enablement expression is missing or structurally invalid, then return immediately + if( fStatus != null ) + return fStatus.booleanValue(); + + // if there is no tu, then the enablement expression cannot be evaluated, assume that all taggers + // are needed + if( tu == null ) + return true; + + if( enablementExpression != null ) + try + { + IProject project = null; + ICProject cProject = tu.getCProject(); + if( cProject != null ) + project = cProject.getProject(); + + EvaluationContext evalContext = new EvaluationContext( null, project ); + + // if the project is not accessible, then only taggers that don't care about it will + // get a chance to run + if( project != null ) + { + String[] natures = project.getDescription().getNatureIds(); + evalContext.addVariable( Var_projectNature, Arrays.asList( natures ) ); + } + + ILanguage language = tu.getLanguage(); + if( language != null ) + evalContext.addVariable( Var_languageId, language.getId() ); + + return enablementExpression.evaluate( evalContext ) == EvaluationResult.TRUE; + } + catch( CoreException e ) + { + CCorePlugin.log( "Error while evaluating enablement expression for " + id, e ); //$NON-NLS-1$ + } + + fStatus = Boolean.FALSE; + return false; + } + + private IBindingTagger getTagger() + { + if( tagger == null ) + synchronized( this ) + { + if( tagger == null ) + { + try { tagger = (IBindingTagger)element.createExecutableExtension( Attr_Class ); } + catch( CoreException e ) + { + String id = element.getDeclaringExtension().getNamespaceIdentifier() + '.' + + element.getDeclaringExtension().getSimpleIdentifier(); + CCorePlugin.log( "Error in class attribute of " + id, e ); //$NON-NLS-1$ + + // mark the tagger with an empty implementation to prevent future load attempts + tagger = NullTagger; + } + } + } + + return tagger; + } + + // Activates the plugin if needed. + public IBindingTagger getBindingTaggerFor( IBinding binding, IASTName ast ) + { + // If there isn't an ast with an AST-TU accessible, then there is no way to defer processing, + // just return the tagger and let it try to sort things out. E.g., this happens for built-in + // things. + if( ast == null ) + return getTagger(); + IASTTranslationUnit astTU = ast.getTranslationUnit(); + if( astTU == null ) + return getTagger(); + + // Otherwise evaluate the enablement expression for this TU + return matches( astTU.getOriginatingTranslationUnit() ) ? getTagger() : null; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java index d306479aaf5..403516e53a8 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java @@ -340,10 +340,10 @@ public class CPPVisitor extends ASTQueries { return false; } } - + if (inScope == null) return false; - + IBinding pb= names[names.length-2].resolvePreBinding(); if (pb instanceof IProblemBinding) return false; @@ -357,7 +357,7 @@ public class CPPVisitor extends ASTQueries { } else if (pb instanceof ICPPNamespace) { scope= ((ICPPNamespace)pb).getNamespaceScope(); } - + return scope == inScope; } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/AbstractIndexerTask.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/AbstractIndexerTask.java index cf628dbf052..df417262095 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/AbstractIndexerTask.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/AbstractIndexerTask.java @@ -41,6 +41,7 @@ import org.eclipse.cdt.core.index.IIndexManager; import org.eclipse.cdt.core.index.IndexLocationFactory; import org.eclipse.cdt.core.model.AbstractLanguage; import org.eclipse.cdt.core.model.ILanguage; +import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.parser.FileContent; import org.eclipse.cdt.core.parser.IParserLogService; import org.eclipse.cdt.core.parser.IScannerInfo; @@ -48,6 +49,7 @@ import org.eclipse.cdt.core.parser.ISignificantMacros; import org.eclipse.cdt.core.parser.IncludeFileContentProvider; import org.eclipse.cdt.core.parser.ParserUtil; import org.eclipse.cdt.internal.core.dom.IIncludeFileResolutionHeuristics; +import org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit; import org.eclipse.cdt.internal.core.index.FileContentKey; import org.eclipse.cdt.internal.core.index.IIndexFragment; import org.eclipse.cdt.internal.core.index.IIndexFragmentFile; @@ -1004,6 +1006,9 @@ public abstract class AbstractIndexerTask extends PDOMWriter { IASTTranslationUnit ast= createAST(lang, codeReader, scanInfo, isSource, fASTOptions, ctx, pm); fStatistics.fParsingTime += System.currentTimeMillis() - start; if (ast != null) { + // Give the new AST-TU a chance to recognize its translation unit before it is written + // to the index. + ( (ASTTranslationUnit)ast ).setOriginatingTranslationUnit( (ITranslationUnit)tu ); writeToIndex(lang.getLinkageID(), ast, codeReader, ctx, pm); } } catch (CoreException e) { diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/PDOM.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/PDOM.java index 03c11be0d0c..c24e8d698af 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/PDOM.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/PDOM.java @@ -89,6 +89,7 @@ import org.eclipse.cdt.internal.core.pdom.dom.PDOMMacroReferenceName; import org.eclipse.cdt.internal.core.pdom.dom.PDOMName; import org.eclipse.cdt.internal.core.pdom.dom.PDOMNamedNode; import org.eclipse.cdt.internal.core.pdom.dom.PDOMNode; +import org.eclipse.cdt.internal.core.pdom.tag.PDOMTagIndex; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; @@ -230,10 +231,12 @@ public class PDOM extends PlatformObject implements IPDOM { * 138.0 - Constexpr functions, bug 395238. * 139.0 - More efficient and robust storage of types and template arguments, bug 395243. * 140.0 - Enumerators with dependent values, bug 389009. + * 140.1 - Mechanism for tagging nodes with extended data, bug TODO */ + private static final int MIN_SUPPORTED_VERSION= version(140, 0); private static final int MAX_SUPPORTED_VERSION= version(140, Short.MAX_VALUE); - private static final int DEFAULT_VERSION = version(140, 0); + private static final int DEFAULT_VERSION = version(140, 1); private static int version(int major, int minor) { return (major << 16) + minor; @@ -269,7 +272,8 @@ public class PDOM extends PlatformObject implements IPDOM { public static final int INDEX_OF_DEFECTIVE_FILES = Database.DATA_AREA + 8; public static final int INDEX_OF_FILES_WITH_UNRESOLVED_INCLUDES = Database.DATA_AREA + 12; public static final int PROPERTIES = Database.DATA_AREA + 16; - public static final int END= Database.DATA_AREA + 20; + public static final int TAG_INDEX = Database.DATA_AREA + 20; + public static final int END= Database.DATA_AREA + 24; static { assert END <= Database.CHUNK_SIZE; } @@ -331,6 +335,7 @@ public class PDOM extends PlatformObject implements IPDOM { // Local caches protected Database db; private BTree fileIndex; + private PDOMTagIndex tagIndex; private BTree indexOfDefectiveFiles; private BTree indexOfFiledWithUnresolvedIncludes; private final Map fLinkageIDCache = new HashMap(); @@ -459,6 +464,15 @@ public class PDOM extends PlatformObject implements IPDOM { return fileIndex; } + public PDOMTagIndex getTagIndex() throws CoreException { + if (tagIndex == null) + { + // tag index can only be stored in database versions 139.1 or greater + tagIndex = new PDOMTagIndex( db.getVersion() >= version( 139, 1 ) ? db : null, TAG_INDEX ); + } + return tagIndex; + } + /** * Returns the index of files that were read with I/O errors. */ @@ -1357,6 +1371,7 @@ public class PDOM extends PlatformObject implements IPDOM { private void clearCaches() { fileIndex= null; + tagIndex = null; indexOfDefectiveFiles= null; indexOfFiledWithUnresolvedIncludes= null; fLinkageIDCache.clear(); diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/PDOMStringSet.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/PDOMStringSet.java new file mode 100644 index 00000000000..976278d06e7 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/PDOMStringSet.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.db; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.CoreException; + +/** + * A container for storing a set of strings in the Database. The container allows only one + * instance of each string to be stored. + *

+ * This implementation should only be used when the set is expected to be small. It uses a + * singly linked list for storing strings in Database. Which means that a linear lookup is needed + * to find strings in the list. An in-memory, lazily-loaded, cache is provided so the list will + * only be fully retrieved once in the lifetime of this instance. A BTree will be more efficient + * for larger sets. + */ +public class PDOMStringSet +{ + private final Database db; + + private long ptr; + private long head; + private long loaded; + + private Map lazyCache; + + public PDOMStringSet( Database db, long ptr ) throws CoreException + { + this.db = db; + this.ptr = ptr; + + head = 0; + loaded = 0; + } + + public void clearCaches() + { + head = 0; + loaded = 0; + + if( lazyCache != null ) + lazyCache = null; + } + + private long getHead() throws CoreException + { + if( head == 0 ) + head = db.getRecPtr( ptr ); + return head; + } + + // A simple enum describing the type of the information that is stored in the Database. Each + // enumerator represents a single field in the persistent structure and is able to answer its + // offset in that structure. + private static enum NodeType + { + Next, + Item, + _last; + + // NOTE: All fields are pointers, if that changes then these initializations will need + // to be updated. + public final long offset = ordinal() * Database.PTR_SIZE; + public static final int sizeof = (int)_last.offset; + + /** Return the value of the pointer stored in this field in the given instance. */ + public long get( Database db, long instance ) throws CoreException + { + return db.getRecPtr( instance + offset ); + } + + /** Store the given pointer into this field in the given instance. */ + public void put( Database db, long instance, long value ) throws CoreException + { + db.putRecPtr( instance + offset, value ); + } + } + + /** + * Adds the given string to the receiving set. May cause the entire list to be loaded from the + * Database while testing for uniqueness. Returns the record of the string that was inserted into + * the list. + */ + public long add( String str ) throws CoreException + { + long record = find( str ); + if( record != 0 ) + return record; + + IString string = db.newString( str ); + record = string.getRecord(); + + long new_node = db.malloc( NodeType.sizeof ); + NodeType.Next.put( db, new_node, getHead() ); + NodeType.Item.put( db, new_node, record ); + + if( lazyCache == null ) + lazyCache = new HashMap(); + lazyCache.put( str, record ); + + // If the Database has already been partially searched, then the loaded pointer will be after the + // head. Since we've already put this new record into the lazy cache, there is no reason to try to + // load it again. We put the new node at the start of the list so that it will be before the loaded + // pointer. + head = new_node; + if( loaded == 0 ) + loaded = new_node; + db.putRecPtr( ptr, new_node ); + return record; + } + + /** + * Search for the given string in the receiver. This could cause the entire list to be loaded from + * the Database. The results are cached, so the list will only be loaded one time during the lifetime + * of this instance. Returns the record of the String. + */ + public long find( String str ) throws CoreException + { + if( lazyCache != null ) + { + Long l = lazyCache.get( str ); + if( l != null ) + return l.longValue(); + } + + // if there is nothing in the Database, then there is nothing to load + if( getHead() == 0 ) + return 0; + + // otherwise prepare the cache for the data that is about to be loaded + if( lazyCache == null ) + lazyCache = new HashMap(); + + // if nothing has been loaded, then start loading with the head node, otherwise continue + // loading from whatever is after the last loaded node + long curr = loaded == 0 ? getHead() : NodeType.Next.get( db, loaded ); + while( curr != 0 ) + { + long next = NodeType.Next.get( db, curr ); + long item = NodeType.Item.get( db, curr ); + + IString string = db.getString( item ); + + // put the value into the cache + lazyCache.put( string.getString(), Long.valueOf( item ) ); + + // return immediately if this is the target + if( string.compare( str, true ) == 0 ) + return item; + + // otherwise keep looking + loaded = curr; + curr = next; + } + + return 0; + } + + /** + * Return a pointer to the record of the String that was removed. + */ + public long remove( String str ) throws CoreException + { + if( lazyCache != null ) + lazyCache.remove( str ); + + long prev = 0; + long curr = getHead(); + while( curr != 0 ) + { + long next = NodeType.Next.get( db, curr ); + long item = NodeType.Item.get( db, curr ); + + IString string = db.getString( item ); + + if( string.compare( str, true ) == 0 ) + { + if( head != curr ) + NodeType.Next.put( db, prev, next ); + else + { + db.putRecPtr( ptr, next ); + head = next; + } + + db.free( curr ); + return item; + } + + prev = curr; + curr = next; + } + + return 0; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/PDOMBinding.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/PDOMBinding.java index 769d7a0071f..233bda56d5a 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/PDOMBinding.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/PDOMBinding.java @@ -26,6 +26,7 @@ import org.eclipse.cdt.core.dom.ast.IScope.ScopeLookupData; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration; +import org.eclipse.cdt.core.dom.ast.tag.ITagReader; import org.eclipse.cdt.core.index.IIndexFileSet; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding; @@ -36,6 +37,7 @@ import org.eclipse.cdt.internal.core.index.IIndexScope; import org.eclipse.cdt.internal.core.pdom.PDOM; import org.eclipse.cdt.internal.core.pdom.db.Database; import org.eclipse.cdt.internal.core.pdom.db.IString; +import org.eclipse.cdt.internal.core.pdom.tag.PDOMTaggable; import org.eclipse.core.runtime.CoreException; /** @@ -67,6 +69,14 @@ public abstract class PDOMBinding extends PDOMNamedNode implements IPDOMBinding if (adapter.isAssignableFrom(PDOMBinding.class)) return this; + // Any PDOMBinding can have a persistent tag. These tags should be deleted when the PDOMBinding + // is deleted. However, PDOMBinding's don't get deleted, so there is no way to trigger deleting + // of the tags. If the implementation is changed so that PDOMBindings do get deleted, then call: + // PDOMTagIndex.setTags( getPDOM(), pdomBinding.record, Collections.emptyList() ); + // to clear out all tags for the binding. + if (adapter == ITagReader.class) + return new PDOMTaggable( getPDOM(), getRecord() ); + return null; } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/c/PDOMCLinkage.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/c/PDOMCLinkage.java index dd7be434ab3..77608c1b493 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/c/PDOMCLinkage.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/c/PDOMCLinkage.java @@ -25,6 +25,7 @@ import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.ITypedef; import org.eclipse.cdt.core.dom.ast.IVariable; import org.eclipse.cdt.core.index.IIndexBinding; +import org.eclipse.cdt.internal.core.dom.ast.tag.TagManager; import org.eclipse.cdt.internal.core.dom.parser.ISerializableEvaluation; import org.eclipse.cdt.internal.core.dom.parser.ITypeMarshalBuffer; import org.eclipse.cdt.internal.core.dom.parser.ProblemType; @@ -97,6 +98,11 @@ class PDOMCLinkage extends PDOMLinkage implements IIndexCBindingConstants { if (pdomBinding != null) { getPDOM().putCachedResult(inputBinding, pdomBinding); } + + // Synchronize the tags associated with the persistent binding to match the set that is + // associated with the input binding. + TagManager.getInstance().syncTags( pdomBinding, inputBinding ); + return pdomBinding; } @@ -104,7 +110,13 @@ class PDOMCLinkage extends PDOMLinkage implements IIndexCBindingConstants { } if (shouldUpdate(pdomBinding, fromName)) { - pdomBinding.update(this, fromName.getBinding()); + IBinding fromBinding = fromName.getBinding(); + + pdomBinding.update(this, fromBinding); + + // Update the tags based on the tags from the new binding. This was in PDOMBinding.update, but + // I found that not all subclasses (e.g., PDOMCPPFunction) call the parent implementation. + TagManager.getInstance().syncTags( pdomBinding, fromBinding ); } return pdomBinding; } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/cpp/PDOMCPPLinkage.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/cpp/PDOMCPPLinkage.java index 656dfb02abd..3d85ab48e7f 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/cpp/PDOMCPPLinkage.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/dom/cpp/PDOMCPPLinkage.java @@ -75,6 +75,7 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPVariable; import org.eclipse.cdt.core.index.IIndexBinding; import org.eclipse.cdt.core.parser.util.ArrayUtil; import org.eclipse.cdt.internal.core.Util; +import org.eclipse.cdt.internal.core.dom.ast.tag.TagManager; import org.eclipse.cdt.internal.core.dom.parser.ASTInternal; import org.eclipse.cdt.internal.core.dom.parser.ISerializableEvaluation; import org.eclipse.cdt.internal.core.dom.parser.ITypeMarshalBuffer; @@ -358,6 +359,10 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { if (inputBinding instanceof CPPClosureType) { addImplicitMethods(pdomBinding, (ICPPClassType) binding, fromName); } + + // Synchronize the tags associated with the persistent binding to match the set that is + // associated with the input binding. + TagManager.getInstance().syncTags( pdomBinding, inputBinding ); } } catch (DOMException e) { throw new CoreException(Util.createStatus(e)); @@ -367,8 +372,15 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { } if (shouldUpdate(pdomBinding, fromName)) { - pdomBinding.update(this, fromName.getBinding()); + IBinding fromBinding = fromName.getBinding(); + + pdomBinding.update(this, fromBinding); + + // Update the tags based on the tags from the new binding. This was in PDOMBinding.update, but + // I found that not all subclasses (e.g., PDOMCPPFunction) call the parent implementation. + TagManager.getInstance().syncTags( pdomBinding, fromBinding ); } + return pdomBinding; } @@ -408,7 +420,7 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { // template parameters are created directly by their owners. if (binding instanceof ICPPTemplateParameter) return null; - if (binding instanceof ICPPUnknownBinding) + if (binding instanceof ICPPUnknownBinding) return null; if (binding instanceof ICPPSpecialization) { @@ -561,6 +573,10 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { } else if (!getPDOM().hasLastingDefinition(pdomBinding)) { pdomBinding.update(this, method); old.remove(pdomBinding); + + // Update the tags based on the tags from the new binding. This was in PDOMBinding.update, but + // I found that not all subclasses (e.g., PDOMCPPFunction) call the parent implementation. + TagManager.getInstance().syncTags( pdomBinding, method ); } } } @@ -691,7 +707,7 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { if (result != null) { return result; } - + // Assign names to anonymous types. IBinding binding= PDOMASTAdapter.getAdapterForAnonymousASTBinding(inputBinding); if (binding == null) { @@ -1111,7 +1127,7 @@ class PDOMCPPLinkage extends PDOMLinkage implements IIndexCPPBindingConstants { return CPPPointerToMemberType.unmarshal(firstByte, buffer); case ITypeMarshalBuffer.DEPENDENT_EXPRESSION_TYPE: return TypeOfDependentExpression.unmarshal(firstByte, buffer); - case ITypeMarshalBuffer.UNKNOWN_MEMBER: + case ITypeMarshalBuffer.UNKNOWN_MEMBER: IBinding binding= CPPUnknownMember.unmarshal(getPDOM(), firstByte, buffer); if (binding instanceof IType) return (IType) binding; diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/BTreeIterable.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/BTreeIterable.java new file mode 100644 index 00000000000..1ea29d1fff7 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/BTreeIterable.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.tag; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.internal.core.pdom.db.BTree; +import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor; +import org.eclipse.core.runtime.CoreException; + +public class BTreeIterable implements Iterable +{ + public static interface Descriptor + { + public int compare( long record ) throws CoreException; + public T create( long record ); + } + + private final BTree btree; + private final Descriptor descriptor; + + public BTreeIterable( BTree btree, Descriptor descriptor ) + { + this.btree = btree; + this.descriptor = descriptor; + } + + @Override + public Iterator iterator() + { + Visitor v = new Visitor(); + try { btree.accept( v ); } + catch( CoreException e ) { CCorePlugin.log( e ); return Collections.emptyList().iterator(); } + return new BTreeIterator( v.records ); + } + + private class Visitor implements IBTreeVisitor + { + public final List records = new LinkedList(); + + @Override + public int compare( long record ) throws CoreException + { + return BTreeIterable.this.descriptor.compare( record ); + } + + @Override + public boolean visit( long record ) throws CoreException + { + records.add( Long.valueOf( record ) ); + return true; + } + } + + private class BTreeIterator implements Iterator + { + private final Iterator records; + + public BTreeIterator( Iterable records ) + { + this.records = records.iterator(); + } + + @Override public void remove() { } + @Override public boolean hasNext() { return records.hasNext(); } + @Override public T next() { return BTreeIterable.this.descriptor.create( records.next() ); } + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTag.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTag.java new file mode 100644 index 00000000000..ed7bfe63347 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTag.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.tag; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; +import org.eclipse.cdt.internal.core.pdom.db.Database; +import org.eclipse.cdt.internal.core.pdom.db.IBTreeComparator; +import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor; +import org.eclipse.core.runtime.CoreException; + +public class PDOMTag implements IWritableTag +{ + private final Database db; + private final long record; + + private String taggerId; + private int dataLen = -1; + + private static enum Fields + { + Node, + TaggerId, + DataLen, + Data; + + public final long offset = ordinal() * Database.PTR_SIZE; + public static int sizeof( int datalen ) { return (int)Data.offset + datalen; } + + public long getRecPtr( Database db, long instance, long data_offset ) throws CoreException + { + return db.getRecPtr( instance + offset + data_offset ); + } + + public void putRecPtr( Database db, long instance, long data_offset, long value ) throws CoreException + { + db.putRecPtr( instance + offset + data_offset, value ); + } + + public void put( Database db, long instance, long data_offset, byte value ) throws CoreException + { + db.putByte( instance + offset + data_offset, value ); + } + + public void put( Database db, long instance, byte[] data, long data_offset, int len ) throws CoreException + { + db.putBytes( instance + offset + data_offset, data, len ); + } + + public byte getByte( Database db, long instance, long data_offset ) throws CoreException + { + return db.getByte( instance + offset + data_offset ); + } + + public byte[] getBytes( Database db, long instance, long data_offset, int len ) throws CoreException + { + byte[] data = new byte[len]; + db.getBytes( instance + offset + data_offset, data ); + return data; + } + + public void put( Database db, long instance, long data_offset, int value ) throws CoreException + { + db.putInt( instance + offset + data_offset, value ); + } + + public int getInt( Database db, long instance, long data_offset ) throws CoreException + { + return db.getInt( instance + offset + data_offset ); + } + } + + public PDOMTag( Database db, long record ) + { + this.db = db; + this.record = record; + } + + public PDOMTag( Database db, int dataLen ) throws CoreException + { + this.db = db; + this.record = db.malloc( Fields.sizeof( dataLen ) ); + this.dataLen = dataLen; + Fields.DataLen.put( db, record, 0, dataLen ); + } + + public long getNode() throws CoreException + { + return Fields.Node.getRecPtr( db, record, 0 ); + } + + @Override + public String getTaggerId() + { + if( taggerId == null ) + try + { + long taggerIdRecord = Fields.TaggerId.getRecPtr( db, record, 0 ); + taggerId = taggerIdRecord == 0L ? new String() : db.getString( taggerIdRecord ).getString(); + } + catch( CoreException e ) + { + CCorePlugin.log( e ); + } + + return taggerId; + } + + @Override + public int getDataLen() + { + if( dataLen < 0 ) + try { dataLen = Fields.DataLen.getInt( db, record, 0 ); } + catch( CoreException e ) { CCorePlugin.log( e ); return 0; } + + return dataLen; + } + + public long getRecord() { return record; } + + /** + * Create and return a new PDOMTag that has the same node/taggerId as the receiver but with the + * specified data. Return null on failure. + */ + public PDOMTag cloneWith( byte[] data ) throws CoreException + { + PDOMTag partialTag = null; + try + { + long existing_node = Fields.Node.getRecPtr( db, record, 0 ); + long existing_id = Fields.TaggerId.getRecPtr( db, record, 0 ); + + partialTag = new PDOMTag( db, data.length ); + Fields.Node.putRecPtr( db, partialTag.record, 0, existing_node ); + Fields.TaggerId.putRecPtr( db, partialTag.record, 0, existing_id ); + if( partialTag.putBytes( 0, data, data.length ) ) + { + PDOMTag tag = partialTag; + partialTag = null; + return tag; + } + } + finally + { + if( partialTag != null ) + partialTag.delete(); + } + + return null; + } + + public void delete() + { + if( db != null + && record != 0 ) + try { db.free( record ); } + catch( CoreException e ) { CCorePlugin.log( e ); } + } + + public static class BTreeComparator implements IBTreeComparator + { + private final Database db; + public BTreeComparator( Database db ) { this.db = db; } + + @Override + public int compare(long record1, long record2) throws CoreException + { + if( record1 == record2 ) + return 0; + + long node1 = Fields.Node.getRecPtr( db, record1, 0 ); + long node2 = Fields.Node.getRecPtr( db, record2, 0 ); + if( node1 < node2 ) + return -1; + if( node1 > node2 ) + return 1; + + long tagger1 = Fields.TaggerId.getRecPtr( db, record1, 0 ); + long tagger2 = Fields.TaggerId.getRecPtr( db, record2, 0 ); + if( tagger1 < tagger2 ) + return -1; + if( tagger1 > tagger2 ) + return 1; + + return 0; + } + } + + public static class BTreeVisitor implements IBTreeVisitor + { + private final Database db; + private final long node2; + private final long tagger2; + + public boolean hasResult = false; + public long tagRecord = 0; + + public BTreeVisitor( Database db, long node2, long tagger2 ) + { + this.db = db; + this.node2 = node2; + this.tagger2 = tagger2; + } + + @Override + public int compare(long record1) throws CoreException { + long node1 = Fields.Node.getRecPtr( db, record1, 0 ); + if( node1 < node2 ) + return -1; + if( node1 > node2 ) + return 1; + + long tagger1 = Fields.TaggerId.getRecPtr( db, record1, 0 ); + if( tagger1 < tagger2 ) + return -1; + if( tagger1 > tagger2 ) + return 1; + + return 0; + } + + @Override + public boolean visit(long record) throws CoreException { + tagRecord = record; + hasResult = true; + return false; + } + } + + public void setNode( long node ) throws CoreException + { + Fields.Node.putRecPtr( db, record, 0, node ); + } + + public void setTaggerId( long idRecord ) throws CoreException + { + Fields.TaggerId.putRecPtr( db, record, 0, idRecord ); + } + + private boolean isInBounds( int offset, int len ) + { + int data_len = getDataLen(); + return offset >= 0 + && offset < data_len + && ( offset + len ) <= data_len; + } + + @Override + public boolean putByte( int offset, byte data ) + { + if( ! isInBounds( offset, 1 ) ) + return false; + + try { Fields.Data.put( db, record, offset, data ); return true; } + catch( CoreException e ) { CCorePlugin.log( e ); return false; } + } + + @Override + public boolean putBytes( int offset, byte[] data, int len ) + { + boolean fullWrite = len < 0; + if( fullWrite ) + len = data.length; + if( ! isInBounds( offset, len ) ) + return false; + + try + { + Fields.Data.put( db, record, data, offset, len ); + + // if the new buffer replaces all of the existing one, then modify the receiver's stored length + int currLen = getDataLen(); + if( fullWrite + && offset == 0 + && currLen > len ) + { + Fields.DataLen.put( db, record, 0, len ); + dataLen = len; + } + + return true; + } + catch( CoreException e ) { CCorePlugin.log( e ); return false; } + } + + @Override + public int getByte( int offset ) + { + if( ! isInBounds( offset, 1 ) ) + return Fail; + + try { return Fields.Data.getByte( db, record, offset ); } + catch( CoreException e ) { CCorePlugin.log( e ); return Fail; } + } + + @Override + public byte[] getBytes( int offset, int len ) + { + len = len >= 0 ? len : getDataLen() - offset; + if( ! isInBounds( offset, len ) ) + return null; + + try { return Fields.Data.getBytes( db, record, offset, len ); } + catch( CoreException e ) { CCorePlugin.log( e ); return null; } + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagIndex.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagIndex.java new file mode 100644 index 00000000000..8cd0c86c9f9 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagIndex.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.tag; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; +import org.eclipse.cdt.internal.core.pdom.PDOM; +import org.eclipse.cdt.internal.core.pdom.db.BTree; +import org.eclipse.cdt.internal.core.pdom.db.Database; +import org.eclipse.cdt.internal.core.pdom.db.PDOMStringSet; +import org.eclipse.core.runtime.CoreException; + +/** + * Not thread-safe. + */ +public class PDOMTagIndex +{ + private final Database db; + private final long ptr; + private long rootRecord; + + private static enum Fields + { + TaggerIds, + Tags, + _last; + + public final long offset = ordinal() * Database.PTR_SIZE; + public static int sizeof = _last.ordinal() * Database.PTR_SIZE; + } + + private PDOMStringSet taggerIds; + private BTree tags; + + public PDOMTagIndex( Database db, long ptr ) throws CoreException + { + this.db = db; + this.ptr = ptr; + this.rootRecord = 0; + } + + private long getFieldAddress( Fields field ) throws CoreException + { + if( rootRecord == 0 ) + rootRecord = db.getRecPtr( ptr ); + + if( rootRecord == 0 ) + { + rootRecord = db.malloc( Fields.sizeof ); + db.putRecPtr( ptr, rootRecord ); + } + + return rootRecord + field.offset; + } + + private PDOMStringSet getTaggerIds() throws CoreException + { + if( taggerIds == null ) + taggerIds = new PDOMStringSet( db, getFieldAddress( Fields.TaggerIds ) ); + return taggerIds; + } + + private BTree getTagsBTree() throws CoreException + { + if( tags == null ) + tags = new BTree( db, getFieldAddress( Fields.Tags ), new PDOMTag.BTreeComparator( db ) ); + return tags; + } + + /** + * Return the record storing the specified tagger id. Create a new record if needed. + */ + private long getIdRecord( String taggerId, boolean createIfNeeded ) + { + assert taggerId != null; + assert ! taggerId.isEmpty(); + + if( db == null + || taggerId == null + || taggerId.isEmpty() + || ( taggerIds == null && ! createIfNeeded ) ) + return 0L; + + try + { + long record = getTaggerIds().find( taggerId ); + if( record == 0 + && createIfNeeded ) + record = getTaggerIds().add( taggerId ); + return record; + } + catch( CoreException e ) + { + CCorePlugin.log( e ); + } + + return 0L; + } + + private IWritableTag createTag( long record, String id, int len ) + { + if( db == null ) + return null; + + long idRecord = getIdRecord( id, true ); + if( idRecord == 0L ) + return null; + + try + { + PDOMTag tag = new PDOMTag( db, len ); + tag.setNode( record ); + tag.setTaggerId( idRecord ); + + // return the tag if it was properly inserted + long inserted = getTagsBTree().insert( tag.getRecord() ); + if( inserted == tag.getRecord() ) + return tag; + + // TODO check that the existing record has the same length + + // otherwise destroy this provisional one and return the tag that was actually inserted + // TODO figure out what this case means + tag.delete(); + return inserted == 0 ? null : new PDOMTag( db, inserted ); + } + catch( CoreException e ) + { + CCorePlugin.log( e ); + } + + return null; + } + + private ITag getTag( long record, String id ) + { + if( db == null ) + return null; + + long idRecord = getIdRecord( id, false ); + if( idRecord == 0L ) + return null; + + PDOMTag.BTreeVisitor v = new PDOMTag.BTreeVisitor( db, record, idRecord ); + try { getTagsBTree().accept( v ); } + catch( CoreException e ) { CCorePlugin.log( e ); } + + return v.hasResult ? new PDOMTag( db, v.tagRecord ) : null; + } + + private Iterable getTags( long binding_record ) + { + BTree btree = null; + try { btree = getTagsBTree(); } + catch( CoreException e ) { CCorePlugin.log( e ); return Collections.emptyList(); } + + final Long bindingRecord = Long.valueOf( binding_record ); + return + new BTreeIterable( + btree, + new BTreeIterable.Descriptor() + { + @Override public ITag create( long record ) { return new PDOMTag( db, record ); } + @Override + public int compare( long test_record ) throws CoreException + { + long test_node = new PDOMTag( db, test_record ).getNode(); + + // -1 if record < key, 0 if record == key, 1 if record > key + return Long.valueOf( test_node ).compareTo( bindingRecord ); + } + } ); + } + + private boolean setTags( long binding_record, Iterable tags ) + { + // There could be several tags for the given record in the database, one for each taggerId. We need + // to delete all of those tags and replace them with given list. The incoming tags are first put + // into a map, indexed by their taggerId. Then we examine the btree of tags to find all tags for this + // record. In each case we decide whether to delete or update the tag. Tags of the same size can be + // updated in place, otherwise the tag needs to be deleted and recreated. + + final Map newTags = new HashMap(); + for( ITag tag : tags ) + { + ITag dupTag = newTags.put( tag.getTaggerId(), tag ); + if( dupTag != null ) + CCorePlugin.log( "Duplicate incoming tag for record " + binding_record + " from taggerId " + tag.getTaggerId() ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + BTree btree = null; + try { btree = getTagsBTree(); } + catch( CoreException e ) { CCorePlugin.log( e ); return false; } + + PDOMTagSynchronizer sync = new PDOMTagSynchronizer( db, Long.valueOf( binding_record ), newTags ); + + // visit the full tree, then return true on success and false on failure + try { btree.accept( sync ); } + catch( CoreException e ) { CCorePlugin.log( e ); return false; } + + // Complete the synchronization (delete/insert the records that could not be modified in-place). This + // will only have something to do when a tag has changed length, which should be a rare. + sync.synchronize( btree ); + + // insert any new tags that are left in the incoming list + for( ITag newTag : newTags.values() ) + { + IWritableTag pdomTag = createTag( binding_record, newTag.getTaggerId(), newTag.getDataLen() ); + pdomTag.putBytes( 0, newTag.getBytes( 0, -1 ), -1 ); + } + + return true; + } + + private static PDOMTagIndex getTagIndex( PDOM pdom ) + { + if( pdom == null ) + return null; + + try + { + PDOMTagIndex index = pdom.getTagIndex(); + return index.db == null ? null : index; + } + catch( CoreException e ) { CCorePlugin.log(e); } + return null; + } + + // common implementations + public static IWritableTag createTag( PDOM pdom, long record, String id, int len ) + { + PDOMTagIndex index = getTagIndex( pdom ); + if( index == null ) + return null; + + return index.createTag( record, id, len ); + } + + public static ITag getTag( PDOM pdom, long record, String id ) + { + PDOMTagIndex index = getTagIndex( pdom ); + if( index == null ) + return null; + + return index.getTag( record, id ); + } + + public static Iterable getTags( PDOM pdom, long record ) + { + PDOMTagIndex index = getTagIndex( pdom ); + if( index == null ) + return Collections.emptyList(); + + return index.getTags( record ); + } + + public static boolean setTags( PDOM pdom, long record, Iterable tags ) + { + if( record == 0 ) + return true; + + PDOMTagIndex index = getTagIndex( pdom ); + if( index == null ) + return false; + + return index.setTags( record, tags ); + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagSynchronizer.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagSynchronizer.java new file mode 100644 index 00000000000..aca9c277272 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTagSynchronizer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.tag; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.internal.core.pdom.db.BTree; +import org.eclipse.cdt.internal.core.pdom.db.Database; +import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor; +import org.eclipse.core.runtime.CoreException; + +public class PDOMTagSynchronizer implements IBTreeVisitor +{ + private final Database db; + private final Long searchRecord; + private final Map newTags; + + private final List toRemove = new LinkedList(); + private final List toInsert = new LinkedList(); + + public PDOMTagSynchronizer( Database db, Long searchRecord, Map newTags ) + { + this.db = db; + this.searchRecord = searchRecord; + this.newTags = newTags; + } + + /** + * Complete the synchronization by deleting and inserting all required records. Return + * true if successful and false otherwise. + */ + public boolean synchronize( BTree tree ) + { + for( Long rm : toRemove ) + try + { + long record = rm.longValue(); + tree.delete( record ); + db.free( record ); + } + catch( CoreException e ) + { + CCorePlugin.log( e ); + } + toRemove.clear(); + + for( Long insert : toInsert ) + try { tree.insert( insert.longValue() ); } + catch( CoreException e ) + { + CCorePlugin.log( e ); + try { db.free( insert.longValue() ); } + catch( CoreException e1 ) { CCorePlugin.log( e1 ); } + } + toInsert.clear(); + + return true; + } + + @Override + public int compare( long test_record ) throws CoreException + { + // TODO this is the same as BTreeIterable.Descriptor.compare + + long test_node = new PDOMTag( db, test_record ).getNode(); + + // -1 if record < key, 0 if record == key, 1 if record > key + return Long.valueOf( test_node ).compareTo( searchRecord ); + } + + @Override + public boolean visit( long existing_record ) throws CoreException + { + PDOMTag existingTag = new PDOMTag( db, existing_record ); + String taggerId = existingTag.getTaggerId(); + + ITag newTag = newTags.remove( taggerId ); + if( newTag == null ) + toRemove.add( Long.valueOf( existing_record ) ); + else if( newTag.getDataLen() > existingTag.getDataLen() ) + { + toRemove.add( Long.valueOf( existing_record ) ); + + PDOMTag pdomTag = existingTag.cloneWith( newTag.getBytes( 0, -1 ) ); + if( pdomTag != null ) + toInsert.add( Long.valueOf( pdomTag.getRecord() ) ); + } + else if( ! existingTag.putBytes( 0, newTag.getBytes( 0, -1 ), -1 ) ) + CCorePlugin.log( "Unable to modify data of tag record " + existing_record + " from taggerId " + taggerId ); //$NON-NLS-1$ //$NON-NLS-2$ + + // try to visit the full tree + return true; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTaggable.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTaggable.java new file mode 100644 index 00000000000..e7a04c839ed --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/tag/PDOMTaggable.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 QNX Software Systems and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.cdt.internal.core.pdom.tag; + +import org.eclipse.cdt.core.dom.ast.tag.ITag; +import org.eclipse.cdt.core.dom.ast.tag.ITagReader; +import org.eclipse.cdt.core.dom.ast.tag.ITagWriter; +import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; +import org.eclipse.cdt.internal.core.pdom.PDOM; + +public class PDOMTaggable implements ITagReader, ITagWriter +{ + private final PDOM pdom; + private final long record; + + public PDOMTaggable( PDOM pdom, long record ) + { + this.pdom = pdom; + this.record = record; + } + + @Override + public IWritableTag createTag( String id, int len ) + { + return PDOMTagIndex.createTag( pdom, record, id, len ); + } + + @Override + public ITag getTag( String id ) + { + return PDOMTagIndex.getTag( pdom, record, id ); + } + + @Override + public Iterable getTags() + { + return PDOMTagIndex.getTags( pdom, record ); + } + + @Override + public boolean setTags( Iterable tags ) + { + return PDOMTagIndex.setTags( pdom, record, tags ); + } +} diff --git a/core/org.eclipse.cdt.core/plugin.properties b/core/org.eclipse.cdt.core/plugin.properties index 46e1547c2f1..287ee250127 100644 --- a/core/org.eclipse.cdt.core/plugin.properties +++ b/core/org.eclipse.cdt.core/plugin.properties @@ -121,6 +121,7 @@ CConfigurationDataProvider.name = CConfigurationData provider projectConverter.name = project converter CIndex.name = CIndex externalSettingsProvider.name = External Settings provider +tagger.name = Parser Node Tagger Extension Point GeneratePDOMApplication.name = GeneratePDOM defaultProvider.name = Default Provider templatesExtensionPoint.name = Templates Extension point diff --git a/core/org.eclipse.cdt.core/plugin.xml b/core/org.eclipse.cdt.core/plugin.xml index bb1125caabd..4b2e257ed6a 100644 --- a/core/org.eclipse.cdt.core/plugin.xml +++ b/core/org.eclipse.cdt.core/plugin.xml @@ -42,6 +42,7 @@ + diff --git a/core/org.eclipse.cdt.core/schema/tagger.exsd b/core/org.eclipse.cdt.core/schema/tagger.exsd new file mode 100644 index 00000000000..98d37683785 --- /dev/null +++ b/core/org.eclipse.cdt.core/schema/tagger.exsd @@ -0,0 +1,139 @@ + + + + + + + + + This extension point allows extensions to contribute to the parsed nodes. When PDOM nodes are tagged, the content of the tag is stored in to the Database. The information for other nodes, e.g., the AST, is stored in memory. + + + + + + + + + + + + + + + + + + + a fully qualified identifier of the target extension point + + + + + + + an optional identifier of the extension instance + + + + + + + an optional name of the extension instance + + + + + + + + + + + + + + + + + + + + + + + + + + + A unique identifier for this tagger's contributions. + +The local id will be appended to the contributing plugin's id (separated with a dot '.') to form the globally unique identifier for this tagger. This id is used to uniquely associate the tag with this plugin. + + + + + + + + + + + + 8.2 + + + + + + + + + The following is an example of a Tagger contribution: +<p> +<pre> +<extension + point="org.eclipse.cdt.core.tagger" + id="example" + name="Example Tagger Extension"> + <bindingTagger + local-id="my-tagger" + class="com.example.internal.ExampleTagger"> + <enablement> + <with variable="projectNatures"> + <iterate operator="or"> + <equals value="com.example.my-nature"/> + </iterate> + </with> + </enablement> + </bindingTagger> +</extension> +</pre> +</p> + + + + + + + + + The contributed class must implement <code>org.eclipse.cdt.core.dom.ast.tag.IBindingTagger</code> + + + + + + + + + + Copyright (c) 2013 QNX Software Systems and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/epl-v10.html + + + + diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java index 22a22903a2e..b5fc7a7384f 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java @@ -26,6 +26,7 @@ import java.util.ResourceBundle; import org.eclipse.cdt.core.cdtvariables.ICdtVariableManager; import org.eclipse.cdt.core.cdtvariables.IUserVarSupplier; import org.eclipse.cdt.core.dom.IPDOMManager; +import org.eclipse.cdt.core.dom.ast.tag.ITagService; import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager; import org.eclipse.cdt.core.index.IIndexManager; import org.eclipse.cdt.core.language.settings.providers.ScannerDiscoveryLegacySupport; @@ -50,6 +51,7 @@ import org.eclipse.cdt.internal.core.ICConsole; import org.eclipse.cdt.internal.core.PositionTrackerManager; import org.eclipse.cdt.internal.core.cdtvariables.CdtVariableManager; import org.eclipse.cdt.internal.core.cdtvariables.UserVarSupplier; +import org.eclipse.cdt.internal.core.dom.ast.tag.TagService; import org.eclipse.cdt.internal.core.envvar.EnvironmentVariableManager; import org.eclipse.cdt.internal.core.language.settings.providers.LanguageSettingsScannerInfoProvider; import org.eclipse.cdt.internal.core.model.CModelManager; @@ -214,6 +216,8 @@ public class CCorePlugin extends Plugin { private PDOMManager pdomManager; + private ITagService tagService = new TagService(); + private CdtVarPathEntryVariableManager fPathEntryVariableManager; private final class NullConsole implements IConsole { @@ -713,6 +717,13 @@ public class CCorePlugin extends Plugin { return getDefault().pdomManager; } + /** + * @since 5.5 + */ + public static ITagService getTagService() { + return getDefault().tagService; + } + public IPathEntryVariableManager getPathEntryVariableManager() { return fPathEntryVariableManager; } -- cgit v1.2.3