mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
374 lines
12 KiB
JavaScript
374 lines
12 KiB
JavaScript
/*
|
|
* Check that only JS_REQUIRES_STACK/JS_FORCES_STACK functions, and functions
|
|
* that have called a JS_FORCES_STACK function, access cx->fp directly or
|
|
* indirectly.
|
|
*/
|
|
|
|
require({ after_gcc_pass: 'cfg' });
|
|
include('gcc_util.js');
|
|
include('unstable/adts.js');
|
|
include('unstable/analysis.js');
|
|
include('unstable/lazy_types.js');
|
|
include('unstable/esp.js');
|
|
|
|
var Zero_NonZero = {};
|
|
include('unstable/zero_nonzero.js', Zero_NonZero);
|
|
|
|
// Tell MapFactory we don't need multimaps (a speed optimization).
|
|
MapFactory.use_injective = true;
|
|
|
|
/*
|
|
* There are two regions in the program: RED and GREEN. Functions and member
|
|
* variables may be declared RED in the C++ source. GREEN is the default.
|
|
*
|
|
* RED signals danger. A GREEN part of a function must not call a RED function
|
|
* or access a RED member.
|
|
*
|
|
* The body of a RED function is all red. The body of a GREEN function is all
|
|
* GREEN by default, but parts dominated by a call to a TURN_RED function are
|
|
* red. This way GREEN functions can safely access RED stuff by calling a
|
|
* TURN_RED function as preparation.
|
|
*
|
|
* The analysis does not attempt to prove anything about the body of a TURN_RED
|
|
* function. (Both annotations are trusted; only unannotated code is checked
|
|
* for errors.)
|
|
*/
|
|
const RED = 'JS_REQUIRES_STACK';
|
|
const TURN_RED = 'JS_FORCES_STACK';
|
|
const IGNORE_ERRORS = 'JS_IGNORE_STACK';
|
|
|
|
function attrs(tree) {
|
|
let a = DECL_P(tree) ? DECL_ATTRIBUTES(tree) : TYPE_ATTRIBUTES(tree);
|
|
return translate_attributes(a);
|
|
}
|
|
|
|
function hasUserAttribute(tree, attrname) {
|
|
let attributes = attrs(tree);
|
|
if (attributes) {
|
|
for (let i = 0; i < attributes.length; i++) {
|
|
let attr = attributes[i];
|
|
if (attr.name == 'user' && attr.value.length == 1 && attr.value[0] == attrname)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function process_tree_type(d)
|
|
{
|
|
let t = dehydra_convert(d);
|
|
if (t.isFunction)
|
|
return;
|
|
|
|
if (t.typedef !== undefined)
|
|
if (isRed(TYPE_NAME(d)))
|
|
error("Typedef declaration is annotated JS_REQUIRES_STACK: the annotation should be on the type itself", t.loc);
|
|
|
|
if (hasAttribute(t, RED)) {
|
|
error("Non-function is annotated JS_REQUIRES_STACK", t.loc);
|
|
return;
|
|
}
|
|
|
|
for (let st = t; st !== undefined && st.isPointer; st = st.type) {
|
|
if (hasAttribute(st, RED)) {
|
|
error("Non-function is annotated JS_REQUIRES_STACK", t.loc);
|
|
return;
|
|
}
|
|
|
|
if (st.parameters)
|
|
return;
|
|
}
|
|
}
|
|
|
|
function process_tree_decl(d)
|
|
{
|
|
// For VAR_DECLs, walk the DECL_INITIAL looking for bad assignments
|
|
if (TREE_CODE(d) != VAR_DECL)
|
|
return;
|
|
|
|
let i = DECL_INITIAL(d);
|
|
if (!i)
|
|
return;
|
|
|
|
assignCheck(i, TREE_TYPE(d), function() { return location_of(d); });
|
|
|
|
functionPointerWalk(i, d);
|
|
}
|
|
|
|
/*
|
|
* x is an expression or decl. These functions assume that
|
|
*/
|
|
function isRed(x) { return hasUserAttribute(x, RED); }
|
|
function isTurnRed(x) { return hasUserAttribute(x, TURN_RED); }
|
|
|
|
function process_tree(fndecl)
|
|
{
|
|
if (hasUserAttribute(fndecl, IGNORE_ERRORS))
|
|
return;
|
|
|
|
if (!(isRed(fndecl) || isTurnRed(fndecl))) {
|
|
// Ordinarily a user of ESP runs the analysis, then generates output based
|
|
// on the results. But in our case (a) we need sub-basic-block resolution,
|
|
// which ESP doesn't keep; (b) it so happens that even though ESP can
|
|
// iterate over blocks multiple times, in our case that won't cause
|
|
// spurious output. (It could cause us to the same error message each time
|
|
// through--but that's easily avoided.) Therefore we generate the output
|
|
// while the ESP analysis is running.
|
|
let a = new RedGreenCheck(fndecl, 0);
|
|
if (a.hasRed)
|
|
a.run();
|
|
}
|
|
|
|
functionPointerCheck(fndecl);
|
|
}
|
|
|
|
function RedGreenCheck(fndecl, trace) {
|
|
//print("RedGreenCheck: " + fndecl.toCString());
|
|
this._fndecl = fndecl;
|
|
|
|
// Tell ESP that fndecl is a "property variable". This makes ESP track it in
|
|
// a flow-sensitive way. The variable will be 1 in RED regions and "don't
|
|
// know" in GREEN regions. (We are technically lying to ESP about fndecl
|
|
// being a variable--what we really want is a synthetic variable indicating
|
|
// RED/GREEN state, but ESP operates on GCC decl nodes.)
|
|
this._state_var_decl = fndecl;
|
|
let state_var = new ESP.PropVarSpec(this._state_var_decl, true, undefined);
|
|
|
|
// Call base class constructor.
|
|
let cfg = function_decl_cfg(fndecl);
|
|
ESP.Analysis.apply(this, [cfg, [state_var], Zero_NonZero.meet, trace]);
|
|
this.join = Zero_NonZero.join;
|
|
|
|
// Preprocess all instructions in the cfg to determine whether this analysis
|
|
// is necessary and gather some information we'll use later.
|
|
//
|
|
// Each isn may include a function call, an assignment, and/or some reads.
|
|
// Using walk_tree to walk the isns is a little crazy but robust.
|
|
//
|
|
this.hasRed = false;
|
|
let self = this; // Allow our 'this' to be accessed inside closure
|
|
for (let bb in cfg_bb_iterator(cfg)) {
|
|
for (let isn in bb_isn_iterator(bb)) {
|
|
// Treehydra objects don't support reading never-defined properties
|
|
// as undefined, so we have to explicitly initialize anything we want
|
|
// to check for later.
|
|
isn.redInfo = undefined;
|
|
walk_tree(isn, function(t, stack) {
|
|
function getLocation(skiptop) {
|
|
if (!skiptop) {
|
|
let loc = location_of(t);
|
|
if (loc !== undefined)
|
|
return loc;
|
|
}
|
|
|
|
for (let i = stack.length - 1; i >= 0; --i) {
|
|
let loc = location_of(stack[i]);
|
|
if (loc !== undefined)
|
|
return loc;
|
|
}
|
|
return location_of(DECL_SAVED_TREE(fndecl));
|
|
}
|
|
|
|
switch (TREE_CODE(t)) {
|
|
case FIELD_DECL:
|
|
if (isRed(t)) {
|
|
let varName = dehydra_convert(t).name;
|
|
// location_of(t) is the location of the declaration.
|
|
isn.redInfo = ["cannot access JS_REQUIRES_STACK variable " + varName,
|
|
getLocation(true)];
|
|
self.hasRed = true;
|
|
}
|
|
break;
|
|
case CALL_EXPR:
|
|
{
|
|
let callee = call_function_decl(t);
|
|
if (callee) {
|
|
if (isRed(callee)) {
|
|
let calleeName = dehydra_convert(callee).name;
|
|
isn.redInfo = ["cannot call JS_REQUIRES_STACK function " + calleeName,
|
|
getLocation(false)];
|
|
self.hasRed = true;
|
|
} else if (isTurnRed(callee)) {
|
|
isn.turnRed = true;
|
|
}
|
|
}
|
|
else {
|
|
let fntype = TREE_CHECK(
|
|
TREE_TYPE( // the function type
|
|
TREE_TYPE( // the function pointer type
|
|
CALL_EXPR_FN(t)
|
|
)
|
|
),
|
|
FUNCTION_TYPE, METHOD_TYPE);
|
|
if (isRed(fntype)) {
|
|
isn.redInfo = ["cannot call JS_REQUIRES_STACK function pointer",
|
|
getLocation(false)];
|
|
self.hasRed = true;
|
|
}
|
|
else if (isTurnRed(fntype)) {
|
|
isn.turnRed = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initialize mixin for infeasible-path elimination.
|
|
this._zeroNonzero = new Zero_NonZero.Zero_NonZero();
|
|
}
|
|
|
|
RedGreenCheck.prototype = new ESP.Analysis;
|
|
|
|
RedGreenCheck.prototype.flowStateCond = function(isn, truth, state) {
|
|
// forward event to mixin
|
|
this._zeroNonzero.flowStateCond(isn, truth, state);
|
|
};
|
|
|
|
RedGreenCheck.prototype.flowState = function(isn, state) {
|
|
// forward event to mixin
|
|
//try { // The try/catch here is a workaround for some baffling bug in zero_nonzero.
|
|
this._zeroNonzero.flowState(isn, state);
|
|
//} catch (exc) {
|
|
// warning(exc, location_of(isn));
|
|
// warning("(Remove the workaround in jsstack.js and recompile to get a JS stack trace.)",
|
|
// location_of(isn));
|
|
//}
|
|
let stackState = state.get(this._state_var_decl);
|
|
let green = stackState != 1 && stackState != ESP.NOT_REACHED;
|
|
let redInfo = isn.redInfo;
|
|
if (green && redInfo) {
|
|
error(redInfo[0], redInfo[1]);
|
|
isn.redInfo = undefined; // avoid duplicate messages about this instruction
|
|
}
|
|
|
|
// If we call a TURNS_RED function, it doesn't take effect until after the
|
|
// whole isn finishes executing (the most conservative rule).
|
|
if (isn.turnRed)
|
|
state.assignValue(this._state_var_decl, 1, isn);
|
|
};
|
|
|
|
function followTypedefs(type)
|
|
{
|
|
while (type.typedef !== undefined)
|
|
type = type.typedef;
|
|
return type;
|
|
}
|
|
|
|
function assignCheck(source, destType, locfunc)
|
|
{
|
|
if (TREE_CODE(destType) != POINTER_TYPE)
|
|
return;
|
|
|
|
let destCode = TREE_CODE(TREE_TYPE(destType));
|
|
if (destCode != FUNCTION_TYPE && destCode != METHOD_TYPE)
|
|
return;
|
|
|
|
if (isRed(TREE_TYPE(destType)))
|
|
return;
|
|
|
|
while (TREE_CODE(source) == NOP_EXPR)
|
|
source = source.operands()[0];
|
|
|
|
// The destination is a green function pointer
|
|
|
|
if (TREE_CODE(source) == ADDR_EXPR) {
|
|
let sourcefn = source.operands()[0];
|
|
|
|
// oddly enough, SpiderMonkey assigns the address of something that's not
|
|
// a function to a function pointer as part of the API! See JS_TN
|
|
if (TREE_CODE(sourcefn) != FUNCTION_DECL)
|
|
return;
|
|
|
|
if (isRed(sourcefn))
|
|
error("Assigning non-JS_REQUIRES_STACK function pointer from JS_REQUIRES_STACK function " + dehydra_convert(sourcefn).name, locfunc());
|
|
}
|
|
else {
|
|
let sourceType = TREE_TYPE(TREE_TYPE(source).tree_check(POINTER_TYPE));
|
|
switch (TREE_CODE(sourceType)) {
|
|
case FUNCTION_TYPE:
|
|
case METHOD_TYPE:
|
|
if (isRed(sourceType))
|
|
error("Assigning non-JS_REQUIRES_STACK function pointer from JS_REQUIRES_STACK function pointer", locfunc());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A type checker which verifies that a red function pointer is never converted
|
|
* to a green function pointer.
|
|
*/
|
|
|
|
function functionPointerWalk(t, baseloc)
|
|
{
|
|
walk_tree(t, function(t, stack) {
|
|
function getLocation(skiptop) {
|
|
if (!skiptop) {
|
|
let loc = location_of(t);
|
|
if (loc !== undefined)
|
|
return loc;
|
|
}
|
|
|
|
for (let i = stack.length - 1; i >= 0; --i) {
|
|
let loc = location_of(stack[i]);
|
|
if (loc !== undefined)
|
|
return loc;
|
|
}
|
|
return location_of(baseloc);
|
|
}
|
|
|
|
switch (TREE_CODE(t)) {
|
|
case GIMPLE_MODIFY_STMT: {
|
|
let [dest, source] = t.operands();
|
|
assignCheck(source, TREE_TYPE(dest), getLocation);
|
|
break;
|
|
}
|
|
case CONSTRUCTOR: {
|
|
let ttype = TREE_TYPE(t);
|
|
switch (TREE_CODE(ttype)) {
|
|
case RECORD_TYPE:
|
|
case UNION_TYPE: {
|
|
for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
|
|
assignCheck(ce.value, TREE_TYPE(ce.index), getLocation);
|
|
break;
|
|
}
|
|
case ARRAY_TYPE: {
|
|
let eltype = TREE_TYPE(ttype);
|
|
for each (let ce in VEC_iterate(CONSTRUCTOR_ELTS(t)))
|
|
assignCheck(ce.value, eltype, getLocation);
|
|
break;
|
|
}
|
|
default:
|
|
warning("Unexpected type in initializer: " + TREE_CODE(TREE_TYPE(t)), getLocation());
|
|
}
|
|
break;
|
|
}
|
|
case CALL_EXPR: {
|
|
// Check that the arguments to a function and the declared types
|
|
// of those arguments are compatible.
|
|
let ops = t.operands();
|
|
let funcType = TREE_TYPE( // function type
|
|
TREE_TYPE(ops[1])); // function pointer type
|
|
let argTypes = [t for (t in function_type_args(funcType))];
|
|
for (let i = argTypes.length - 1; i >= 0; --i) {
|
|
let destType = argTypes[i];
|
|
let source = ops[i + 3];
|
|
assignCheck(source, destType, getLocation);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function functionPointerCheck(fndecl)
|
|
{
|
|
let cfg = function_decl_cfg(fndecl);
|
|
for (let bb in cfg_bb_iterator(cfg))
|
|
for (let isn in bb_isn_iterator(bb))
|
|
functionPointerWalk(isn, DECL_SAVED_TREE(fndecl));
|
|
}
|