Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix several problems with symbol protection. #153

Closed
wants to merge 2 commits into from

Conversation

Hackerpilot
Copy link
Collaborator

There were several problems with protection being set on symbols. An example of this is dlang-community/DCD#620.

This adds a few extra asserts, and prevents a lot of cases of symbols being created without any protection being set.

@Hackerpilot Hackerpilot requested a review from WebFreak001 July 23, 2020 00:21
Copy link
Member

@WebFreak001 WebFreak001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this absolutely needs a lot of tests before I would approve this

@@ -1106,7 +1113,7 @@ private:
}

SemanticSymbol* allocateSemanticSymbol(string name, CompletionKind kind,
istring symbolFile, size_t location = 0)
istring symbolFile, size_t location = 0, IdType protection = tok!"public")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a default value is a bad idea. I would revert this definition, make it deprecated and add another overload with non-optional protection argument. This way we don't get issues with people assuming no protection and also simplify a lot of caller code which currently manually sets acSymbol.protection which isn't covered by the PR yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few points to keep in mind:

  • I think that this default is a better one than the previous default of tok!"", which is much less valid than public. public is the default for class members, and I'm pretty sure that it's the default for other things as well, but it's not actually written down in the language spec.
  • allocateSemanticSymbol and pushSymbol are both private, so we're not really breaking an API.

I do plan on doing another pass over this code to make more use of this extra argument. In general this module needs a lot of cleanup and documentation.

@@ -848,7 +856,7 @@ private:
}

void pushSymbol(string name, CompletionKind kind, istring symbolFile,
size_t location = 0, const Type type = null)
size_t location = 0, const Type type = null, const IdType protection = tok!"public")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this protection argument isn't used

also ditto from previous comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

@WebFreak001
Copy link
Member

let's try to make this file 100% covered with tests

@WebFreak001
Copy link
Member

I made the following addition in tests.d for a small testing solution:

/**
 * Parses `source`, caches its symbols and compares the the cache content
 * with `expected`.
 *
 * Params:
 *      source = The source code to test.
 *      expected = An array of [variable name, protection]
 */
void expectSymbolsAndProtection(const string source, const string[2][] expected,
	string file = __FILE_FULL_PATH__, size_t line = __LINE__)
{
	import core.exception : AssertError;
	import std.array : appender;
	import std.exception : enforce;

	ModuleCache mcache = createTestModuleCache();
	auto pair = generateAutocompleteTrees(source, mcache);
	scope(exit) pair.destroy();

	string[2][] actual;

	void visit(DSymbol* symbol)
	{
		// filter no protection
		// keywords (.init, .min, .max)
		// class __vptr, __monitor, etc
		// remove empty symbol names (root symbol)
		if (symbol.protection
			&& symbol.kind != CompletionKind.keyword
			&& !symbol.name.data.startsWith("__")
			&& symbol.name.data != "classinfo"
			&& symbol.name.data.length)
		{
			actual ~= [symbol.name.data, str(symbol.protection)];
		}

		foreach(DSymbol* ss; (*symbol)[])
			visit(ss);
	}

	visit(pair.symbol);

	enforce!AssertError(actual == expected,
		"expected symbols\n[\n\t%(%s\n\t%)\n] but got\n[\n\t%(%s\n\t%)\n]".format(expected, actual),
		file, line);
}

with a test

@("exhaustive protection tests")
unittest
{
	q{
		import object;
		class DefaultFoo {}
		public class PublicFoo {}
		private struct PrivateStruct
		{
			T memberMethod(T, alias aliasArg, int intArg, VA...)(short runtimeArg, VA vaarg)
			{
				return T.init + runtimeArg * vaarg;
			}
		}
		package { enum PackageEnum { enumValue } }
		int publicInt;
		private int privateInt;
		void publicMain() {
			int localVariable;
		}
	}.expectSymbolsAndProtection([
		["DefaultFoo", "public"]
		["PublicFoo", "public"]
		["PrivateStruct", "private"] // <-- this is failing
		["*constructor*", "public"]
		["memberMethod", "public"]
		["T", "public"] // should these symbols have protection?
		["aliasArg", "public"] // ditto
		["intArg", "public"] // ditto
		["VA", "public"] // ditto
		["runtimeArg", "public"] // ditto
		["vaarg", "public"] // ditto
		["PackageEnum", "package"] // <-- this is failing
		["enumValue", "public"]
		["publicInt", "public"]
		["privateInt", "private"]
		["publicMain", "public"]
		["localVariable", "public"], // should this symbol have protection?
	]);
}

@Hackerpilot
Copy link
Collaborator Author

I think that I finally reduced a test case for the root cause of the original DCD issue:

struct Indexed
{
    static if (true)
    {
        unittest
        {
        }
    }
private:
}

int indexed()
{
    return 0;
}

This combination of static if, unittest, and private: causes indexed to be marked private.

@Hackerpilot
Copy link
Collaborator Author

All right. I found it. The problem is that SimpleParser.parseUnittest() always returns null. A lot of the parser code assumes that returning null is an error condition and so the error recovery kicks in and the parser jumps ahead and assumes that when it hits the closing braces of the static if, that it's done with the struct declaration. It then puts the private: at module scope and then errors again when it hits the closing brace on the next line.

@Hackerpilot
Copy link
Collaborator Author

Hackerpilot commented Jul 25, 2020

I suspect that SimpleParser.parseMissingFunctionBody is wrong as well.

Edit: It is

struct Indexed
{
    static if (true)
    {
        void doStuff() do {}
    }
private:
}
int indexed;

This causes indexed to be marked private as well.

@Hackerpilot
Copy link
Collaborator Author

I still do want to do this rework of the symbol creation, but the actual fix for the the DCD issue will come in a separate pull request that fixes the parser contained in dsymbol/conversion/package.d

@WebFreak001
Copy link
Member

moved to dlang-community/DCD#679

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants