Aul: Allow the user to selectively enable/disable warnings

This commit introduces a new Aul directive "#warning", which can be used
to enable or disable warnings for a particular piece of code.

"#warning enable" enables all warnings.
"#warning disable" disables all warnings.
"#warning enable empty_parameter_in_call" selectively enables one
specific warning while not affecting any other.

All warnings that used to be controlled by Developer.ExtraWarnings
remain disabled by default.
alut-include-path
Nicolas Hake 2017-02-13 14:53:17 +01:00
parent 23bf3c4f0a
commit 30c5bb5f8d
16 changed files with 553 additions and 40 deletions

View File

@ -1062,6 +1062,7 @@ src/script/C4AulParse.cpp
src/script/C4AulParse.h
src/script/C4AulScriptFunc.cpp
src/script/C4AulScriptFunc.h
src/script/C4AulWarnings.h
src/script/C4Effect.cpp
src/script/C4Effect.h
src/script/C4PropList.cpp

View File

@ -516,7 +516,7 @@
<xsl:template name="color2">
<xsl:param name="s" select="." />
<!-- the list of keywords -->
<xsl:param name="t" select="'#include|#appendto|public|private|protected|global|static|var|local|const|any|int|bool|def|effect|object|proplist|string|array|func|return|if|else|break|continue|while|for|true|false|nil|'" />
<xsl:param name="t" select="'#include|#appendto|#warning|public|private|protected|global|static|var|local|const|any|int|bool|def|effect|object|proplist|string|array|func|return|if|else|break|continue|while|for|true|false|nil|'" />
<xsl:param name="w" select="substring-before($t, '|')" />
<!-- text before the keyword -->
<xsl:variable name="l" select="substring-before($s, $w)" />

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE doc
SYSTEM '../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../clonk.xsl"?>
<doc>
<title>Diagnostic Messages</title>
<h>Diagnostic Messages</h>
<part>
<text>
Certain constructs may be flagged by the engine as potentially
unintended or deprecated. In these cases, the engine will, by default,
emit a warning to the log file.
</text>
<text>
On occasion, these constructs are in fact intended by the script
author. In order to avoid unwanted warning messages hiding more important
messages, the engine supports selectively suppressing a warning category
for parts of a script.
</text>
</part>
<part>
<text>
Suppression and re-enablement is handled by the <code>#warning</code>
directive. The directive must be placed on a separate line.
</text>
<text>Warnings can be controlled using this syntax:</text>
<code>#warning {enable|disable} [warning_category [warning_category...]]</code>
<text>
If no category is given, the engine will suppress or enable all messages,
including those that are not enabled by default. A category remains
disabled or enabled until the next directive that affects it, or until
the end of the script. A script linked to via the <code>#include</code>
or <code>#appendto</code> directives does not affect, and is itself not
affected by, the warning settings of the current script.
</text>
<text>
It is not an error to specify a category that does not exist; the
invalid category is simply ignored. No separate warning is emitted.
</text>
</part>
<part>
<h>Warning Categories</h>
<text>The following warning categories currently exist:</text>
<table>
<rowh>
<col>Category</col>
<col>Description</col>
</rowh>
<row>
<col>invalid_escape_sequence</col>
<col>
<text>
The engine found an escape sequence inside a string that it
did not recognize.
</text>
<part><code>"\p"</code></part>
</col>
</row>
<row>
<col>invalid_hex_escape</col>
<col>
<text>
The engine found the start of a hexadecimal escape sequence
inside a string, but no hexadecimal digits followed it.
</text>
<part><code>"\xGN"</code></part>
</col>
</row>
<row>
<col>type_name_used_as_par_name</col>
<col>
<text>
A function parameter was declared without an explicit type
specification, but with a name that is the same as a built-in type.
</text>
<part><code>func f(array)</code></part>
<text>
This warning is not enabled by default.
<a href="#fn1" title="The warning may be enabled by default in a future version.">¹</a>
</text>
</col>
</row>
<row>
<col>empty_parameter_in_call</col>
<col>
<text>
In a function call, a parameter was left empty. The engine is
passing <code>nil</code> in its place.
</text>
<part><code><funclink>CreateObject</funclink>(Clonk,, 30, 100);</code></part>
<text>
This warning is not enabled by default.
<a href="#fn1" title="The warning may be enabled by default in a future version.">¹</a>
</text>
</col>
</row>
<row>
<col>empty_parameter_in_array</col>
<col>
<text>
In an array literal, an entry was left empty. The engine is
using <code>nil</code> in its place.
</text>
<part><code>[1, 2,, 3, 4]</code></part>
<text>
This warning is not enabled by default.
<a href="#fn1" title="The warning may be enabled by default in a future version.">¹</a>
</text>
</col>
</row>
<row>
<col>implicit_range_loop_var_decl</col>
<col>
<text>
The loop variable of a for-in loop was not declared either in the
loop header itself nor in the containing function. This is only
accepted for backwards compatibility and may be removed in a
future release. Explicitly declare the variable by adding the
<code>var</code> keyword.
</text>
<part>
<code>func f() {
&#9;for (i in [1, 2, 3]) {
&#9;}
}</code>
</part>
</col>
</row>
<row>
<col>non_global_var_is_never_const</col>
<col>
<text>
A variable has been declared as <code>const</code>, but is not
global. At this time, non-global variables are always mutable.
</text>
<part>
<code>const local a = {}</code>
</part>
</col>
</row>
<row>
<col>variable_shadows_variable</col>
<col>
<text>
The declaration of a variable uses the same name as a variable
in a greater scope. Changes to the shadowing variable will not
affect the shadowed variable.
</text>
<part>
<code>static foo;
func f() {
&#9;var foo = 3;
}</code>
</part>
</col>
</row>
<row>
<col>redeclaration</col>
<col>
<text>
A variable has been redeclared in the same scope. Make sure
you do not accidentally overwrite values another part of the
code relies upon.
</text>
<part>
<code>func f() {
&#9;var i;
&#9;var i;
}</code>
</part>
</col>
</row>
<row>
<col>undeclared_varargs</col>
<col>
<text>
Use of <code><funclink>Par</funclink></code> inside a function
implicitly declares it as using a variable number of arguments.
This is not immediately obvious to callers of the function, and
should be explicitly declared in the function signature by
adding a final <code>...</code> parameter.
</text>
<part>
<code>func f(a) {
&#9;return <funclink>Par</funclink>(a);
}
// Better:
func g(a, ...) {
&#9;return <funclink>Par</funclink>(a);
}</code>
</part>
</col>
</row>
<row>
<col>arg_count_mismatch</col>
<col>
<text>
A function call passes more parameters than the function will
accept.
</text>
<part>
<code><funclink>GetDir</funclink>(0)</code>
</part>
</col>
</row>
<row>
<col>arg_type_mismatch</col>
<col>
<text>
The parameter given in a function call is of a different type
than the called function expects. The call will likely fail at
runtime.
</text>
<part>
<code><funclink>Sin</funclink>("huh?")</code>
</part>
</col>
</row>
<row>
<col>empty_if</col>
<col>
<text>
An <code>if</code> conditional is controlling an empty statement.
Use the empty block <code>{}</code> if this is intentional, or
remove the conditional entirely.
</text>
<part>
<code>if (true);</code>
</part>
</col>
</row>
</table>
</part>
<part>
<h>Examples</h>
<examples>
<example>
<code>func f(string s) {
&#9;Sin(s);&#x9;// WARNING: parameter 0 of call to 'Sin' passes string (int expected)
#warning disable arg_type_mismatch
&#9;Sin(s);
#warning enable arg_type_mismatch
&#9;Sin(s);&#x9;// WARNING: parameter 0 of call to 'Sin' passes string (int expected)
}</code>
</example>
</examples>
</part>
<a id="fn1" style="font-size: smaller; color: inherit;">¹ The warning may be enabled by default in a future version.</a>
</doc>

