fixed excessive VFS load and assertion added in tests for accessing VFS outside the project roots

This commit is contained in:
Alexey Kudravtsev
2011-05-11 16:26:20 +04:00
parent 8b2a300ab0
commit 92e1e91560
6 changed files with 193 additions and 25 deletions
@@ -1,6 +1,7 @@
package com.intellij.openapi.vfs;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.impl.win32.Win32LocalFileSystem;
@@ -19,6 +20,22 @@ import java.util.Arrays;
public class LocalFileSystemTest extends IdeaTestCase{
private static final String KEY = "filesystem.useNative";
public static void setContentOnDisk(File file, byte[] bom, String content, Charset charset) throws IOException {
FileOutputStream stream = new FileOutputStream(file);
stream.write(bom);
OutputStreamWriter writer = new OutputStreamWriter(stream, charset);
writer.write(content);
writer.close();
}
public static VirtualFile createTempFile(@NonNls String ext, byte[] bom, @NonNls String content, Charset charset) throws IOException {
File temp = FileUtil.createTempFile("copy", "." + ext);
setContentOnDisk(temp, bom, content, charset);
myFilesToDelete.add(temp);
return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(temp);
}
public void testChildrenAccessedButNotCached() throws Exception{
ApplicationManager.getApplication().runWriteAction(
new Runnable() {
@@ -116,7 +133,7 @@ public class LocalFileSystemTest extends IdeaTestCase{
new Runnable() {
@Override
public void run() {
try{
try {
File fromDir = createTempDirectory();
File toDir = createTempDirectory();
@@ -132,7 +149,7 @@ public class LocalFileSystemTest extends IdeaTestCase{
assertEquals(newName, copy.getName());
assertTrue(Arrays.equals(byteContent, copy.contentsToByteArray()));
}
catch(Exception e){
catch (Exception e) {
LOG.error(e);
}
}
@@ -222,22 +239,22 @@ public class LocalFileSystemTest extends IdeaTestCase{
}
}
);
}
public static void setContentOnDisk(File file, byte[] bom, String content, Charset charset) throws IOException {
FileOutputStream stream = new FileOutputStream(file);
stream.write(bom);
OutputStreamWriter writer = new OutputStreamWriter(stream, charset);
writer.write(content);
writer.close();
}
public void testFindRoot() {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath("wrong_path");
assertNull(file);
public static VirtualFile createTempFile(@NonNls String ext, byte[] bom, @NonNls String content, Charset charset) throws IOException {
File temp = FileUtil.createTempFile("copy", "." + ext);
setContentOnDisk(temp, bom, content, charset);
if (SystemInfo.isWindows && new File("c:").exists()) {
VirtualFile root = LocalFileSystem.getInstance().findFileByPath("c:");
assertNotNull(root);
}
if (SystemInfo.isUnix) {
VirtualFile root = LocalFileSystem.getInstance().findFileByPath("/");
assertNotNull(root);
}
myFilesToDelete.add(temp);
return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(temp);
VirtualFile root = LocalFileSystem.getInstance().findFileByPath("");
assertNotNull(root);
}
}
@@ -30,6 +30,8 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.source.resolve.ResolveCache;
@@ -193,21 +195,37 @@ public class FileReference implements FileReferenceOwner, PsiPolyVariantReferenc
else {
final String decoded = decode(text);
if (decoded != null) {
processVariants(context, new PsiFileSystemItemProcessor() {
public boolean acceptItem(String name, boolean isDirectory) {
return caseSensitive ? decoded.equals(name) : decoded.compareToIgnoreCase(name) == 0;
if (context instanceof PsiDirectory && caseSensitivityApplies((PsiDirectory)context, caseSensitive)) {
// optimization: do not load all children into VFS
PsiDirectory directory = (PsiDirectory)context;
PsiFileSystemItem child = directory.findFile(decoded);
if (child == null) child = directory.findSubdirectory(decoded);
if (child != null) {
result.add(new PsiElementResolveResult(getOriginalFile(child)));
}
}
else {
processVariants(context, new PsiFileSystemItemProcessor() {
public boolean acceptItem(String name, boolean isDirectory) {
return caseSensitive ? decoded.equals(name) : decoded.compareToIgnoreCase(name) == 0;
}
public boolean execute(PsiFileSystemItem element) {
result.add(new PsiElementResolveResult(getOriginalFile(element)));
return true;
}
});
public boolean execute(PsiFileSystemItem element) {
result.add(new PsiElementResolveResult(getOriginalFile(element)));
return true;
}
});
}
}
}
}
}
private static boolean caseSensitivityApplies(PsiDirectory context, boolean caseSensitive) {
VirtualFileSystem fs = context.getVirtualFile().getFileSystem();
return fs instanceof NewVirtualFileSystem && ((NewVirtualFileSystem)fs).isCaseSensitive() == caseSensitive;
}
private boolean isAllowedEmptyPath(String text) {
return text.length() == 0 && isLast() &&
(StringUtil.isEmpty(myFileReferenceSet.getPathString()) && myFileReferenceSet.isEmptyPathAllowed() ||
@@ -139,7 +139,8 @@ public class IdeaGateway {
public static Iterable<VirtualFile> iterateDBChildren(VirtualFile f) {
if (!(f instanceof NewVirtualFile)) return ContainerUtil.emptyIterable();
return ((NewVirtualFile)f).iterInDbChildren();
NewVirtualFile nf = (NewVirtualFile)f;
return nf.getCachedChildren();
}
public RootEntry createTransientRootEntry() {
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
@@ -126,7 +127,7 @@ public abstract class LocalFileSystemBase extends LocalFileSystem {
}
public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
if (fileOrDirectory.getParent() == null) return true;
if (StringUtil.isEmpty(fileOrDirectory.getPath())) return true; // fake top dir for Windows
return convertToIOFile(fileOrDirectory).exists();
}
@@ -19,8 +19,25 @@
*/
package com.intellij.openapi.vfs.newvfs.impl;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
@@ -28,6 +45,8 @@ import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.THashMap;
@@ -36,14 +55,18 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
private static final VirtualFileSystemEntry NULL_VIRTUAL_FILE = new VirtualFileImpl("*?;%NULL", null, -42);
private final NewVirtualFileSystem myFS;
private static final boolean IS_UNIT_TESTS = ApplicationManager.getApplication().isUnitTestMode();
// guarded by this
private Object myChildren; // Either HashMap<String, VFile> or VFile[]
@@ -141,6 +164,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
else {
child = new VirtualFileImpl(name, this, id);
assertAccessInTests(child);
}
if (fs.markNewFilesAsDirty()) {
@@ -150,6 +174,107 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
return child;
}
private static void assertAccessInTests(VirtualFileSystemEntry child) {
if (IS_UNIT_TESTS && ApplicationManager.getApplication() instanceof ApplicationImpl && ((ApplicationImpl)ApplicationManager.getApplication()).isComponentsCreated()) {
NewVirtualFileSystem fileSystem = child.getFileSystem();
if (fileSystem != LocalFileSystem.getInstance() && fileSystem != JarFileSystem.getInstance()) {
return;
}
// root' children are loaded always
if (child.getParent() == null || child.getParent().getParent() == null) return;
Set<String> allowed = allowedRoots();
boolean isUnder = allowed == null;
if (!isUnder) {
for (String root : allowed) {
String childPath = child.getPath();
if (child.getFileSystem() == JarFileSystem.getInstance()) {
VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child);
childPath = local.getPath();
}
if (FileUtil.startsWith(childPath, root)) {
isUnder = true;
break;
}
if (root.startsWith(JarFileSystem.PROTOCOL_PREFIX)) {
String rootLocalPath = FileUtil.toSystemIndependentName(PathUtil.toPresentableUrl(root));
isUnder = FileUtil.startsWith(childPath, rootLocalPath);
if (isUnder) break;
}
}
}
if (!isUnder) {
if (!allowed.isEmpty()) {
assert false : "File accessed outside project: " + child +"; project roots: "+new ArrayList(allowed);
}
}
}
}
// null means we were unable to get roots, so do not check access
private static Set<String> allowedRoots() {
if (insideGettingRoots) return null;
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
if (openProjects.length == 0) return null;
Set<String> allowed = new THashSet<String>();
String homePath = PathManager.getHomePath();
allowed.add(FileUtil.toSystemIndependentName(homePath));
try {
URL outUrl = Application.class.getResource("/");
String output = new File(outUrl.toURI()).getParentFile().getParentFile().getPath();
allowed.add(FileUtil.toSystemIndependentName(output));
}
catch (URISyntaxException ignored) {
}
String javaHome = SystemProperties.getJavaHome();
allowed.add(FileUtil.toSystemIndependentName(javaHome));
String tempDirectorySpecific = new File(FileUtil.getTempDirectory()).getParent();
allowed.add(FileUtil.toSystemIndependentName(tempDirectorySpecific));
String tempDirectory = System.getProperty("java.io.tmpdir");
allowed.add(FileUtil.toSystemIndependentName(tempDirectory));
String home = SystemProperties.getUserHome();
allowed.add(FileUtil.toSystemIndependentName(home));
for (Project project : openProjects) {
if (!project.isInitialized()) {
return null; // all is allowed
}
for (VirtualFile root : ProjectRootManager.getInstance(project).getContentRoots()) {
allowed.add(root.getPath());
}
for (VirtualFile root : getAllRoots(project)) {
allowed.add(StringUtil.trimEnd(root.getPath(), JarFileSystem.JAR_SEPARATOR));
}
String location = project.getLocation();
allowed.add(FileUtil.toSystemIndependentName(location));
}
return allowed;
}
private static boolean insideGettingRoots;
private static VirtualFile[] getAllRoots(Project project) {
insideGettingRoots = true;
Set<VirtualFile> roots = new THashSet<VirtualFile>();
final Module[] modules = ModuleManager.getInstance(project).getModules();
for (Module module : modules) {
final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
final OrderEntry[] orderEntries = moduleRootManager.getOrderEntries();
for (OrderEntry entry : orderEntries) {
VirtualFile[] files;
files = entry.getFiles(OrderRootType.CLASSES);
ContainerUtil.addAll(roots, files);
files = entry.getFiles(OrderRootType.SOURCES);
ContainerUtil.addAll(roots, files);
files = entry.getFiles(OrderRootType.CLASSES_AND_OUTPUT);
ContainerUtil.addAll(roots, files);
}
}
insideGettingRoots = false;
return VfsUtil.toVirtualFileArray(roots);
}
@Nullable
private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name) {
final NewVirtualFileSystem delegate = getFileSystem();
@@ -688,6 +688,12 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
public VirtualFile[] getChildren() {
return getRoots(fs);
}
@Override
public VirtualFileSystemEntry findChild(@NotNull String name) {
if (name.length() == 0) return null;
return findRoot(name, fs);
}
};
}
if (!fs.exists(root)) return null;