## compile-time chained operator evaluation mechanism
Constants are stored in `value` object. The object has two members: `constval` and `boolresult`. The `constval` member stores the actual value which the object represents: `15` and `10` for example in `15 < 20`. All operators are in a chain of operators. On most occasions, there is just one operator in the chain but it is still a valid singleton chain. The `boolresult` member stores the result of the previous operations which the constant was involved in if any. For example, in `15 < 20 < 30`, the `boolresult` member of object representing `20` stores the result of `15 < 20`. In more complex expressions such as, `15 < 20 < 30 < 40 < 50`, the `boolresult` member of object representing `40` stores the result of the `15 < 20 < 30 < 40`.
The `boolresult` of the first constant in a chain is set to `TRUE` by default.
**Details:**
The processing of chains begins at `plnge_rel` which maintains two `value` objects: `lval` and `lval2`. The `lval` is taken by `plnge_rel` as an argument of the first constant (stored in `constval` field) value in the operator chain. The result of the full chain is also passed through it by storing the result in the `constval` field (like `15 < 20` evaluates to the constant `1`; the result of `15 < 20` is `1` which is also a constant).
The `boolresult` of `lval` is set to `TRUE` as this is the first constant in the chain. The `plnge_rel` progresses through the chain one by one with `lval` and `lval2` always being the left operand and the right operand respectively. For each operator it calls `plnge2` which dumps instructions for loading constant and the right operand into the staging buffer. If the right operand turns out to be a constant, the staging buffer is flushed and `static cell calc(cell left,void (*oper)(),cell right,char *boolresult);` is called to evaluate the compile-time expression with the constant value of the left and right operand passed as `left` and `right` arguments along with the operator and `boolresult` of the left operand. `calc` evluates the expression and returns the right operand and sets the `boolresult` to the result of the operator chain. After the call returns, `constval` of `lval` (left operand) is set to the `constval` of `lval2` (right operand). As of now, the `lval` now stores the right operand and the result of the operator chain uptil now.
`plnge2` returns and control is transfered back to `plnge_rel`. `plnge_rel` copies the value stored in `lval2` into `lval`. This implies that the `boolresult` and `constval` fields of `lval2` are copied into `lval` as the previous right operand is now the left operand for the next operator in the chain. **Oops! BUG!** The `boolresult` which was stored in `lval` is lost and is replaced by `FALSE` which was stored in `lval2` which is the default for `value` objets (set by `clear_value`). The compiler should've ensured that the `boolresult` of `lval` was retained. `plnge2` is again called to process the right operand and evaluate the expression.
This keeps repeating until the entire chain has been evaluated. After the entire chain has been evaluated, `lval` has `constval` as the most recent right operand and `boolresult` has the result of the entire chain. The compiler sets `constval` of `lval` to `boolresult` of `lval` and its tag to `bool`. Note that this is done in the last step. The `lval` now is a constant representing the result of the entire chain of operators, i.e. `1` if the chain evaluated to `TRUE` or `0` otherwise.
**Relavant bugged code:**
```
static int plnge_rel(int *opstr,int opoff,int (*hier)(value *lval),value *lval)
{
.
.
.
lval->boolresult=TRUE;
do {
/* same check as in plnge(), but "chkbitwise" is always TRUE */
if (count>0 && bitwise_opercount!=0)
error(212);
if (count>0) {
relop_prefix();
*lval=lval2; /* copy right hand expression of the previous iteration */
} /* if */
opidx+=opoff;
plnge2(op1[opidx],hier,lval,&lval2);
if (count++>0)
relop_suffix();
} while (nextop(&opidx,opstr)); /* enddo */
lval->constval=lval->boolresult;
lval->tag=pc_addtag("bool"); /* force tag to be "bool" */
return FALSE; /* result of expression is not an lvalue */
}
```
```
static void plnge2(void (*oper)(void),
int (*hier)(value *lval),
value *lval1,value *lval2)
{
.
.
.
if (check_userop(oper,lval1->tag,lval2->tag,2,NULL,&lval1->tag)) {
lval1->ident=iEXPRESSION;
lval1->constval=0;
} else if (lval1->ident==iCONSTEXPR && lval2->ident==iCONSTEXPR) {
/* only constant expression if both constant */
stgdel(index,cidx); /* scratch generated code and calculate */
check_tagmismatch(lval1->tag,lval2->tag,FALSE,-1);
lval1->constval=calc(lval1->constval,oper,lval2->constval,&lval1->boolresult);
} else {
.
.
.
}
```
```
static cell calc(cell left,void (*oper)(),cell right,char *boolresult)
{
.
.
.
else if (oper==os_le)
return *boolresult &= (char)(left <= right), right;
else if (oper==os_ge)
return *boolresult &= (char)(left >= right), right;
else if (oper==os_lt)
return *boolresult &= (char)(left < right), right;
else if (oper==os_gt)
return *boolresult &= (char)(left > right), right;
.
.
.
}
```
As you can see, the `boolresult` is ANDed with the result of the current operation. If one of the operators in the chain earlier had evaluated to false, the `&` will force `boolresult` to remain `FALSE` irrespective of whether the current expression evaluates to true or not.
### What's the bug?
The compiler loses the `boolresult` of `lval` in `plnge_rel` and replaces it with the `boolresult` of `lval2` which is always initalized to `FALSE` by `clear_val`.
The fix is to ensure that `lval` retains the value.
This commits does this by copying the `boolresult` of `lval2` into `lval` before `*lval=lval2`. This ensures that the `boolresult` is retained in `lval`.
Adds a new warning to warn users when they pass an array/string literal to a non-const qualified parameter.
```
f1(arr[]) {
new a = arr[0];
#pragma unused a
}
f2(arr[5]) {
new a = arr[0];
#pragma unused a
}
f3(const arr[]) {
new a = arr[0];
#pragma unused a
}
f4(const arr[5]) {
new a = arr[0];
#pragma unused a
}
main () {
f1("test");
f2("test");
f3("test");
f4("test");
new arr[5];
f1(arr);
f2(arr);
f3(arr);
f4(arr);
f1(arr[0]);
//f2(arr[0]); - array size must match
f3(arr[0]);
//f4(arr[0]); - array size must match
}
```
```
test.pwn(1) : warning 214: possibly a "const" array argument was intended: "arr"
test.pwn(6) : warning 214: possibly a "const" array argument was intended: "arr"
test.pwn(20) : warning 239: literal array/string passed to a non-const parameter
test.pwn(21) : warning 239: literal array/string passed to a non-const parameter
```
During first pass the call to error() is ignored and therefore
doesn't break ouf of the loop. This causes stack courrption because
of OBB write to arglist.
Fixes#298.
Previously, if the tagof operator was used on an argument with default value, the tag identifier recorded would be 0 in case the default value was used.
```
f({Tag1, Tag2}:a = Tag2:123, b = tagof(a)) { }
main () { f(); }
```
In the above code, the argument `a` would have the value `123` but `b` would have `0`.
This commit patches the bug. The value of `b` will be the tag identifier of the default argument.
This commit patches for two cases: default argument is a reference and default argument is a variable.
The case of reference arrays is not handled correctly as the tag of the default reference array is not well defined.
**Implementation details:**
The compiler first processes arguments which are not taking default values (including `tagof` and `sizeof` parameters). The compiler does a second pass on the list of all formal parameters and processes the default values. The compiler then does a final pass on the list of formal parameters and processes `sizeof` and `tagof` parameters.
The compiler maintains a list of tags of all the arguments in a `constvalue` list called `taglst` as it processes the arguments.
The name field of the members of the tag list is the argument name and the tag field stores the tag identifier of that argument (tag identifier of the actual parameter).
In the first pass where the expclit arguments are being processed, the compiler checks the argument's tag and adds the tag along with the formal parameter name (can be thought of as <formal-paramter-name : tag pair>) to the tag list if the tag identifier is NOT zero.
In the second pass where the default values are being handled, the compiler does not modify the tag list (**source of the bug**).
After all the arguments other than `sizeof` and `tagof` have been handled othe, the compiler iterates through the list of arguments and takes care of `sizeof` and `tagof` arguments.
Here it checks to which formal parameter (say F) the `tagof` parameter (say T) points to. It then checks the tag list for that symbol F, if an entry is found, it uses the tag identifier associated with that argument. If not found, it defaults to 0.
Since the tags of default arguments along with it's formal name is not added to the tag list, the third pass won't be aware of any tag associated with the default values and assumes 0.
This commit adds code to add the tags of the default values to the tag list so that the compiler is aware of the tag of the default values in the final pass.
The standalone tagof operator tries to export tall tags which it works with.
The tagof operator when used as a default argument checks if the tag is zero before exporting.
As the zero tag identifier cannot be exported (even if its `PUBLICTAG` bit is set), it makes sense to use `0` as tag identifier instead of `0 | PUBLICTAG`.
This commit changes the standalone tagof operator behaviour so that the zero tag identifier does not get its `PUBLICTAG` bit set.
Change lvalue type (ident) to iEXPRESSION if a unary operator is
applied to it ('!', '~', '-'). This allows the self-assignment
check to pass.
Fixes#78.
Only reparse if the function has a tagged result (old behavior) or a
global variable is passed as one of its arguments at some point before
declaration/definition.
Also warn if need to reparse so that developers are aware of potential
performance hit.
Fixes#131.
If one of the two values is a string literal and the other is a
non-array, the symbol associated with the first one will be NULL.
But the code checked if sym->name!=NULL rather than sym!=NULL,
hence the crash.
--------- test code --------
main() {
new a, b;
return (a != 0 ? b : "string");
}
----- end of test code -----
When applied to a function #pragma naked merely disables the "should return
a value" warning for the function. It's intended to be used with functions
that return a value via #emit instead of the normal return statement.
This pragma works only on function definitions, not declarations. It's also
pretty stupid - the function may be defined way after this directive and it
won't stop on things coming in between.
For example, here all declarations between #pragma naked and f() are
effectively ignored:
#pragma naked
new x; // ignored
forward g(); // ignored
native n(); // ignored
f() {
// f() becomes naked
}
Note that #pragma naked does not affect generated code in any way, unlike
e.g. __declspec(naked) or __attribute__((naked)) in C/C++ where the compiler
omits the code for the prolog and epilog.
This fixes a crash that occurs if a global variable is initialized
with the result of a function call.
See 3) here: http://forum.sa-mp.com/showthread.php?t=355877
--------- test code --------
native use(...);
f() {
return 0;
}
new x = f();
main() {
use(x);
}
----- end of test code -----