View File

@ -47,6 +47,7 @@
<text><emlink href="script/GetXXVal.html">Querying Game Data</emlink></text>
<text><emlink href="script/ScriptPlayers.html">Script Player (i.e. AI player)</emlink></text>
<text><emlink href="script/SoundModifiers.html">Sound modifiers</emlink></text>
<text><emlink href="script/Diagnostics.html">Diagnostic messages</emlink></text>
<h id="Infos">Libraries</h>
<text><emlink href="script/Shape.html">Shape</emlink></text>
</part>

View File

@ -88,7 +88,6 @@ void C4ConfigGeneral::CompileFunc(StdCompiler *pComp)
void C4ConfigDeveloper::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(AutoFileReload, "AutoFileReload", 1 , false, true));
pComp->Value(mkNamingAdapt(ExtraWarnings, "ExtraWarnings", 0 , false, true));
pComp->Value(mkNamingAdapt(s(TodoFilename), "TodoFilename", "{SCENARIO}/TODO.txt", false, true));
pComp->Value(mkNamingAdapt(s(AltTodoFilename), "AltTodoFilename2", "{USERPATH}/TODO.txt", false, true));
pComp->Value(mkNamingAdapt(MaxScriptMRU, "MaxScriptMRU", 30 , false, false));

View File

@ -82,7 +82,6 @@ class C4ConfigDeveloper
{
public:
int32_t AutoFileReload;
int32_t ExtraWarnings;
char TodoFilename[CFG_MaxString + 1];
char AltTodoFilename[CFG_MaxString + 1];
int32_t MaxScriptMRU; // maximum number of remembered elements in recently used scripts

View File

@ -24,7 +24,7 @@
#ifdef _WIN32
#include <io.h>
#include "platform/C4windowswrapper.h"
#define vsnprintf _vsnprintf
#define vsnprintf _vsprintf_p
#else
#define O_BINARY 0
#define O_SEQUENTIAL 0

View File

@ -17,6 +17,7 @@
#include "C4Include.h"
#include "script/C4Aul.h"
#include "script/C4AulExec.h"
#include "script/C4AulDebug.h"
#include "config/C4Config.h"
@ -26,6 +27,22 @@
#include "c4group/C4Components.h"
#include "c4group/C4LangStringTable.h"
const char *C4AulWarningMessages[] = {
#define DIAG(id, text, enabled) text,
#include "C4AulWarnings.h"
#undef DIAG
nullptr
};
const char *C4AulWarningIDs[] = {
#define DIAG(id, text, enabled) #id,
#include "C4AulWarnings.h"
#undef DIAG
nullptr
};
static_assert(std::extent<decltype(C4AulWarningMessages), 0>::value - 1 == static_cast<size_t>(C4AulWarningId::WarningCount), "Warning message count doesn't match warning count");
static_assert(std::extent<decltype(C4AulWarningIDs), 0>::value - 1 == static_cast<size_t>(C4AulWarningId::WarningCount), "Warning ID count doesn't match warning count");
static class DefaultErrorHandler : public C4AulErrorHandler
{
public:

View File

@ -27,6 +27,18 @@
// consts
#define C4AUL_MAX_Identifier 100 // max length of function identifiers
// warning flags
enum class C4AulWarningId
{
#define DIAG(id, text, enabled) id,
#include "C4AulWarnings.h"
#undef DIAG
WarningCount
};
extern const char *C4AulWarningIDs[];
extern const char *C4AulWarningMessages[];
// generic C4Aul error class
class C4AulError : public std::exception
{

View File

@ -29,6 +29,10 @@
#define C4AUL_SafeInherited "_inherited"
#define C4AUL_DebugBreak "__debugbreak"
#ifdef _MSC_VER
#define vsnprintf _vsprintf_p
#endif
static std::string vstrprintf(const char *format, va_list args)
{
va_list argcopy;
@ -86,23 +90,30 @@ static std::string FormatCodePosition(const C4ScriptHost *source_host, const cha
}
template<class... T>
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, const char *msg, T &&...args)
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const char *SPos, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
{
if (!host->IsWarningEnabled(SPos, warning))
return;
const char *msg = C4AulWarningMessages[static_cast<size_t>(warning)];
std::string message = sizeof...(T) > 0 ? strprintf(msg, std::forward<T>(args)...) : msg;
message += FormatCodePosition(host, SPos, target_host, func);
message += " [";
message += C4AulWarningIDs[static_cast<size_t>(warning)];
message += ']';
::ScriptEngine.GetErrorHandler()->OnWarning(message.c_str());
}
template<class... T>
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, const char *msg, T &&...args)
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const ::aul::ast::Node *n, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
{
return Warn(target_host, host, n->loc, func, msg, std::forward<T>(args)...);
return Warn(target_host, host, n->loc, func, warning, std::forward<T>(args)...);
}
template<class... T>
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, const char *msg, T &&...args)
static void Warn(const C4ScriptHost *target_host, const C4ScriptHost *host, const std::nullptr_t &, const C4AulScriptFunc *func, C4AulWarningId warning, T &&...args)
{
return Warn(target_host, host, static_cast<const char*>(nullptr), func, msg, std::forward<T>(args)...);
return Warn(target_host, host, static_cast<const char*>(nullptr), func, warning, std::forward<T>(args)...);
}
template<class... T>
@ -463,7 +474,7 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::RangeLoop *n)
// the function and warn if it hasn't been declared at all.
if (Fn->VarNamed.GetItemNr(cname) == -1)
{
Warn(target_host, host, n, Fn, "Implicit declaration of the loop variable in a for-in loop is deprecated: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::implicit_range_loop_var_decl, cname);
Fn->VarNamed.AddName(cname);
}
}
@ -474,7 +485,7 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
{
if (n->constant && n->scope != ::aul::ast::VarDecl::Scope::Global)
{
Warn(target_host, host, n, Fn, "Non-global variables cannot be constant");
Warn(target_host, host, n, Fn, C4AulWarningId::non_global_var_is_never_const);
}
for (const auto &var : n->decls)
{
@ -492,10 +503,10 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
// if target_host is unset, we're parsing this func for direct execution,
// in which case we don't want to warn about variable hiding.
if (target_host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || target_host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
Warn(target_host, host, n, Fn, "function-local variable hides a global variable: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, cname, "local variable", "global variable");
C4String *s = ::Strings.FindString(cname);
if (s && target_host->GetPropList()->HasProperty(s))
Warn(target_host, host, n, Fn, "function-local variable hides an object-local variable: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, cname, "local variable", "object-local variable");
}
Fn->VarNamed.AddName(cname);
break;
@ -503,10 +514,10 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
case ::aul::ast::VarDecl::Scope::Object:
{
if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
Warn(target_host, host, n, Fn, "object-local variable hides a global variable: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::variable_shadows_variable, cname, "object-local variable", "global variable");
C4String *s = ::Strings.RegString(cname);
if (target_host->GetPropList()->HasProperty(s))
Warn(target_host, host, n, Fn, "object-local variable declared multiple times: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, cname, "object-local variable");
else
target_host->GetPropList()->SetPropertyByS(s, C4VNull);
break;
@ -517,7 +528,7 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::VarDecl *n)
throw Error(target_host, host, n, Fn, "internal error: global var declaration inside function");
if (host->Engine->GlobalNamedNames.GetItemNr(cname) >= 0 || host->Engine->GlobalConstNames.GetItemNr(cname) >= 0)
Warn(target_host, host, n, Fn, "global variable declared multiple times: %s", cname);
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, cname, "global variable");
if (n->constant)
host->Engine->GlobalConstNames.AddName(cname);
else
@ -584,7 +595,7 @@ void C4AulCompiler::PreparseAstVisitor::visit(const ::aul::ast::ParExpr *n)
{
if (Fn->ParCount != C4AUL_MAX_Par)
{
Warn(target_host, host, n, Fn, "using Par() inside a function forces it to take variable arguments");
Warn(target_host, host, n, Fn, C4AulWarningId::undeclared_varargs, "Par()");
Fn->ParCount = C4AUL_MAX_Par;
}
DefaultRecursiveVisitor::visit(n);
@ -1288,8 +1299,8 @@ void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::CallExpr *n)
if (n->args.size() > fn_argc)
{
// Pop off any args that are over the limit
Warn(target_host, host, n->args[fn_argc].get(), Fn,
"call to %s passes %zu parameters, of which only %zu are used", cname, n->args.size(), fn_argc);
Warn(target_host, host, n->args[fn_argc].get(), Fn, C4AulWarningId::arg_count_mismatch,
cname, n->args.size(), fn_argc);
AddBCC(n->loc, AB_STACK, fn_argc - n->args.size());
}
else if (n->args.size() < fn_argc)
@ -1369,7 +1380,7 @@ void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::CallExpr *n)
C4V_Type to = expected_par_types[i];
if (C4Value::WarnAboutConversion(from, to))
{
Warn(target_host, host, n->args[i].get(), Fn, "parameter %zu of %s is %s (%s expected)", i, cname, GetC4VName(from), GetC4VName(to));
Warn(target_host, host, n->args[i].get(), Fn, C4AulWarningId::arg_type_mismatch, cname, i, GetC4VName(from), GetC4VName(to));
}
}
@ -1576,14 +1587,14 @@ void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::If *n)
// Warn if we're controlling a no-op ("if (...);")
if (dynamic_cast<::aul::ast::Noop*>(n->iftrue.get()))
{
Warn(target_host, host, n->iftrue->loc, Fn, "empty controlled statement found (use '{}' if this is intentional)");
Warn(target_host, host, n->iftrue->loc, Fn, C4AulWarningId::empty_if);
}
if (SafeVisit(n->iftrue))
MaybePopValueOf(n->iftrue);
if (dynamic_cast<::aul::ast::Noop*>(n->iffalse.get()))
{
Warn(target_host, host, n->iffalse->loc, Fn, "empty controlled statement found (use '{}' if this is intentional)");
Warn(target_host, host, n->iffalse->loc, Fn, C4AulWarningId::empty_if);
}
if (n->iffalse)
@ -1642,7 +1653,7 @@ void C4AulCompiler::CodegenAstVisitor::visit(const ::aul::ast::FunctionDecl *n)
if (f->SFunc() && f->SFunc()->pOrgScript == host && f->Parent == Parent)
{
if (Fn)
Warn(target_host, host, n, Fn, "function declared multiple times");
Warn(target_host, host, n, Fn, C4AulWarningId::redeclaration, f->GetName(), "function");
Fn = f->SFunc();
}
f = f->SFunc() ? f->SFunc()->OwnerOverloaded : 0;

View File

@ -38,6 +38,10 @@
#define C4AUL_Include "#include"
#define C4AUL_Append "#appendto"
#define C4AUL_Warning "#warning"
#define C4Aul_Warning_enable "enable"
#define C4Aul_Warning_disable "disable"
#define C4AUL_Func "func"
@ -128,19 +132,20 @@ C4AulParse::~C4AulParse()
void C4ScriptHost::Warn(const char *pMsg, ...)
{
va_list args; va_start(args, pMsg);
StdStrBuf Buf;
Buf.AppendFormatV(pMsg, args);
StdStrBuf Buf = FormatStringV(pMsg, args);
Buf.AppendFormat(" (%s)", ScriptName.getData());
Engine->GetErrorHandler()->OnWarning(Buf.getData());
va_end(args);
}
void C4AulParse::Warn(const char *pMsg, ...)
void C4AulParse::Warn(C4AulWarningId warning, ...)
{
va_list args; va_start(args, pMsg);
StdStrBuf Buf;
Buf.AppendFormatV(pMsg, args);
if (!pOrgScript->IsWarningEnabled(TokenSPos, warning))
return;
va_list args; va_start(args, warning);
StdStrBuf Buf = FormatStringV(C4AulWarningMessages[static_cast<size_t>(warning)], args);
AppendPosition(Buf);
Buf.AppendFormat(" [%s]", C4AulWarningIDs[static_cast<size_t>(warning)]);
Engine->GetErrorHandler()->OnWarning(Buf.getData());
va_end(args);
}
@ -383,6 +388,16 @@ C4AulTokenType C4AulParse::GetNextToken()
C = *(++SPos);
}
// Special case for #warning because we don't want to give it to the parser
if (dir && SEqual2(TokenSPos, C4AUL_Warning))
{
// Look for end of line or end of file
while (*SPos != '\n' && *SPos != '\0') ++SPos;
Parse_WarningPragma();
// And actually return the next token.
return GetNextToken();
}
Len = std::min(Len, C4AUL_MAX_Identifier);
SCopy(TokenSPos, Idtf, Len);
return dir ? ATT_DIR : ATT_IDTF;
@ -443,7 +458,7 @@ C4AulTokenType C4AulParse::GetNextToken()
// First char must be a hexdigit
if (!std::isxdigit(*SPos))
{
Warn("\\x used with no following hex digits");
Warn(C4AulWarningId::invalid_hex_escape);
strbuf.push_back('\\'); strbuf.push_back('x');
}
else
@ -481,7 +496,7 @@ C4AulTokenType C4AulParse::GetNextToken()
// just insert "\"
strbuf.push_back('\\');
// show warning
Warn("unknown escape \"%c\"", *(SPos + 1));
Warn(C4AulWarningId::invalid_escape_sequence, *(SPos + 1));
}
}
else if (C == 0 || C == 10 || C == 13) // line break / feed
@ -685,6 +700,13 @@ bool C4ScriptHost::Preparse()
// Add any engine functions specific to this script
AddEngineFunctions();
// Insert default warnings
assert(enabledWarnings.empty());
auto &warnings = enabledWarnings[Script.getData()];
#define DIAG(id, text, enabled) warnings.set(static_cast<size_t>(C4AulWarningId::id), enabled);
#include "C4AulWarnings.h"
#undef DIAG
C4AulParse parser(this);
ast = parser.Parse_Script(this);
@ -749,6 +771,66 @@ void C4AulParse::UnexpectedToken(const char * Expected)
throw C4AulParseError(this, FormatString("%s expected, but found %s", Expected, GetTokenName(TokenType)).getData());
}
void C4AulParse::Parse_WarningPragma()
{
assert(SEqual2(TokenSPos, C4AUL_Warning));
assert(std::isspace(TokenSPos[sizeof(C4AUL_Warning) - 1]));
// Read parameters in to string buffer. The sizeof() includes the terminating \0, but
// that's okay because we need to skip (at least) one whitespace character anyway.
std::string line(TokenSPos + sizeof(C4AUL_Warning), SPos);
auto end = line.end();
auto cursor = std::find_if_not(begin(line), end, IsWhiteSpace);
if (cursor == end)
throw C4AulParseError(this, "'" C4Aul_Warning_enable "' or '" C4Aul_Warning_disable "' expected, but found end of line");
// Split directive on whitespace
auto start = cursor;
cursor = std::find_if(start, end, IsWhiteSpace);
bool enable_warning = false;
if (std::equal(start, cursor, C4Aul_Warning_enable))
{
enable_warning = true;
}
else if (std::equal(start, cursor, C4Aul_Warning_disable))
{
enable_warning = false;
}
else
{
throw C4AulParseError(this, FormatString("'" C4Aul_Warning_enable "' or '" C4Aul_Warning_disable "' expected, but found '%s'", std::string(start, cursor).c_str()).getData());
}
cursor = std::find_if_not(cursor, end, IsWhiteSpace);
if (cursor == end)
{
// enable or disable all warnings
#define DIAG(id, text, enabled) pOrgScript->EnableWarning(TokenSPos, C4AulWarningId::id, enable_warning);
#include "C4AulWarnings.h"
#undef DIAG
return;
}
// enable or disable specific warnings
static const std::map<std::string, C4AulWarningId> warnings{
#define DIAG(id, text, enabled) std::make_pair(#id, C4AulWarningId::id),
#include "C4AulWarnings.h"
#undef DIAG
};
while (cursor != end)
{
start = std::find_if_not(cursor, end, IsWhiteSpace);
cursor = std::find_if(start, end, IsWhiteSpace);
auto entry = warnings.find(std::string(start, cursor));
if (entry != warnings.end())
{
pOrgScript->EnableWarning(TokenSPos, entry->second, enable_warning);
}
}
}
void C4AulScriptFunc::ParseDirectExecFunc(C4AulScriptEngine *Engine, C4AulScriptContext* context)
{
ClearCode();
@ -936,8 +1018,7 @@ void C4AulParse::Parse_Function(::aul::ast::Function *func)
if (TokenType == ATT_BCLOSE || TokenType == ATT_COMMA)
{
par_name = Idtf;
if (Config.Developer.ExtraWarnings)
Warn("'%s' used as parameter name", Idtf);
Warn(C4AulWarningId::type_name_used_as_par_name, Idtf);
}
else
{
@ -1097,8 +1178,7 @@ void C4AulParse::Parse_CallParams(::aul::ast::CallExpr *call)
{
case ATT_COMMA:
// got no parameter before a ","
if (Config.Developer.ExtraWarnings)
Warn(FormatString("parameter %zu of call to %s is empty", call->args.size(), call->callee.c_str()).getData(), nullptr);
Warn(C4AulWarningId::empty_parameter_in_call, call->args.size(), call->callee.c_str());
call->args.push_back(::aul::ast::NilLit::New(TokenSPos));
Shift();
break;
@ -1131,8 +1211,7 @@ std::unique_ptr<::aul::ast::ArrayLit> C4AulParse::Parse_Array()
// got no parameter before a ","? then push nil
if (TokenType == ATT_COMMA)
{
if (Config.Developer.ExtraWarnings)
Warn(FormatString("array entry %zu is empty", arr->values.size()).getData(), nullptr);
Warn(C4AulWarningId::empty_parameter_in_array, arr->values.size());
arr->values.emplace_back(::aul::ast::NilLit::New(TokenSPos));
}
else
@ -1143,8 +1222,7 @@ std::unique_ptr<::aul::ast::ArrayLit> C4AulParse::Parse_Array()
// [] -> size 0, [*,] -> size 2, [*,*,] -> size 3
if (TokenType == ATT_BCLOSE2)
{
if (Config.Developer.ExtraWarnings)
Warn(FormatString("array entry %zu is empty", arr->values.size()).getData(), nullptr);
Warn(C4AulWarningId::empty_parameter_in_array, arr->values.size());
arr->values.emplace_back(::aul::ast::NilLit::New(TokenSPos));
}
}
@ -1695,6 +1773,38 @@ bool C4ScriptHost::Parse()
return true;
}
void C4ScriptHost::EnableWarning(const char *pos, C4AulWarningId warning, bool enable)
{
auto entry = enabledWarnings.emplace(pos, decltype(enabledWarnings)::mapped_type{});
if (entry.second)
{
// If there was no earlier entry for this position, copy the previous
// warning state
assert(entry.first != enabledWarnings.begin());
auto previous = entry.first;
--previous;
entry.first->second = previous->second;
}
entry.first->second.set(static_cast<size_t>(warning), enable);
}
bool C4ScriptHost::IsWarningEnabled(const char *pos, C4AulWarningId warning) const
{
assert(!enabledWarnings.empty());
if (enabledWarnings.empty())
return false;
// find nearest set of warnings at or before the current position
auto entry = enabledWarnings.upper_bound(pos);
assert(entry != enabledWarnings.begin());
if (entry != enabledWarnings.begin())
{
--entry;
}
return entry->second.test(static_cast<size_t>(warning));
}
void C4AulParse::PushParsePos()
{
parse_pos_stack.push(TokenSPos);

View File

@ -62,6 +62,7 @@ private:
C4String * cStr; // current string constant
C4AulScriptContext* ContextToExecIn;
void Parse_Function(bool parse_for_direct_exec);
void Parse_WarningPragma();
protected:
// All of the Parse_* functions need to be protected (not private!) so
@ -93,7 +94,7 @@ private:
void Check(C4AulTokenType TokenType, const char * Expected = nullptr);
NORETURN void UnexpectedToken(const char * Expected);
void Warn(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O;
void Warn(C4AulWarningId warning, ...);
void Error(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O;
void AppendPosition(StdStrBuf & Buf);

View File

@ -0,0 +1,42 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2017, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// C4Aul diagnostics definitions
#pragma push_macro("DIAG")
#ifndef DIAG
#define DIAG(id, text, enabled_by_default)
#endif
// Lexer diagnostics
DIAG(invalid_escape_sequence, "unknown escape sequence '\\%1$s'", true)
DIAG(invalid_hex_escape, "'\\x' used with no following hex digits", true)
// Parser diagnostics
DIAG(type_name_used_as_par_name, "type '%1$s' used as parameter name", false)
DIAG(empty_parameter_in_call, "parameter %1$zu of call to '%2$s' is empty", false)
DIAG(empty_parameter_in_array, "array entry %1$zu is empty", false)
// Compiler diagnostics
DIAG(implicit_range_loop_var_decl, "implicit declaration of the loop variable '%1$s' in a for-in loop is deprecated", true)
DIAG(non_global_var_is_never_const, "variable '%1$s' declared as const, but non-global variables are always mutable", true)
DIAG(variable_shadows_variable, "declaration of %2$s '%1$s' shadows %3$s", true)
DIAG(redeclaration, "redeclaration of %2$s '%1$s'", true)
DIAG(undeclared_varargs, "use of '%1$s' in a function forces it to take variable arguments", true)
DIAG(arg_count_mismatch, "call to '%1$s' passes %2$zu arguments, of which only %3$zu are used", true)
DIAG(arg_type_mismatch, "parameter %2$zu of call to '%1$s' passes %3$s (%4$s expected)", true)
DIAG(empty_if, "empty controlled statement (use '{}' if this is intentional)", true)
#pragma pop_macro("DIAG")

View File

@ -71,6 +71,7 @@ void C4ScriptHost::Clear()
Appends.clear();
// reset flags
State = ASS_NONE;
enabledWarnings.clear();
}
void C4ScriptHost::UnlinkOwnedFunctions()

View File

@ -24,6 +24,7 @@
#include "script/C4Aul.h"
#include "script/C4AulAST.h"
#include <bitset>
// aul script state
enum C4AulScriptState
@ -55,6 +56,8 @@ public:
std::list<C4ScriptHost *> SourceScripts;
StdCopyStrBuf ScriptName; // script name
bool IsWarningEnabled(const char *pos, C4AulWarningId warning) const;
protected:
C4ScriptHost();
void Unreg(); // remove from list
@ -97,6 +100,8 @@ protected:
// in Clear() even in case of cyclic references.
std::vector<C4Value> ownedPropLists;
void EnableWarning(const char *pos, C4AulWarningId warning, bool enable = true);
friend class C4AulParse;
friend class C4AulProfiler;
friend class C4AulScriptEngine;
@ -105,6 +110,7 @@ protected:
friend class C4AulScriptFunc;
private:
std::map<const char*, std::bitset<(size_t)C4AulWarningId::WarningCount>> enabledWarnings;
std::unique_ptr<::aul::ast::Script> ast;
};

View File

@ -313,3 +313,65 @@ TEST_F(AulTest, NoWarnings)
EXPECT_CALL(errh, OnWarning(::testing::_)).Times(0);
EXPECT_EQ(C4Value(), RunScript("func Main(string s, object o, array a) { var x; Sin(x); }"));
}
TEST_F(AulTest, DiagnosticsSelection)
{
{
ErrorHandler errh;
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[arg_type_mismatch]"))).Times(2);
// Test disabling and re-enabling warnings
RunScript(R"(
func Main(string s) {
Sin(s);
#warning disable arg_type_mismatch
Sin(s);
#warning enable arg_type_mismatch
Sin(s);
}
)");
}
{
ErrorHandler errh;
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[arg_type_mismatch]")));
// Test that disabling a warning doesn't affect any other warnings
RunScript(R"(
func Main(string s) {
#warning disable redeclaration
Sin(s);
}
)");
}
{
ErrorHandler errh;
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[arg_count_mismatch]"))).Times(0);
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[arg_type_mismatch]"))).Times(0);
// Test disabling multiple warnings at once
RunScript(R"(
func Main(string s) {
#warning disable arg_count_mismatch arg_type_mismatch
Sin(s, s);
}
)");
}
{
ErrorHandler errh;
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[arg_type_mismatch]"))).Times(0);
// Test disabling all warnings at once
RunScript(R"(
func Main(string s) {
#warning disable
Sin(s);
}
)");
}
{
ErrorHandler errh;
EXPECT_CALL(errh, OnWarning(::testing::EndsWith("[type_name_used_as_par_name]"))).Times(2);
// Test that disabled-by-default warnings have to be enabled explicitly
RunScript(R"(
func Main(array) {}
#warning enable type_name_used_as_par_name
func f(array, string) {}
)");
}
}