diff --git a/changelog/dmd.fastdfa.escapeanalysis.dd b/changelog/dmd.fastdfa.escapeanalysis.dd new file mode 100644 index 000000000000..f6056df654ce --- /dev/null +++ b/changelog/dmd.fastdfa.escapeanalysis.dd @@ -0,0 +1,30 @@ +The fast DFA engine has gained escape analysis capabilities + +The engine works on the fundamental concept on "cells", a cell is a location in memory that holds something. +So a variable that is on the stack has a cell that provides it, but also may point to another cell if its typed as a pointer. + +Due to the fast DFA engine not being able to model indirection, the outer cell is considered separately from cells seen via indirection. +This is particularly interesting with how by-ref parameters handle it. +The cell pointed at by the by-ref is the outer cell, even if its typed as a pointer. + +```d +// Think of parameter as int* not int, so the outer cell is the container for the int. +int* pointToRefCell(ref int arg) => &arg; +``` +This allows you to escape values that come from indirection: + +```d +struct Animal { + int* datem; +} + +int* grabFromAnimal(scope Animal* animal) => animal.datem; +``` + +Partial violations of ``scope`` is allowed in non-@safe functions. +Escaping via a throw statement will still error. + +The first 29 parameters may be treated as outputs, all others must be inputs only. +This does not include the this pointer. + +No attributes have been added at this time for users to use, you must rely solely on existing ones and inference. diff --git a/compiler/src/dmd/dfa/entry.d b/compiler/src/dmd/dfa/entry.d index d29b9631247d..619da92ae0b8 100644 --- a/compiler/src/dmd/dfa/entry.d +++ b/compiler/src/dmd/dfa/entry.d @@ -29,6 +29,10 @@ import dmd.globals; import dmd.mangle; import dmd.dscope; import dmd.dsymbol; +import dmd.attribsem; +import dmd.expression; +import dmd.id; +import dmd.timetrace; import core.stdc.stdio; import core.stdc.string; @@ -78,6 +82,7 @@ void fastDFA(FuncDeclaration fd, Scope* sc) import dmd.dfa.fast.statement; import dmd.dfa.fast.analysis; import dmd.dfa.fast.report; + import dmd.dfa.utils; if (fd.skipCodegen) { @@ -95,11 +100,90 @@ void fastDFA(FuncDeclaration fd, Scope* sc) } // Use these if statements for debugging specific things. - //if (fd.ident.toString != "checkFloatInit5") return; - //if (!(fd.ident.toString == "replaceReferenceDefinition" || fd.ident.toString == "extractReferences")) return; + //if (fd.ident.toString != "nullPtrVarDerefOuter") return; + //if (!(fd.ident.toString == "extractReferences" || fd.ident.toString == "replaceReferenceDefinition")) return; //if (fd.loc.linnum != 54) return; - //if (fd.getModule.ident.toString != "start") return; - //if (strcmp(mangleExact(fd), "_D4core9exception15ArraySliceError6__ctorMFNaNbNiNfmmmAyamC6object9ThrowableZCQCyQCwQCp") != 0) return; + //if (fd.getModule.ident.toString != "doc") return; + //if (strcmp(mangleExact(fd), "_D5ocean4util9container5cache16ExpiringLRUCache__TQvTSQCaQBxQBvQBo20ExpiredCacheReloader__TQzTSQDpQDmQDkQDd25ExpiredCacheReloader_test7TrivialZQCz10CacheValueZQFa19getExpiringOrCreateMFmJbbZPQFi") != 0) return; + //if (!(strcmp(mangleExact(fd), "?visit@DeduceType@deduceType@@UEAAXPEAVType@@@Z") == 0 || fd.ident.toString == "deduceWildHelper") )return; + + // used in CI to skip tests that do need to error + version (all) + { + // If you try to mangle C++ file, and C++ generator is on, it can do funky things and error out. + auto mangling = fd._linkage == LINK.d ? mangleExact(fd) : null; + + if (fd.getModule.ident.toString == "testassert") + return; + else if (fd.getModule.ident.toString == "xtest46") + return; + else if (fd.getModule.ident.toString == "xtest46_gc") + return; + else if (fd.getModule.ident.toString == "b3841") + return; + else if (fd.getModule.ident.toString == "fail329") + return; + else if (fd.getModule.ident.toString == "fail_scope") + return; + else if (fd.getModule.ident.toString == "failcstuff2") + return; + else if (fd.getModule.ident.toString == "exe1") + return; + else if (fd.getModule.ident.toString == "exe2") + return; + else if (fd.getModule.ident.toString == "exe3") + return; + else if (fd.getModule.ident.toString == "nullderefcheck_safeonly") + return; + else if (fd.getModule.ident.toString == "nullderefcheck") + return; + else if (fd.getModule.ident.toString == "testcontracts") + return; + else if (fd.getModule.ident.toString == "arraytopointer") + return; + else if (fd.getModule.ident.toString == "compile1") + return; + else if (fd.getModule.ident.toString == "ctests2") + return; + else if (fd.getModule.ident.toString == "fix20425") + return; + else if (fd.getModule.ident.toString == "revert_dip1000") + return; + else if (fd.getModule.ident.toString == "test19873") + return; + else if (fd.getModule.ident.toString == "test22875") + return; + else if (fd.getModule.ident.toString == "test22842") + return; + else if (fd.getModule.ident.toString == "test22904") + return; + else if (fd.getModule.ident.toString == "test23034") + return; + else if (fd.getModule.ident.toString == "test23034b") + return; + else if (fd.getModule.ident.toString == "test23044") + return; + else if (fd.getModule.ident.toString == "test23875") + return; + else if (fd.getModule.ident.toString == "test24069") + return; + else if (fd.getModule.ident.toString == "testcstuff2") + return; + else if (fd.getModule.ident.toString == "testcstuff1") + return; + else if (fd.getModule.ident.toString == "complex") + return; + else if (fd.getModule.ident.toString == "a12874") + return; + else if (fd.getModule.ident.toString == "noreturn2") + return; + + if (mangling !is null) + { + if (strcmp(mangling, "_D3std4json9JSONValue17__lambda_L726_C31FNaNbNiZSQBvQBuQBs") == 0) + return; + } + } // Protect functions based upon safetiness of it. // It may be desirable to disable some behaviors in @system code, or completely. @@ -118,6 +202,13 @@ void fastDFA(FuncDeclaration fd, Scope* sc) DFAReporter reporter; dfaCommon.allocator.dfaCommon = &dfaCommon; + dfaCommon.currentFunction = fd; + + if (auto ag = fd.isThis) + { + if (ag.isClassDeclaration) + dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor; + } stmtWalker.dfaCommon = &dfaCommon; expWalker.dfaCommon = &dfaCommon; @@ -133,6 +224,49 @@ void fastDFA(FuncDeclaration fd, Scope* sc) analyzer.reporter = &reporter; reporter.errorSink = global.errorSink; + bool errorsPrinted; + + void printOnError() + { + if (errorsPrinted) + return; + errorsPrinted = true; + + dfaCommon.printIfStructure((ref OutBuffer ob, scope void delegate(const(char)*) prefix) { + prefix(""); + ob.printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars, + mangleExact(fd), fd.toFullSignature, fd.loc.toChars); + + dfaCommon.allocator.allVariables((DFAVar* var) { + prefix("var"); + ob.printf(" %p base1=%p, base2=%p, dereferenceVar=%p, oldestLifeTimeAllowedDepth=%d<%d, writeCount=%d, unmodel=%d, isScope=%d, isByRef=%d, mayEscapeInitialValue=%d", + var, var.base1, var.base2, var.dereferenceVar, var.oldestLifeTimeAllowedDepth, + var.youngestLifeTimeAllowedDepth, var.writeCount, var.unmodellable, + var.isScope, var.isByRef, var.mayEscapeInitialValue); + + if (var.var !is null) + { + ob.printf(", `%s` at ", var.var.ident.toChars); + appendLoc(ob, var.var.loc); + } + + ob.printf("\n"); + }); + dfaCommon.allocator.allObjects((DFAObject* obj) { + prefix("object"); + ob.printf(" %p base1=%p, base2=%p, storageFor=%p, derivedFrom=%p, inCell=%p, constrainedBy=%p, mayNotBeExactPointer=%d, minimumDeclaredAtDepth=%d, onTheStack=%d, lifeTimeUnderstood=%d, delayOnReadErrorOfEscape=%d\n", + obj, obj.base1, obj.base2, obj.storageFor, obj.derivedFrom, + obj.inCell, obj.constrainedBy, obj.mayNotBeExactPointer, obj.minimumDeclaredAtDepth, + obj.onTheStack, obj.lifeTimeUnderstood, obj.delayOnReadErrorOfEscape); + }); + }); + + dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { + if (fd.parametersDFAInfo !is null) + printDFAParameters(fd.parametersDFAInfo); + }); + } + dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { ob.printf("============================== %s : %s = %s at ", fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature); @@ -151,13 +285,6 @@ void fastDFA(FuncDeclaration fd, Scope* sc) } } - version (none) - { - printf("function s %s : %s = %s at %s\n", fd.getModule.ident.toChars, - mangleExact(fd), fd.toFullSignature, fd.loc.toChars); - fflush(stdout); - } - version (none) { import dmd.hdrgen; @@ -169,20 +296,151 @@ void fastDFA(FuncDeclaration fd, Scope* sc) printf(buf.extractChars); } - stmtWalker.start(fd); + int currentErrors = global.errors; - version (none) + try { - printf("function e %s : %s = %s at %s\n", fd.getModule.ident.toChars, - mangleExact(fd), fd.toFullSignature, fd.loc.toChars); - fflush(stdout); + timeTraceBeginEvent(TimeTraceEventType.dfa); + stmtWalker.start(fd); + } + finally + { + timeTraceEndEvent(TimeTraceEventType.dfa, fd); } - dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("------------------------------ %s : %s = %s at ", - fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature); + if (currentErrors != global.errors) + { + version (none) + { + printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars, + mangleExact(fd), fd.toFullSignature, fd.loc.toChars); + } - appendLoc(ob, fd.loc); - ob.writestring("\n"); + printOnError; + } + + version (all) + printOnError; + + version (all) + { + if (checkEscapes(fd, sc)) + printOnError; + } +} + +bool checkEscapes(FuncDeclaration fd, Scope* sc) +{ + import dmd.dfa.utils; + import dmd.printast; + + if (fd.getModule.ident.toString != "__fastdfa_escape_test") + return false; + + Expression[] expecteds; + bool found, error; + + foreachUda(fd, sc, (Expression uda) { + if (auto sl = uda.isStructLiteralExp) + { + ArrayLiteralExp al; + + if (sl.sd.ident.toString() == "__FastDFAEscapeTest") + { + if ((al = (*sl.elements)[0].isArrayLiteralExp) !is null) + { + if (al.elements !is null) + expecteds = (*al.elements)[]; + } + + found = true; + } + } + + return 0; }); + + if (found) + { + struct TestParam + { + bool present; + bool escapeIntoNothing; + bool escapeIntoUnknown; + ParameterDFAInfo.Inferrable inferrable; + } + + TestParam nextTestParam() + { + if (expecteds.length == 0) + return TestParam(false); + + TestParam ret = TestParam(true); + + StructLiteralExp sl = expecteds[0].isStructLiteralExp; + expecteds = expecteds[1 .. $]; + if (sl is null || sl.elements is null) + return TestParam(true); + else if (sl.elements.length != 2) + return TestParam(false); + + IntegerExp ie; + + ret.escapeIntoNothing = (ie = (*sl.elements)[0].isIntegerExp) !is null && ie.value == 1; + ret.inferrable.escapesInto = (ie = (*sl.elements)[1].isIntegerExp) !is null ? ie.value + : 0; + + return ret; + } + + void checkEscapeTest(ref ParameterDFAInfo paramDFAInfo) + { + TestParam tp = nextTestParam(); + if (!tp.present) + { + printf("Missing UDA param info for param %d\n", paramDFAInfo.parameterId); + error = true; + return; + } + + if (tp.escapeIntoNothing && !paramDFAInfo.inferred.escapeIntoNothing) + { + printf("UDA param info param %d missing escapeIntoNothing\n", + paramDFAInfo.parameterId); + error = true; + } + + if (tp.inferrable.escapesInto != 0 && paramDFAInfo.inferred.escapesInto == 0) + { + printf("UDA param info param %d missing escapesInto\n", paramDFAInfo.parameterId); + error = true; + } + else if (tp.inferrable.escapesInto != paramDFAInfo.inferred.escapesInto) + { + printf("UDA param info param %d incorrect escapesInto\n", paramDFAInfo.parameterId); + error = true; + } + } + + if (fd.parametersDFAInfo.thisPointer.parameterId == -2) + checkEscapeTest(fd.parametersDFAInfo.thisPointer); + + foreach (ref paramDFAInfo; fd.parametersDFAInfo.parameters) + checkEscapeTest(paramDFAInfo); + + foreach (expected; expecteds) + printAST(expected); + } + else + error = true; + + if (error && !fd.isGenerated && fd.loc.linnum > 999) + { + printf("%s test UDA: function %s : %s = %s at #%d\n", found ? "Incompatible".ptr : "Missing".ptr, + fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature, fd.loc.linnum); + printDFAParameters(fd.parametersDFAInfo); + return true; + } + else + return false; } diff --git a/compiler/src/dmd/dfa/fast/analysis.d b/compiler/src/dmd/dfa/fast/analysis.d index efb89cd948b8..32b9910404df 100644 --- a/compiler/src/dmd/dfa/fast/analysis.d +++ b/compiler/src/dmd/dfa/fast/analysis.d @@ -65,7 +65,7 @@ struct DFAAnalyzer { printf("converge expression for var %p isSideEffect=%d\n", ctxVar, isSideEffect); lr.printState("after cleanup"); - flush(stdout); + fflush(stdout); } DFAScope* sc = isSideEffect ? dfaCommon.getSideEffectScope() : dfaCommon.currentDFAScope; @@ -76,7 +76,7 @@ struct DFAAnalyzer version (DebugJoinMeetOp) { printf("on sc %p\n", sc); - flush(stdout); + fflush(stdout); } DFAScopeVar* scv; @@ -114,6 +114,193 @@ struct DFAAnalyzer return ret; } + void convergeFunctionCall(ref DFALatticeRef ret, + ParameterDFAInfo* retParamInfo, DFAArgumentListRef alr, ref Loc loc) + { + /* + We want to be able to see the following: + int* val1, val2; + int** ptr = *val1; + *ptr = grab(&val2); + But not see cases where there is indirection or fields. + */ + + /* + int bar; + int* v = foo(&bar); + obj is cell of bar + + int* bar = ...; + int* v = foo(bar); + no relationship to a var + */ + + /* + int* a, b = ...; + bar(a, @escape(first=) b); + + int* cell; + int** ptr = &cell; + bar(*ptr, @escape(first=) new int*); + obj is cell + infinite life obj + onRead/seeDereference would have resolved dereference + + int* cell; + int** ptrCell = ...; + int** ptr = cond ? &cell : ptrCell; + bar(*ptr, @escape(first=) new int*); + obj is cell + obj is ??? + */ + + void handleRelationshipConsequence(ParameterDFAInfo.EscapedRelationship rel, + DFAConsequence* cctx, DFAObject* source) + { + version (none) + { + printf("escape relationship %d\n", rel); + } + + if (rel == ParameterDFAInfo.EscapedRelationship.Unknown) + return; + + final switch (rel) + { + case ParameterDFAInfo.EscapedRelationship.Unknown: + return; + + case ParameterDFAInfo.EscapedRelationship.ByValue: + cctx.obj = dfaCommon.makeObject(cctx.obj); + cctx.obj.derivedFrom = source; + return; + case ParameterDFAInfo.EscapedRelationship.PointerTo: + cctx.obj = dfaCommon.makeObject(cctx.obj); + cctx.obj.derivedFrom = dfaCommon.makeInCellObject(source); + return; + case ParameterDFAInfo.EscapedRelationship.Borrows: + return; + } + } + + void handleRelationship(ParameterDFAInfo.EscapedRelationship rel, + DFAVar* outputVar, DFAObject* source) + { + dfaCommon.swapLattice(outputVar, (DFALatticeRef lr) { + DFAConsequence* cctx = lr.getContext; + + handleRelationshipConsequence(rel, cctx, source); + + return lr; + }); + } + + foreach (i, list; alr) + { + ParameterDFAInfo* paramInfo = list.each[i].paramInfo; + DFAObject* sourceObject = list.each[i].lr.getContextObject; + + // I.e. could be because of meet due to unknown resolution of branches + if (sourceObject is null) + continue; + + /* + int* ptr = ...; + foo(ptr) + At this point we can see ptr's object or: + int val; + foo(&val); + But we don't actually care what the object is. + */ + + ulong escapesInto = paramInfo.inferred.escapesInto != 0 + ? paramInfo.inferred.escapesInto : paramInfo.userSupplied.escapesInto; + int outputParamId = -3; + + if (escapesInto != 0) + { + // Return + + ParameterDFAInfo.EscapedRelationship rel = cast( + ParameterDFAInfo.EscapedRelationship)(escapesInto & 0x3); + handleRelationshipConsequence(rel, ret.getContext, sourceObject); + + outputParamId++; + escapesInto >>= 2; + } + + for (; escapesInto != 0; escapesInto >>= 2, outputParamId++) + { + ParameterDFAInfo.EscapedRelationship rel = cast( + ParameterDFAInfo.EscapedRelationship)(escapesInto & 0x3); + + if (rel == ParameterDFAInfo.EscapedRelationship.Unknown) + { + // This could either be scope, or unknown escape. + // There is nothing to do on scope since it doesn't violate any existing modelling. + // There is nothing to do on unknown escape, since its the fast dfa engine and we just ignore this. + } + else + { + DFAArgumentList.Each* outputParam = alr[outputParamId]; + if (outputParam is null) + continue; + + if (DFAConsequence* outputCctx = outputParam.lr.getContext) + { + bool gotACell; + + if (outputCctx.obj !is null) + { + outputCctx.obj.walkIndirection((DFAObject* obj, + bool hadAnIndirection, bool haveConstraintOnLifeTime, + bool inCell) { + /* + Could be something along the lines of: + &foo.bar + */ + if (hadAnIndirection) + return; + + // The cell of a variable. + DFAVar* cellOf = (obj.storageFor !is null + && obj.storageFor.var !is null) ? obj.storageFor : null; + + // Have multiple layers of pointers, which we can't model. + if (obj.derivedFrom !is null) + cellOf = null; + + // Make sure we have a cell that is declared on the stack. + if (cellOf is null) + return; + + gotACell = true; + + if (cellOf.oldestLifeTimeAllowedDepth < 0) + return; + + handleRelationship(rel, cellOf, sourceObject); + }, null); + } + + if (!gotACell && outputCctx.var !is null) + { + outputCctx.var.visitIndirectSources((DFAVar* var, + bool hadAnIndirection, bool hadAnOuterDeref, + bool takenAddressOf, bool hadFields, + bool isOffsetOfStorage, ref bool unknown) { + if (hadAnIndirection || takenAddressOf || hadFields) + return; + + handleRelationship(rel, var, sourceObject); + }); + } + } + } + } + } + } + /*********************************************************** * Merges the states from the True and False branches of an If statement. * @@ -136,7 +323,7 @@ struct DFAAnalyzer int predicateNegation) { const lastLoopyLabelDepth = dfaCommon.lastLoopyLabel.depth; - bool bothJumped, bothReturned; + uint bothJumped; // False body should never be null. assert(!scrFalse.isNull); @@ -173,7 +360,7 @@ struct DFAAnalyzer foreach (contextVar, l, scv; scr) { // Ignore any variables declared within the loop - if (contextVar.declaredAtDepth >= lastLoopyLabelDepth) + if (contextVar.oldestLifeTimeAllowedDepth >= lastLoopyLabelDepth) continue; DFAScopeVar* scvParent = dfaCommon.acquireScopeVar(contextVar); @@ -216,6 +403,10 @@ struct DFAAnalyzer */ DFAConsequence* gateConsequence = condition.getGateConsequence; + // Simplify later checks to see if we have a valid gate consequence + if (gateConsequence !is null && gateConsequence.var.haveBase) + gateConsequence = null; + /* Given a gate, store any output state of a if statement branch into it. Ignoring the nature of consequences, and instead look limitedly at the maybe tops. @@ -250,7 +441,7 @@ struct DFAAnalyzer } } - if (gateConsequence !is null && !gateConsequence.var.haveBase) + if (gateConsequence !is null) { DFAScopeVar* gateScv = dfaCommon.lastLoopyLabel.getScopeVar(gateConsequence.var); @@ -365,7 +556,7 @@ struct DFAAnalyzer || (cParent.truthiness != Truthiness.Unknown && cFalse.truthiness == Truthiness.Unknown)) { - contextVar.unmodellable = true; + contextVar.markUnmodellable(); cFalse.truthiness = Truthiness.Unknown; } @@ -373,7 +564,7 @@ struct DFAAnalyzer || (cParent.nullable != Nullable.Unknown && cFalse.nullable == Nullable.Unknown)) { - contextVar.unmodellable = true; + contextVar.markUnmodellable(); cFalse.nullable = Nullable.Unknown; } } @@ -408,8 +599,7 @@ struct DFAAnalyzer // Converge the false branch assuming that it will always have executed. // This covers scenario 1. - bothJumped = scrFalse.isNull ? false : scrFalse.sc.haveJumped; - bothReturned = scrFalse.isNull ? false : scrFalse.sc.haveReturned; + bothJumped = scrFalse.controlFlowJumped; protectLoopAgainst(scrFalse); convergeScope(scrFalse); @@ -459,36 +649,37 @@ struct DFAAnalyzer } */ - bothJumped = scrTrue.sc.haveJumped && scrFalse.sc.haveJumped; - bothReturned = scrTrue.sc.haveReturned && scrFalse.sc.haveReturned; + bothJumped = ifBothJumpedControlFlow(scrTrue, scrFalse); - if (scrTrue.sc.haveJumped == scrFalse.sc.haveJumped) + if ((scrTrue.controlFlowJumped > 0) == (scrFalse.controlFlowJumped > 0)) { // In scenario d we've both jumped, any effects at that time were already copied to the jump point. // Nothing for us to do in that scenario. - if (scrTrue.sc.haveJumped) + if (bothJumped != 0) { // Both jumped, no state to converge. } + else { // In scenario a, neither has jumped, any effects at the end of their bodies should be meet'd. // Then we can converge that into parent scope. protectGate; - DFAScopeRef meeted = meetScope(scrTrue, scrFalse, true); + DFAScopeRef meeted = meetScope(scrTrue, scrFalse, gateConsequence !is null); assert(!meeted.isNull); + protectLoopAgainst(meeted); convergeScope(meeted); } } - else if (scrTrue.sc.haveJumped) + else if (scrTrue.controlFlowJumped > 0) { // In scenario b the true branch has jumped, but not the false. // Any effects seen at the true branch have been already copied to the jump point. protectLoopAgainst(scrFalse); convergeScope(scrFalse); } - else if (scrFalse.sc.haveJumped) + else if (scrFalse.controlFlowJumped > 0) { // In scenario c, the false branch has jumped, but not the true. // Any effects seen at the false branch have been already copied to the jump point. @@ -504,7 +695,7 @@ struct DFAAnalyzer // We have a true branch with user code, but not a false branch. // The false branch contains the effects of the negated condition. - if (scrTrue.sc.haveJumped) + if (scrTrue.controlFlowJumped > 0) { /* The true branch jumped, which means only the false branch matters. @@ -548,14 +739,21 @@ struct DFAAnalyzer // It may appear to be a great idea to change this, but I can assure you, // without accepting a false positive or two or three you will not be changing it from this. // LEAVE THIS ALONE! - convergeStatement(scrTrue, false, true); + /* + One of the problems is the following code: + Thing* thing; + if (param1.cond) + thing = new Thing; + if (cast(Something)param2) + assert(thing !is null); + Well we don't have a gate consequence, so therefore we must converge the first if statement down to unknown, rather than null. + But if we did have a gate consequence, we can then go for null instead, as that'll upgrade if you use it again. + */ + convergeStatementFromBranch(scrTrue, false, gateConsequence !is null); } } - if (bothJumped) - dfaCommon.currentDFAScope.haveJumped = true; - if (bothReturned) - dfaCommon.currentDFAScope.haveReturned = true; + dfaCommon.currentDFAScope.controlFlow |= bothJumped; } void applyGateOnBranch(DFAVar* gateVar, int predicateNegation, bool branch) @@ -648,12 +846,11 @@ struct DFAAnalyzer convergeScope(containing); else { - if (containing.sc.haveReturned) - dfaCommon.currentDFAScope.haveReturned = true; - convergeStatement(containing, false, true); + dfaCommon.currentDFAScope.controlFlow |= containing.controlFlowJumped; + convergeStatementFromBranch(containing, true, false); } - convergeStatement(afterScopeState, false); + convergeScope(afterScopeState); } void loopyLabelBeforeContainingCheck(ref DFAScopeRef containing, @@ -797,7 +994,7 @@ struct DFAAnalyzer if (scr.isNull) return; - const jumped = scr.sc.haveJumped, returned = scr.sc.haveReturned; + const jumped = scr.controlFlowJumped; DFALatticeRef lr; DFAScopeVarMergable mergable; @@ -830,15 +1027,11 @@ struct DFAAnalyzer dfaCommon.check; } - if (jumped) - dfaCommon.currentDFAScope.haveJumped = true; - if (returned) - dfaCommon.currentDFAScope.haveReturned = true; + dfaCommon.currentDFAScope.controlFlow |= jumped; } - void convergeStatement(DFAScopeRef scr, bool allBranchesTaken = true, - bool couldScopeNotHaveRan = false, bool ignoreWriteCount = false, - bool unknownAware = false) + void convergeStatementFromBranch(DFAScopeRef scr, bool couldScopeNotHaveRan, + bool predictableBranch) { const depth = dfaCommon.currentDFAScope.depth; @@ -864,9 +1057,8 @@ struct DFAAnalyzer assert(lrCtx is var); // make sure it exists in parent - DFALatticeRef lr = allBranchesTaken ? join(lattice, lr, depth, - null, couldScopeNotHaveRan || ignoreWriteCount, unknownAware) : meet(lattice, - lr, depth, couldScopeNotHaveRan); + DFALatticeRef lr = meet(lattice, lr, depth, + couldScopeNotHaveRan, false, !predictableBranch); assert(!lr.isNull); lr.check; @@ -879,16 +1071,15 @@ struct DFAAnalyzer } } - void convergeFunctionCallArgument(DFALatticeRef lr, ParameterDFAInfo* paramInfo, - ulong paramStorageClass, FuncDeclaration calling, ref Loc loc) + void transferFunctionCallArgument(DFALatticeRef lr, + DFAArgumentList.Each* argListItem, FuncDeclaration calling, ref Loc loc) { - - const isByRef = (paramStorageClass & (STC.ref_ | STC.out_)) != 0; - const couldEscape = (paramStorageClass & STC.scope_) == 0 - || (paramStorageClass & (STC.return_ | STC.returnScope | STC.returnRef)) != 0; + auto paramInfo = argListItem.paramInfo; + const isByRef = paramInfo !is null ? paramInfo.isByRef : false; + const couldEscape = paramInfo !is null ? (!paramInfo.escapeIntoNothing) : true; // A function call argument, may initialize the parameter if its by-ref or if its the this pointer. - this.onRead(lr, loc, paramInfo is null || paramInfo.parameterId == -1 || isByRef, isByRef); + this.onRead(lr, loc, isByRef, isByRef); DFAVar* ctx; DFAConsequence* cctx = lr.getContext(ctx); @@ -908,11 +1099,11 @@ struct DFAAnalyzer // Or if the parameter is by-ref, then we must consider the output state of the parameter. if (isByRef || var.haveBase) { - var.walkRoots((root) { + var.walkRoots((DFAVar* root) { // If the var could be escaped out, forget about reporting on it ever again // If we had escape analysis we could temporarily disable it, but that isn't implemented. if (couldEscape) - root.unmodellable = true; + root.markUnmodellable(); DFAConsequence* rootCctx = lr.findConsequence(root); @@ -978,7 +1169,8 @@ struct DFAAnalyzer seeObject(cctx.obj); } - this.convergeExpression(lr, true); + this.convergeExpression(lr.copy, true); + argListItem.lr = lr; } void transferAssert(DFALatticeRef lr, ref Loc loc, bool ignoreWriteCount, @@ -1025,16 +1217,18 @@ struct DFAAnalyzer version (none) { - printf("asserting %d %p %d:%d %d:%d\n", ignoreWriteCount, c.var, c.var.writeCount, - c.writeOnVarAtThisPoint, currentDepth, c.var.declaredAtDepth); + printf("asserting %d %p %d:%d %d %d<%d\n", ignoreWriteCount, + c.var, c.var.writeCount, c.writeOnVarAtThisPoint, + currentDepth, c.var.oldestLifeTimeAllowedDepth, + c.var.youngestLifeTimeAllowedDepth); printf(" %d && %d\n", !ignoreWriteCount, c.var.writeCount > c.writeOnVarAtThisPoint); - printf(" %d <\n", currentDepth < c.var.declaredAtDepth); + printf(" %d <\n", currentDepth < c.var.oldestLifeTimeAllowedDepth); printf(" write on var %d vs %d\n", c.var.writeCount, c.writeOnVarAtThisPoint); } - if (currentDepth < c.var.declaredAtDepth || (!ignoreWriteCount - && c.var.writeCount > c.writeOnVarAtThisPoint)) + if (currentDepth < c.var.oldestLifeTimeAllowedDepth + || (!ignoreWriteCount && c.var.writeCount > c.writeOnVarAtThisPoint)) return; if ((scv = dfaCommon.lastLoopyLabel.findScopeVar(c.var)) !is null) @@ -1202,10 +1396,41 @@ struct DFAAnalyzer if (assignToCtx !is null) { + if (assignToCtx.mayBeGlobal && !noLR) + reporter.onGlobalEscape(assignToCtx, lrCctx.obj, loc); + // *var = expr; // is very different from: // var = expr; + if (lrCctx !is null && lrCctx.obj !is null) + { + if (construct && assignToCtx.isByRef) + { + // By-ref variables that are not parameters (parameters won't call this transfer function), + // will obtain their object from the initializer expression. + + // int other; + // ref var = other; + + // int* other; + // ref int* var = other; + + assignToCtx.storageFor = dfaCommon.makeObject(lrCctx.obj); + } + + if (assignToCtx.isScope && (lrCctx.obj.minimumDeclaredAtDepth == -1 + || lrCctx.obj.minimumDeclaredAtDepth > dfaCommon.currentDFAScope.depth)) + { + // Adjust object to take into account scope variables + + lrCctx.obj = dfaCommon.makeObject(lrCctx.obj); + lrCctx.obj.minimumDeclaredAtDepth = dfaCommon.currentDFAScope.depth; + lrCctx.obj.onTheStack = true; + lrCctx.obj.constrainedBy = assignToCtx; + } + } + bool wasDereferenced; assignToCtx.visitIfReferenceToAnotherVar((contextVar) { @@ -1241,7 +1466,7 @@ struct DFAAnalyzer ret.setContext(cctx); if (!construct && noLR) - assignToCtx.unmodellable = true; + assignToCtx.markUnmodellable(); } } else @@ -1325,7 +1550,8 @@ struct DFAAnalyzer { if (firstRoot !is null) { - objRoot.storageFor.unmodellable = true; + objRoot.storageFor.markUnmodellable(); + DFAConsequence* cUnk = ret.addConsequence(objRoot.storageFor); cUnk.truthiness = Truthiness.Unknown; cUnk.nullable = Nullable.Unknown; @@ -1349,7 +1575,8 @@ struct DFAAnalyzer } else if (rootCount > 1) { - firstRoot.unmodellable = true; + firstRoot.markUnmodellable(); + DFAConsequence* cUnk = ret.addConsequence(firstRoot); cUnk.truthiness = Truthiness.Unknown; cUnk.nullable = Nullable.Unknown; @@ -1365,7 +1592,8 @@ struct DFAAnalyzer lrCctx.obj.walkRoots((objRoot) { if (objRoot.storageFor !is null && !objRoot.storageFor.haveBase) { - objRoot.storageFor.unmodellable = true; + objRoot.storageFor.markUnmodellable(); + DFAConsequence* cUnk = ret.addConsequence(objRoot.storageFor); cUnk.truthiness = Truthiness.Unknown; cUnk.nullable = Nullable.Unknown; @@ -1387,7 +1615,7 @@ struct DFAAnalyzer } if (unmodellable) - assignToCtx.unmodellable = true; + assignToCtx.markUnmodellable(); } return ret; @@ -1535,58 +1763,11 @@ struct DFAAnalyzer DFALatticeRef transferDereference(DFALatticeRef lr, ref Loc loc) { - DFAConsequence* ctx = lr.getContext; - bool couldBeNull; + DFAVar* contextVar = lr.getContextVar; - if (ctx !is null) - { - DFAVar* var = ctx.var; - - if (var !is null) - { - DFAScopeVar* scv; - - if ((scv = dfaCommon.lastLoopyLabel.findScopeVar(var)) !is null) - { - if (scv.derefDepth == 0 || scv.derefDepth > dfaCommon.currentDFAScope.depth) - scv.derefDepth = dfaCommon.currentDFAScope.depth; - } - - if ((scv = dfaCommon.currentDFAScope.findScopeVar(var)) !is null) - { - if (var.writeCount > 0 && scv.mergable.nullAssignAssertedCount == var.assertedCount - && scv.mergable.nullAssignWriteCount == var.writeCount - && var.var !is null) - couldBeNull = true; - } - - var = dfaCommon.findDereferenceVar(var); - lr.setContext(var); - } - - if (ctx.nullable == Nullable.Unknown) - { - // Infer input nullability - - if (couldBeNull) - reporter.onDereference(ctx, loc); - else if (ctx.var !is null && ctx.var.param !is null - && !ctx.var.param.specifiedByUser && !dfaCommon.currentDFAScope.inConditional - && ctx.writeOnVarAtThisPoint == 1 && ctx.var.assertedCount == 0) - { - if (dfaCommon.debugIt) - printf("Infer variable as non-null `%s` at %s\n", - ctx.var.var.ident.toChars, loc.toChars); - - ctx.nullable = Nullable.NonNull; - if (!ctx.var.doNotInferNonNull) - ctx.var.param.notNullIn = ParameterDFAInfo.Fact.Guaranteed; - } - } - else if (ctx.nullable == Nullable.Null) - reporter.onDereference(ctx, loc); - } + // Does not call seeDereference, this is handled via onRead + lr.setContext(dfaCommon.findDereferenceVar(contextVar)); return lr; } @@ -1782,24 +1963,6 @@ struct DFAAnalyzer { if (equalityIsTrue) { - // var is !null -> var non-null - // !null is var -> var non-null - - // if lhs or rhs is constant - // if lhs or rhs is non-constant && lhs or rhs !is null - // upgrade the non-constant to !is null - - if (lhsVar is null && rhsVar !is null && lhsCctx.nullable == Nullable.Null) - { - if (rhsCctx.nullable == Nullable.NonNull) - rhsCctx.nullable = Nullable.NonNull; - } - else if (lhsVar !is null && rhsVar is null && rhsCctx.nullable == Nullable.Null) - { - if (lhsCctx.nullable == Nullable.NonNull) - lhsCctx.nullable = Nullable.NonNull; - } - ret.walkMaybeTops((c) => c.protectElseNegate = true); } else @@ -2127,7 +2290,7 @@ struct DFAAnalyzer { assert(!next.isNull); - if (next.sc.haveJumped || next.sc.haveReturned) + if (next.controlFlowJumped > 0) { /* Handles the case where you have: @@ -2147,27 +2310,146 @@ struct DFAAnalyzer if (previous.isNull) return next; - const allJumped = previous.sc.haveJumped && next.sc.haveJumped, - allReturned = previous.sc.haveReturned && next.sc.haveReturned; - - DFAScopeRef ret = meetScope(previous, next, true, true); + const allJumped = ifBothJumpedControlFlow(previous, next); + DFAScopeRef ret = meetScope(previous, next); assert(!ret.isNull); - if (allJumped) - ret.sc.haveJumped = true; - if (allReturned) - ret.sc.haveReturned = true; - + ret.sc.controlFlow |= allJumped; return ret; } + void transferAddressOf(ref DFALatticeRef lr) + { + //&( base T + // storage = storage + //&( base T* + // obj1 = storage + //&( ref base T + // storage = storage + //&( base T . field T + // storage storage = storage + //&( base T* . field T + // obj1 obj1 = obj1 + //&( base T* . field T* + // obj1 obj2 = obj1 + + DFAVar* ctx; + DFAConsequence* cctx = lr.getContext(ctx); + + if (cctx is null) + return; + + DFAObject* resultObj; + bool unknown, allDeclaredOnStack = true; + + ctx.visitIndirectSources((DFAVar* var, bool hadAnIndirection, bool hadAnOuterDeref, + bool takenAddressOf, bool hadFields, bool isOffsetOfStorage, ref bool unknown) { + version (none) + { + printf("found storage %p, hadAnIndirection=%d, hadAnOuterDeref=%d, takenAddressOf=%d, hadFields=%d, isOffsetOfStorage=%d, unknown=%d\n", + var, hadAnIndirection, hadAnOuterDeref, takenAddressOf, + hadFields, isOffsetOfStorage, unknown); + } + + // Storage consequence may not have a object available for it. + // If we haven't got a base on the variable that tells us + // that this variable can have storage associated with it. + + DFAConsequence* cctx = lr.findConsequence(var); + + DFAObject* storage; + + if ((hadFields || hadAnIndirection) && var.isNullable && !var.isAA) + { + // Taking a pointer *into* the object + // AA's won't however take a pointer into + + if (cctx !is null && cctx.obj !is null) + { + // we know what the object is + + storage = dfaCommon.makeObject(); + + // hadFields looks like &foo.bar.dar where bar is a non-nullable type like a struct instance. + // hadAnIndirection looks similar, except bar is a pointer. + // If we didn't have an indirection then it doesn't matter if we had fields. + + if (!hadAnIndirection) + storage.constrainedBy = var; + + storage.derivedFrom = cctx.obj; + } + else + { + // We didn't get an object, and yeah we should've + unknown = true; + return; + } + } + else + { + // Taking a pointer for the variable + storage = dfaCommon.makeObject(var); + } + + if (isOffsetOfStorage || (!hadAnIndirection && var.isByRef)) + { + // ref a = ...; auto b = ...; + // &(cond ? a : b) + // ref T c = ...; + // &c.field + // where T is value type + + storage = dfaCommon.makeInCellObject(storage); + } + + DFAObject* o = storage; + + if (hadAnIndirection || hadAnOuterDeref) + { + o = dfaCommon.makeObject(); + o.derivedFrom = storage; + o.minimumDeclaredAtDepth = storage.minimumDeclaredAtDepth; + + allDeclaredOnStack = false; + } + else if (allDeclaredOnStack) + allDeclaredOnStack = var.oldestLifeTimeAllowedDepth > 0; + + if (resultObj !is null) + resultObj = dfaCommon.makeObject(resultObj, o); + else + resultObj = o; + }); + + DFAVar* varOffset = dfaCommon.findAddressOfVar(ctx); + cctx = lr.setContext(varOffset); + cctx.obj = unknown ? null : resultObj; + + if (cctx.obj !is null && allDeclaredOnStack) + { + cctx.truthiness = Truthiness.True; + cctx.nullable = Nullable.NonNull; + } + } + + DFALatticeRef transferThrow(DFALatticeRef lr, ref const Loc loc) + { + DFAConsequence* cctx = lr.getContext; + + if (cctx !is null) + reporter.onThrowEscape(cctx.obj, loc); + + return lr; + } + void onRead(ref DFALatticeRef lr, ref Loc loc, bool willInit = false, bool isByRef = false, bool forMathOp = false) { version (none) { - lr.printStructure("READ READ READ"); printf("READ READ value %d %d\n", willInit, isByRef); + lr.printStructure("READ READ READ"); } // Called by statement, expressions and analysis. @@ -2184,7 +2466,7 @@ struct DFAAnalyzer { version (none) { - printf("root object %p var %p\n", root, root.storageFor); + printf("root indirect object %p var %p\n", root, root.storageFor); } // Don't apply effects from a variable that isn't actually modelled i.e. indirection. @@ -2196,104 +2478,82 @@ struct DFAAnalyzer if (cctx !is null && cctx.writeOnVarAtThisPoint == 0) reporter.onReadOfUninitialized(root.storageFor, lr, loc, forMathOp); - - if (toStore.isNull) - toStore = lr; - else - toStore = meet(toStore, lr); } } - if (willInit) - { - // We differentiate if it is some form of initialize either lhs or rhs and isByRef because initialization of a struct member - // does not necessarily read the struct itself (offsets are fine), whereas ref parameters - // likely imply a read or dependency that requires initialization. - - // When initializing (writing), we only care if we are reading a variable to determine the address. - // e.g. `*ptr = 2` reads `ptr`. `struct.field = 2` does NOT read `struct`. - // `visitIfReadOfReferenceToAnotherVar` implements this logic (dereferences read base, offsets do not). + contextVar.visitIndirectSources((DFAVar* root, bool hadAnIndirection, bool hadAnOuterDeref, + bool takenAddressOf, bool hadFields, bool isOffsetOfStorage, ref bool unknown) { + version (none) + { + printf("got a variable %p %p hadAnIndirection=%d, hadAnOuterDeref=%d, takenAddressOf=%d, hadFields=%d, isOffsetOfStorage=%d, unknown=%d\n", + root, root.var, hadAnIndirection, hadAnOuterDeref, + takenAddressOf, hadFields, isOffsetOfStorage, unknown); + printf(" %d %d %d\n", root.haveBase, root.unmodellable, + root.haveInfiniteLifetime); + } - contextVar.visitIfReadOfReferenceToAnotherVar((DFAVar* root) { - if (root.var is null || root is contextVar || root.unmodellable - || root.haveInfiniteLifetime) - return; + if (root.isNullable && (hadAnIndirection || hadFields + || isOffsetOfStorage || (hadAnOuterDeref && !takenAddressOf))) + seeDereference(lr, root, loc); - DFAConsequence* c = lr.findConsequence(root); - if (c is null) - return; + DFAConsequence* c = lr.findConsequence(root); + if (c is null || root.unmodellable || root.haveInfiniteLifetime) + return; + else if ((willInit || isByRef || takenAddressOf) && !hadAnIndirection) + { + // by-ref parameters may initialize and act similarly to initialization/assignments - if (c.writeOnVarAtThisPoint == 0) - reporter.onReadOfUninitialized(root, lr, loc, forMathOp); - }); - } - else if (isByRef) - { - // For ref params, we generally require initialization unless it's 'out', but 'out' should be handled elsewhere/differently? - // Existing logic assumes 'ref' requires init. + //&( base T . field T + // storage storage = storage + // var.field = 2; // ok - contextVar.visitIfReferenceToAnotherVar((DFAVar* root) { - if (root.var is null || root is contextVar || root.unmodellable - || root.haveInfiniteLifetime) - return; + //&( base T* . field T* . field T + // obj1 obj2 obj2 = obj2 + // ptr.var.field = 2; // need to check on ptr - DFAConsequence* c = lr.findConsequence(root); - if (c is null) - return; + // &var ok + // &ptr.field not ok - if (c.writeOnVarAtThisPoint == 0) - reporter.onReadOfUninitialized(root, lr, loc, forMathOp); - else if (c.obj !is null) - c.obj.walkRoots(&seeRootObject); - }); - } - else - { - // We don't care about the case where indirection is involved. - // int val1 = void; - // int* ptr = &val1; - // int val2 = *ptr; // no error + return; + } - if (!contextVar.haveBase) + version (none) { - if (contextVar.var !is null && cctx.writeOnVarAtThisPoint == 0) - reporter.onReadOfUninitialized(contextVar, lr, loc, forMathOp); + printf(" %p %p\n", c.obj, contextVar); } - contextVar.visitIfReadOfReferenceToAnotherVar((DFAVar* root) { - version (none) - { - printf("got a variable %p %p\n", root, root.var); - printf(" %d %d %d\n", root.haveBase, root.unmodellable, - root.haveInfiniteLifetime); - } - - if (root.haveBase || root.unmodellable || root.haveInfiniteLifetime) - return; + if (c.obj !is null && root.var !is null && root !is contextVar) + reporter.onDelayEscapeOfObject(root, c.obj, loc); - DFAConsequence* c = lr.findConsequence(root); - if (c is null) - return; + if (root.haveBase) + return; - version (none) - { - printf(" %d\n", c.writeOnVarAtThisPoint); - } + version (none) + { + printf(" %d\n", c.writeOnVarAtThisPoint); + } - if (root.var !is null && c.writeOnVarAtThisPoint == 0) - reporter.onReadOfUninitialized(root, lr, loc, forMathOp); - else if (c.obj !is null) + if (root.var !is null && c.writeOnVarAtThisPoint == 0) + reporter.onReadOfUninitialized(root, lr, loc, forMathOp); + else if (c.obj !is null) + { + if (hadAnIndirection || hadFields || hadAnOuterDeref) c.obj.walkRoots(&seeRootObject); - }); + } + }); + } - cctx.copyFrom(toStore.getContext); - } + void onWriteViaLR(ref DFALatticeRef lr, ref Loc loc) + { + DFAVar* ctx = lr.getContextVar; + + if (ctx !is null) + this.seeWrite(ctx, lr); } private: - DFAScopeRef meetScope(DFAScopeRef scr1, DFAScopeRef scr2, bool copyAll, - bool couldScopeNotHaveRan = false) + DFAScopeRef meetScope(DFAScopeRef scr1, DFAScopeRef scr2, bool haveGateVariable = false) { scr1.check; scr2.check; @@ -2303,8 +2563,7 @@ private: DFAScopeRef ret; ret.sc = dfaCommon.allocator.makeScope(dfaCommon, dfaCommon.currentDFAScope); ret.sc.depth = depth; - ret.sc.haveJumped = scr1.sc.haveJumped && scr2.sc.haveJumped; - ret.sc.haveReturned = scr1.sc.haveReturned && scr2.sc.haveReturned; + ret.sc.controlFlow |= ifBothJumpedControlFlow(scr1, scr2); DFALatticeRef lr1, lr2; DFAScopeVarMergable mergable1, mergable2; @@ -2318,13 +2577,10 @@ private: lr2 = scr2.consumeVar(var, mergable2); - if (!copyAll && lr2.isNull) - continue; - lr1.check; lr2.check; - DFALatticeRef meeted = meet(lr1, lr2, depth, couldScopeNotHaveRan); + DFALatticeRef meeted = meet(lr1, lr2, depth, false, true, !haveGateVariable); assert(!meeted.isNull); meeted.check; @@ -2333,18 +2589,15 @@ private: scv.mergable.merge(mergable2); } - if (copyAll) + for (;;) { - for (;;) - { - lr2 = scr2.consumeNext(var, mergable2); - if (lr2.isNull) - break; + lr2 = scr2.consumeNext(var, mergable2); + if (lr2.isNull) + break; - lr2.check; - DFAScopeVar* scv = ret.assignLattice(var, lr2); - scv.mergable.merge(mergable2); - } + lr2.check; + DFAScopeVar* scv = ret.assignLattice(var, lr2); + scv.mergable.merge(mergable2); } ret.check; @@ -2361,8 +2614,7 @@ private: DFAScopeRef ret; ret.sc = dfaCommon.allocator.makeScope(dfaCommon, dfaCommon.currentDFAScope); ret.sc.depth = depth; - ret.sc.haveJumped = scr1.sc.haveJumped && scr2.sc.haveJumped; - ret.sc.haveReturned = scr1.sc.haveReturned && scr2.sc.haveReturned; + ret.sc.controlFlow |= ifBothJumpedControlFlow(scr1, scr2); DFALatticeRef lr1, lr2; DFAScopeVarMergable mergable1, mergable2; @@ -2399,8 +2651,9 @@ private: * Example: If `x` is NonNull in path A AND `x` is NonNull in path B, * then `x` is NonNull. */ - DFALatticeRef meet(DFALatticeRef lhs, DFALatticeRef rhs, - int filterDepthOfVariable = 0, bool couldScopeNotHaveRan = false) + DFALatticeRef meet(DFALatticeRef lhs, DFALatticeRef rhs, int filterDepthOfVariable = 0, + bool couldScopeNotHaveRan = false, bool ignoreWriteCount = false, + bool preferUnknown = false) { DFALatticeRef ret = dfaCommon.makeLatticeRef; DFAConsequence* constantRHS = rhs.findConsequence(null); @@ -2419,17 +2672,19 @@ private: version (DebugJoinMeetOp) { - printf("meet lhs %p %d\n", c1.var, c1.var.declaredAtDepth); + printf("meet lhs %p %d<%d\n", c1.var, c1.var.oldestLifeTimeAllowedDepth, + c1.var.youngestLifeTimeAllowedDepth); fflush(stdout); } - if (filterDepthOfVariable < c1.var.declaredAtDepth && filterDepthOfVariable > 0) + if (filterDepthOfVariable < c1.var.oldestLifeTimeAllowedDepth + && filterDepthOfVariable > 0) continue; DFAConsequence* result = ret.addConsequence(c1.var); DFAConsequence* c2 = rhs.findConsequence(c1.var); - result.meetConsequence(c1, c2, couldScopeNotHaveRan); + result.meetConsequence(c1, c2, couldScopeNotHaveRan, ignoreWriteCount, preferUnknown); if (c1.var is lhsVar && constantRHS !is null) { @@ -2444,11 +2699,13 @@ private: version (DebugJoinMeetOp) { - printf("meet rhs %p %d\n", c2.var, c2.var.declaredAtDepth); + printf("meet rhs %p %d<%d\n", c2.var, c2.var.oldestLifeTimeAllowedDepth, + c2.var.youngestLifeTimeAllowedDepth); fflush(stdout); } - if (filterDepthOfVariable < c2.var.declaredAtDepth && filterDepthOfVariable > 0) + if (filterDepthOfVariable < c2.var.oldestLifeTimeAllowedDepth + && filterDepthOfVariable > 0) continue; DFAConsequence* result = ret.addConsequence(c2.var); @@ -2492,7 +2749,8 @@ private: fflush(stdout); } - if (filterDepthOfVariable < c1.var.declaredAtDepth && filterDepthOfVariable > 0) + if (filterDepthOfVariable < c1.var.oldestLifeTimeAllowedDepth + && filterDepthOfVariable > 0) return; DFAConsequence* result = ret.addConsequence(c1.var); @@ -2548,7 +2806,8 @@ private: if (c2.var is null || ret.findConsequence(c2.var) !is null) continue; - if (filterDepthOfVariable < c2.var.declaredAtDepth && filterDepthOfVariable > 0) + if (filterDepthOfVariable < c2.var.oldestLifeTimeAllowedDepth + && filterDepthOfVariable > 0) continue; DFAConsequence* result = ret.addConsequence(c2.var); @@ -2592,12 +2851,19 @@ private: if (var is null) return; - if (var.declaredAtDepth > 0 && dfaCommon.lastLoopyLabel.depth > var.declaredAtDepth) + if (var.oldestLifeTimeAllowedDepth > 0 + && dfaCommon.lastLoopyLabel.depth > var.oldestLifeTimeAllowedDepth) couldBeUnknown = true; } - void seeWrite(ref DFAVar* assignTo, ref DFALatticeRef from) + void seeWrite(DFAVar* assignTo, ref DFALatticeRef from) { + version (none) + { + printf("seeWrite for %p\n", assignTo); + from.printStructure("lr1"); + } + assert(assignTo !is null); const currentDepth = dfaCommon.currentDFAScope.depth; @@ -2615,5 +2881,55 @@ private: DFAConsequence* c = from.addConsequence(root); c.writeOnVarAtThisPoint = root.writeCount; }); + + version (none) + { + from.printStructure("lr2"); + } } + + void seeDereference(ref DFALatticeRef lr, DFAVar* contextVar, ref Loc loc) + { + DFAConsequence* cctx = lr.findConsequence(contextVar); + if (cctx is null) + return; + + if (contextVar !is null) + { + DFAScopeVar* scv; + + if ((scv = dfaCommon.lastLoopyLabel.findScopeVar(contextVar)) !is null) + { + if (scv.derefDepth == 0 || scv.derefDepth > dfaCommon.currentDFAScope.depth) + scv.derefDepth = dfaCommon.currentDFAScope.depth; + } + } + + if (cctx.nullable == Nullable.Unknown) + { + // Infer input nullability + + if (contextVar !is null && contextVar.param !is null + && contextVar.param.inferred.notNullIn == Fact.Unspecified + && !dfaCommon.currentDFAScope.inConditional + && cctx.writeOnVarAtThisPoint == 1 && contextVar.assertedCount == 0) + { + if (dfaCommon.debugIt) + printf("Infer variable as non-null `%s` at %s\n", + contextVar.var.ident.toChars, loc.toChars); + + cctx.nullable = Nullable.NonNull; + if (!contextVar.doNotInferNonNull) + contextVar.param.inferred.notNullIn = Fact.Guaranteed; + } + } + else if (cctx.nullable == Nullable.Null) + reporter.onDereference(cctx, loc); + } +} + +uint ifBothJumpedControlFlow(ref DFAScopeRef scr1, ref DFAScopeRef scr2) +{ + const cf1 = scr1.controlFlowJumped, cf2 = scr2.controlFlowJumped; + return (cf1 > 0 && cf2 > 0) ? (cf1 | cf2) : 0; } diff --git a/compiler/src/dmd/dfa/fast/expression.d b/compiler/src/dmd/dfa/fast/expression.d index 990e1f27d289..d5a55f4c80a0 100644 --- a/compiler/src/dmd/dfa/fast/expression.d +++ b/compiler/src/dmd/dfa/fast/expression.d @@ -55,31 +55,55 @@ struct ExpressionWalker void seeConvergeExpression(DFALatticeRef lr, bool isSideEffect = false) { dfaCommon.printStateln("Converging expression:"); - lr.printState("", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("", dfaCommon.sdepth, dfaCommon.edepth); lr.check; dfaCommon.check; analyzer.convergeExpression(lr, isSideEffect); dfaCommon.check; - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; } - void seeConvergeFunctionCallArgument(DFALatticeRef lr, ParameterDFAInfo* paramInfo, - ulong paramStorageClass, FuncDeclaration calling, ref Loc loc) + void seeConvergeFunctionCall(ref DFALatticeRef ret, + ParameterDFAInfo* retParamInfo, DFAArgumentListRef alr, ref Loc loc) + { + dfaCommon.printStateln("Converging function call:"); + ret.printState("return", dfaCommon.sdepth, dfaCommon.edepth); + ret.check; + + dfaCommon.printIfState((ref OutBuffer ob, scope PrintPrefixType prefix) { + printDFAParameter(retParamInfo); + + foreach (ubyte i, list; alr) + { + list.each[i].lr.printState("- ", dfaCommon.sdepth, dfaCommon.edepth); + list.each[i].lr.check; + printDFAParameter(list.each[i].paramInfo); + } + }); + + dfaCommon.check; + analyzer.convergeFunctionCall(ret, retParamInfo, alr, loc); + dfaCommon.check; + + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); + dfaCommon.currentDFAScope.check; + } + + void seeFunctionCallArgument(DFALatticeRef lr, + DFAArgumentList.Each* argListItem, FuncDeclaration calling, ref Loc loc) { dfaCommon.printStateln("Converging argument:"); - lr.printState("", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("", dfaCommon.sdepth, dfaCommon.edepth); lr.check; dfaCommon.check; - analyzer.convergeFunctionCallArgument(lr, paramInfo, paramStorageClass, calling, loc); + analyzer.transferFunctionCallArgument(lr, argListItem, calling, loc); dfaCommon.check; - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; } @@ -115,11 +139,11 @@ struct ExpressionWalker { dfaCommon.printState((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.printf( "Assigning construct?%d:%d %d\n", construct, isBlit, alteredState)); - assignTo.printState("to", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + assignTo.printState("to", dfaCommon.sdepth, dfaCommon.edepth); assignTo.check; - lr.printState("from", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("from", dfaCommon.sdepth, dfaCommon.edepth); lr.check; - indexLR.printState("index", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + indexLR.printState("index", dfaCommon.sdepth, dfaCommon.edepth); indexLR.check; assert(analyzer !is null); @@ -129,10 +153,9 @@ struct ExpressionWalker dfaCommon.check; ret.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; return ret; } @@ -155,14 +178,14 @@ struct ExpressionWalker ob.writestring("\n"); }); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferEqual(lhs, rhs, truthiness, equalityType, false); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } @@ -184,27 +207,27 @@ struct ExpressionWalker ob.writestring("\n"); }); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferEqual(lhs, rhs, truthiness, equalityType, true); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } DFALatticeRef seeNegate(DFALatticeRef lr, Type type, bool protectElseNegate = false, bool limitToContext = false) { - lr.printState("Negate", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("Negate", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferNegate(lr, type, protectElseNegate, limitToContext); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } @@ -214,56 +237,63 @@ struct ExpressionWalker ob.printf("Math op %d", op); ob.writestring("\n"); }); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferMathOp(lhs, rhs, type, op); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } + void seeAddressOf(ref DFALatticeRef lr) + { + dfaCommon.printStateln("Address of"); + lr.printState("Address of", dfaCommon.sdepth, dfaCommon.edepth); + + dfaCommon.check; + analyzer.transferAddressOf(lr); + dfaCommon.check; + + lr.printState("result", dfaCommon.sdepth, dfaCommon.edepth); + } + DFALatticeRef seeDereference(ref Loc loc, DFALatticeRef lr) { dfaCommon.printStateln("Dereferencing"); - lr.printState("Dereference", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("Dereference", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferDereference(lr, loc); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } void seeAssert(DFALatticeRef lr, ref Loc loc, bool dueToConditional = false) { - lr.printState("Assert", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("Assert", dfaCommon.sdepth, dfaCommon.edepth); - dfaCommon.currentDFAScope.printState("scope", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("scope", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; dfaCommon.check; analyzer.transferAssert(lr, loc, false, dueToConditional); dfaCommon.check; - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; } void seeSilentAssert(DFALatticeRef lr, bool ignoreWriteCount = false, bool dueToConditional = false) { - lr.printState("Assert silent", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("Assert silent", dfaCommon.sdepth, dfaCommon.edepth); - dfaCommon.currentDFAScope.printState("scope", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("scope", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; Loc loc; @@ -271,24 +301,23 @@ struct ExpressionWalker analyzer.transferAssert(lr, loc, ignoreWriteCount, dueToConditional); dfaCommon.check; - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.currentDFAScope.check; } DFALatticeRef seeLogicalAnd(DFALatticeRef lhs, DFALatticeRef rhs) { dfaCommon.printStateln("Logical and:"); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); lhs.check; - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); rhs.check; dfaCommon.check; DFALatticeRef ret = analyzer.transferLogicalAnd(lhs, rhs); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); ret.check; return ret; } @@ -296,14 +325,14 @@ struct ExpressionWalker DFALatticeRef seeLogicalOr(DFALatticeRef lhs, DFALatticeRef rhs) { dfaCommon.printStateln("Logical or:"); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferLogicalOr(lhs, rhs); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } @@ -312,25 +341,19 @@ struct ExpressionWalker int predicateNegation) { dfaCommon.printStateln("Condition:"); - condition.printState("condition", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); - scrTrue.printState("true sc", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); - lrTrue.printState("true lr", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); - scrFalse.printState("false sc", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); - lrFalse.printState("false lr", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + condition.printState("condition", dfaCommon.sdepth, dfaCommon.edepth); + scrTrue.printState("true sc", dfaCommon.sdepth, dfaCommon.edepth); + lrTrue.printState("true lr", dfaCommon.sdepth, dfaCommon.edepth); + scrFalse.printState("false sc", dfaCommon.sdepth, dfaCommon.edepth); + lrFalse.printState("false lr", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferConditional(condition, scrTrue, lrTrue, scrFalse, lrFalse, unknownBranchTaken, predicateNegation); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); + dfaCommon.currentDFAScope.printState("so far", dfaCommon.sdepth, dfaCommon.edepth); ret.check; dfaCommon.currentDFAScope.check; @@ -340,21 +363,42 @@ struct ExpressionWalker DFALatticeRef seeGreaterThan(bool orEqualTo, DFALatticeRef lhs, DFALatticeRef rhs) { dfaCommon.printStateln(orEqualTo ? "Greater than or equal to:" : "Greater than:"); - lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); - rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("lhs", dfaCommon.sdepth, dfaCommon.edepth); + rhs.printState("rhs", dfaCommon.sdepth, dfaCommon.edepth); dfaCommon.check; DFALatticeRef ret = analyzer.transferGreaterThan(orEqualTo, lhs, rhs); dfaCommon.check; - ret.printState("result", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); + return ret; + } + + DFALatticeRef seeThrow(ref DFALatticeRef lr, ref const Loc loc) + { + dfaCommon.printStateln("Seeing throw of lr"); + lr.printState("throw lr", dfaCommon.sdepth, dfaCommon.edepth); + + dfaCommon.check; + DFALatticeRef ret = analyzer.transferThrow(lr, loc); + dfaCommon.check; + + ret.printState("result", dfaCommon.sdepth, dfaCommon.edepth); return ret; } + void seeWriteViaLR(ref DFALatticeRef lr, ref Loc loc) + { + dfaCommon.printStateln("Seeing write of lr"); + lr.printState("write lr", dfaCommon.sdepth, dfaCommon.edepth); + + analyzer.onWriteViaLR(lr, loc); + } + void seeRead(ref DFALatticeRef lr, ref Loc loc) { dfaCommon.printStateln("Seeing read of lr"); - lr.printState("read lr", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("read lr", dfaCommon.sdepth, dfaCommon.edepth); analyzer.onRead(lr, loc); } @@ -362,7 +406,7 @@ struct ExpressionWalker void seeReadMathOp(ref DFALatticeRef lr, ref Loc loc) { dfaCommon.printStateln("Seeing read of math op lr"); - lr.printState("read lr", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("read lr", dfaCommon.sdepth, dfaCommon.edepth); analyzer.onRead(lr, loc, false, false, true); } @@ -500,13 +544,28 @@ struct ExpressionWalker DFAConsequence* c = ret.addConsequence(var); ret.setContext(c); + + if (context !is null && context.var !is null && context.var.haveBase + && context.var.base1.dereferenceVar is context.var) + { + // (*base).field + + DFAConsequence* base = ret.findConsequence(context.var.base1); + + if (base !is null && base.obj !is null) + { + c.obj = dfaCommon.makeObject(); + c.obj.derivedFrom = base.obj; + } + } + return ret; case EXP.symbolOffset: - // similar to address except has a variable offset + // similar to address except has a integer offset auto soe = expr.isSymOffExp; DFALatticeRef ret; - DFAVar* varBase, varOffset; + DFAVar* varBase; if (auto vd = soe.var.isVarDeclaration) { @@ -518,22 +577,19 @@ struct ExpressionWalker if (ret.isNull) ret = dfaCommon.makeLatticeRef; - varOffset = dfaCommon.findOffsetVar(soe.offset, varBase); - assert(varOffset is null || varOffset.haveBase); - - DFAConsequence* c = ret.addConsequence(varOffset); - ret.setContext(c); + if (soe.offset != 0) + { + DFAVar* varOffset = dfaCommon.findOffsetVar(soe.offset, varBase); + assert(varOffset is null || varOffset.haveBase); - c.obj = dfaCommon.makeObject(varBase); + DFAConsequence* c = ret.addConsequence(varOffset); + ret.setContext(c); - if (varBase.declaredAtDepth > 0) - { - c.truthiness = Truthiness.True; - c.nullable = Nullable.NonNull; + c.obj = dfaCommon.makeObject(varBase); + c.obj.mayNotBeExactPointer = true; } - if (soe.offset != 0) - c.obj.mayNotBeExactPointer = true; + this.seeAddressOf(ret); } else if (auto fd = soe.var.isFuncDeclaration) { @@ -558,28 +614,10 @@ struct ExpressionWalker case EXP.address: // Similar to symbolOffset except is always at offset 0 auto ae = expr.isAddrExp; - DFALatticeRef ret = this.walk(ae.e1); if (!ret.isNull) - { - DFAVar* ctx = ret.getContextVar; - - if (ctx !is null) - { - DFAVar* varOffset = dfaCommon.findOffsetVar(0, ctx); - DFAConsequence* cctx = ret.setContext(varOffset); - - cctx.obj = dfaCommon.makeObject(ctx); - - if (ctx.declaredAtDepth > 0) - { - cctx.truthiness = Truthiness.True; - cctx.nullable = Nullable.NonNull; - } - } - } - + this.seeAddressOf(ret); return ret; case EXP.declaration: @@ -593,12 +631,38 @@ struct ExpressionWalker if (var.ident !is null) { dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("Declaring variable: %s\n", var.ident.toChars); + ob.printf("Declaring variable: %s stc %lld\n", + var.ident.toChars, var.storage_class); }); } DFAVar* dfaVar = dfaCommon.findVariable(var); - dfaVar.declaredAtDepth = dfaCommon.currentDFAScope.depth; + + if (dfaVar.isStackVar) + { + dfaVar.oldestLifeTimeAllowedDepth = dfaCommon.lastLoopyLabel.depth; + dfaVar.youngestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; + + if (auto ts = var.type.baseElemOf.isTypeStruct) + { + if (ts.sym.dtor !is null) + { + // This variable has cleanup generated for it, + // either due to it being a struct, + // or a static array of structs, + // this will pin it to this exact scope. + // In this case the lifetime is that of the current scope, + // otherwise it can default to the last loopy label, + // which maps to the codegen of compiler backends, + // and how the stack variables will overlap in storages. + + dfaVar.oldestLifeTimeAllowedDepth + = dfaCommon.currentDFAScope.depth; + // youngest lifetime allowed is already set + } + } + } + DFAScopeVar* scv = dfaCommon.acquireScopeVar(dfaVar); DFAConsequence* cctx = scv.lr.getContext; @@ -633,7 +697,7 @@ struct ExpressionWalker } // does this var escape another? Can't model that. - if ((var.storage_class & STC.ref_) == STC.ref_) + if (dfaVar.isByRef) markUnmodellable(ei.exp); DFALatticeRef lr = this.walk(ei.exp); @@ -664,6 +728,11 @@ struct ExpressionWalker break; } } + + // By-ref variables that are not parameters that don't already have a storage object can't be modelled, + // as we don't understand where its pointing to. + if (dfaVar.isByRef && dfaVar.storageFor is null) + dfaVar.markUnmodellable(); } } @@ -783,8 +852,39 @@ struct ExpressionWalker dfaCommon.printStateln("assign rhs"); DFALatticeRef rhs = this.walk(ae.e2); - DFALatticeRef ret = seeAssign(lhs, false, rhs, ae.loc, false, - lhsIndex !is null ? 3 : 0, indexLR); + DFALatticeRef ret; + + if (ae.e1.isSliceExp) + { + // These two branches should really be given their own transfer function, + // for the purposes of handling nullability, length checks ext. + + if (ae.memset == MemorySet.blockAssign) + { + // e1[] = X + // This is a copy similar to a memset. + // The effects, specifically the object could change if both are present. + ret = lhs; + + DFAConsequence* lhsCctx = ret.getContext, rhsCctx = rhs.getContext; + + if (lhsCctx.obj !is null && rhsCctx !is null && rhsCctx.obj !is null) + lhsCctx.obj = dfaCommon.makeObject(lhsCctx.obj, rhsCctx.obj); + else + lhsCctx.obj = null; + } + else + { + // e1[] = X[] + // This is a copy, and quite importantly indirection, which we don't model. + // But the effects won't change. + ret = lhs; + } + + this.seeWriteViaLR(ret, ae.loc); + } + else + ret = seeAssign(lhs, false, rhs, ae.loc, false, lhsIndex !is null ? 3 : 0, indexLR); if (ret.isNull) { @@ -805,10 +905,10 @@ struct ExpressionWalker // See Slice auto ie = expr.isIndexExp; - dfaCommon.printStateln("index lhs"); - DFALatticeRef lhs = this.walk(ie.e1); dfaCommon.printStateln("index rhs"); DFALatticeRef index = this.walk(ie.e2); + dfaCommon.printStateln("index lhs"); + DFALatticeRef lhs = this.walk(ie.e1); DFAVar* lhsCtx; DFAConsequence* lhsCctx = lhs.getContext(lhsCtx); @@ -822,6 +922,8 @@ struct ExpressionWalker { // Apply effects that this operation will have on to the lhs + lhsObject = lhsCctx.obj; + bool effectIndexBase = ie.indexIsInBounds; if (lhsType.isTypeAArray !is null) @@ -837,12 +939,6 @@ struct ExpressionWalker lhsCctx.truthiness = Truthiness.True; lhsCctx.nullable = Nullable.NonNull; } - - if (lhsCctx.obj !is null) - { - lhsObject = lhsCctx.obj; - lhsObject.mayNotBeExactPointer = true; - } } if (lhsCtx !is null && lhsCtx.isNullable) @@ -854,9 +950,13 @@ struct ExpressionWalker DFAVar* indexVar = dfaCommon.findIndexVar(lhsCtx); DFALatticeRef combined = this.seeLogicalAnd(lhs, index); + DFAConsequence* newCctx = combined.addConsequence(indexVar); combined.setContext(newCctx); + if (lhsObject !is null) + newCctx.obj = dfaCommon.makeInCellObject(lhsObject); + if (resultHasEffect && indexVar !is null) { if (indexVar.isTruthy) @@ -873,22 +973,33 @@ struct ExpressionWalker // See index auto se = expr.isSliceExp; + // It is important to evaluate the expressions in this order: + // Lower, Upper, Base + // Otherwise any side effects will not be seen. + + dfaCommon.printStateln("Slice [lwr"); + DFALatticeRef lower = this.walk(se.lwr); + dfaCommon.printStateln("Slice upr]"); + DFALatticeRef upper = this.walk(se.upr); dfaCommon.printStateln("Slice base"); DFALatticeRef base = this.walk(se.e1); + DFAVar* baseCtx; DFAConsequence* baseCctx = base.getContext(baseCtx); + DFAVar* asSliceVar = dfaCommon.findAsSliceVar(baseCtx); + if (se.lwr is null && se.upr is null) { // Equivalent to doing slice[] - DFAVar* asSliceVar = dfaCommon.findAsSliceVar(baseCtx); DFAConsequence* newCctx = base.addConsequence(asSliceVar); base.setContext(newCctx); if (baseCctx !is null && baseCctx.obj !is null) newCctx.obj = baseCctx.obj; - else + else if (baseCtx !is null && !baseCtx.haveBase) newCctx.obj = dfaCommon.makeObject(baseCtx); + return base; } @@ -896,11 +1007,6 @@ struct ExpressionWalker DFALatticeRef ret = base; { - dfaCommon.printStateln("Slice [lwr"); - DFALatticeRef lower = this.walk(se.lwr); - dfaCommon.printStateln("Slice upr]"); - DFALatticeRef upper = this.walk(se.upr); - DFAConsequence* lowerCctx = lower.getContext, upperCctx = upper.getContext; DFAPAValue newPA; @@ -932,18 +1038,17 @@ struct ExpressionWalker ret = seeDereference(se.loc, ret); } - DFAVar* indexVar = dfaCommon.findIndexVar(baseCtx); - DFAConsequence* newCctx = ret.addConsequence(indexVar); + DFAConsequence* newCctx = ret.addConsequence(asSliceVar); ret.setContext(newCctx); - if (indexVar !is null) + if (asSliceVar !is null) { - if (indexVar.isTruthy) + if (asSliceVar.isTruthy) newCctx.truthiness = expectedTruthiness; // If this expression were to succeed, the resulting slice will be non-null. // Thanks to the read barrier for bounds checking. - if (indexVar.isNullable) + if (asSliceVar.isNullable) newCctx.nullable = Nullable.NonNull; } @@ -1321,31 +1426,7 @@ struct ExpressionWalker case EXP.structLiteral: { auto sle = expr.isStructLiteralExp; - - if (sle.elements !is null) - { - foreach (ele; *sle.elements) - { - if (ele is null) - continue; - - if (auto ae = ele.isAddrExp) - { - if (auto sle2 = ae.e1.isStructLiteralExp) - { - if (sle.origin is sle2.origin) - continue; - } - } - - DFALatticeRef temp = this.walk(ele); - this.seeConvergeFunctionCallArgument(temp, null, 0, null, sle.loc); - } - } - - DFALatticeRef ret = dfaCommon.makeLatticeRef; - ret.acquireConstantAsContext; - return ret; + return this.callFunction(null, null, null, sle.elements, sle.loc, sle); } case EXP.tuple: @@ -1503,9 +1584,12 @@ struct ExpressionWalker case EXP.throw_: auto te = expr.isThrowExp; - dfaCommon.currentDFAScope.haveJumped = true; - dfaCommon.currentDFAScope.haveReturned = true; - return this.walk(te.e1); + + DFALatticeRef ret = this.walk(te.e1); + ret = this.seeThrow(ret, te.loc); + + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Thrown; + return ret; case EXP.type: // holds a type, known no-op @@ -1815,16 +1899,19 @@ struct ExpressionWalker case EXP.arrayLiteral: { auto ale = expr.isArrayLiteralExp; - DFALatticeRef ret = dfaCommon.makeLatticeRef; + + // Don't try to optimize this, we can't tell the different between: + // [new int] + // and + // [foo = new int] + DFALatticeRef ret = this.callFunction(null, null, null, ale.elements, ale.loc); if (ale.elements !is null && ale.elements.length > 0) { - foreach (ele; *ale.elements) - { - if (ele !is null) - this.seeConvergeFunctionCallArgument(seeExp(ele, - negated, 0), null, 0, null, ele.loc); - } + DFAObject* obj = ret.getContextObject(); + + if (obj is null) + obj = dfaCommon.makeObject; DFAVar* var = dfaCommon.getInfiniteLifetimeVariable; DFAConsequence* cctx = ret.addConsequence(var); @@ -1833,7 +1920,7 @@ struct ExpressionWalker cctx.truthiness = Truthiness.True; cctx.nullable = Nullable.NonNull; cctx.pa = DFAPAValue(ale.elements.length); - cctx.obj = dfaCommon.makeObject; + cctx.obj = obj; } else ret.acquireConstantAsContext(Truthiness.Maybe, Nullable.Null, null); @@ -2078,6 +2165,7 @@ struct ExpressionWalker } DFAVar* var = dfaCommon.findVariable(vd); + var.mayBeGlobal = !var.isStackVar; if (var !is null) { @@ -2085,11 +2173,11 @@ struct ExpressionWalker ret.check; assert(!ret.isNull); + DFAConsequence* cctx = ret.getContext; + assert(cctx !is null); + if (applyByTest != 0) { - DFAConsequence* cctx = ret.getContext; - assert(cctx !is null); - if (var.isNullable && cctx.nullable == Nullable.Unknown) cctx.nullable = applyByTest == 2 ? Nullable.NonNull : Nullable.Null; @@ -2250,8 +2338,7 @@ struct ExpressionWalker } seeRead(lhs, oe.e1.loc); - lhs.printState("or lhs", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + lhs.printState("or lhs", dfaCommon.sdepth, dfaCommon.edepth); stmtWalker.endScope; @@ -2286,8 +2373,7 @@ struct ExpressionWalker dfaCommon.printStructureln("Or expression rhs:"); // don't care about seeExpInOr here, can just do a normal walk DFALatticeRef rhs = this.walk(oe.e2); - rhs.printState("or rhs", dfaCommon.sdepth, - dfaCommon.currentFunction, dfaCommon.edepth); + rhs.printState("or rhs", dfaCommon.sdepth, dfaCommon.edepth); seeRead(rhs, oe.e2.loc); stmtWalker.endScope; @@ -2409,7 +2495,8 @@ struct ExpressionWalker } DFALatticeRef callFunction(FuncDeclaration toCallFunction, Expression thisPointer, - Expression argPrefix, Expressions* arguments, ref Loc loc) + Expression argPrefix, Expressions* arguments, ref Loc loc, + StructLiteralExp sleOrigin = null) { TypeFunction toCallFunctionType = toCallFunction !is null ? toCallFunction .type.isFunction_Delegate_PtrToFunction : null; @@ -2436,6 +2523,8 @@ struct ExpressionWalker } } + ParametersDFAInfo* calledFunctionInfo = ensureDFAParameters(toCallFunction); + if (toCallFunction !is null && toCallFunction.ident !is null) { dfaCommon.printState((ref OutBuffer ob, scope PrintPrefixType prefix) { @@ -2464,135 +2553,132 @@ struct ExpressionWalker else dfaCommon.printStateln("Calling function"); - const functionIsNoReturn = (toCallFunction !is null && toCallFunction.noreturn) || (toCallFunctionType !is null - && toCallFunctionType.next !is null - && toCallFunctionType.next.isTypeNoreturn !is null); + DFALatticeRef ret; + ParameterDFAInfo returnBuffer; + ParameterDFAInfo* returnInfo; - { - ParametersDFAInfo* calledFunctionInfo; - ParameterDFAInfo* thisPointerInfo; - ParameterDFAInfo* returnInfo; + DFAArgumentListRef alr = dfaCommon.makeArgumentListRef((thisPointer !is null) + ( + argPrefix !is null) + (arguments !is null ? arguments.length : 0)); - if (toCallFunction !is null && toCallFunction.parametersDFAInfo) - { - calledFunctionInfo = toCallFunction.parametersDFAInfo; - thisPointerInfo = &calledFunctionInfo.thisPointer; - returnInfo = &calledFunctionInfo.returnValue; - } + const thisPointerId = thisPointer !is null ? 0 : -1; + const argPrefixId = argPrefix !is null ? thisPointerId + 1 : -1; + const firstArgId = (thisPointer !is null) + (argPrefix !is null); + { stmtWalker.startScope; dfaCommon.currentDFAScope.sideEffectFree = true; - if (thisPointer !is null) + foreach (i, DFAArgumentList* list; alr) { - dfaCommon.printStateln("This:"); - DFALatticeRef thisExp = this.walk(thisPointer); + if (i == thisPointerId) + { + dfaCommon.printStateln("This:"); + DFALatticeRef thisExp = this.walk(thisPointer); + DFAVar* thisVar = thisExp.getContextVar; + DFAObject* thisObj = thisExp.getContextObject; - if (isTypeNullable(thisPointer.type)) - thisExp = this.seeDereference(thisPointer.loc, thisExp); + if (isTypeNullable(thisPointer.type)) + thisExp = this.seeDereference(thisPointer.loc, thisExp); - this.seeConvergeFunctionCallArgument(thisExp, thisPointerInfo, - 0, toCallFunction, thisPointer.loc); - } + list.each[i].paramInfo = &list.infoBuffer[i]; + ensureDFAParameter(-2, toCallFunction, toCallFunctionType, + list.each[i].paramInfo); - if (argPrefix !is null) - { - dfaCommon.printStateln("Argument prefix:"); - DFALatticeRef argPrefixLR = this.walk(argPrefix); - this.seeConvergeFunctionCallArgument(argPrefixLR, null, 0, - toCallFunction, argPrefix.loc); - } + this.seeFunctionCallArgument(thisExp, &list.each[i], + toCallFunction, thisPointer.loc); - if (arguments !is null && arguments.length > 0) - { - Parameter[] allParameters; - VarDeclaration[] allParameterVars; - ParameterDFAInfo[] allInfos = calledFunctionInfo !is null ? calledFunctionInfo.parameters - : null; + if (toCallFunction !is null && (toCallFunction.ident is Id.dtor + || toCallFunction.ident is Id.__xdtor || toCallFunction.ident is Id.__fieldDtor + || toCallFunction.ident is Id.__aggrDtor)) + { + // If we're calling a cleanup function, that tells us that we understand lifetimes. + // Allows us to delay errors on reads if a specific condition has occured. - if (toCallFunctionType !is null) - { - TypeFunction functionType = toCallFunctionType.toTypeFunction; + if (thisVar !is null && thisObj is null) + thisObj = dfaCommon.makeObject(thisVar); - if (functionType.parameterList.parameters !is null - && functionType.parameterList.parameters.length != 0) - { - allParameters = (*functionType.parameterList.parameters)[]; + if (thisObj !is null) + thisObj.lifeTimeUnderstood = true; } } + else if (i == argPrefixId) + { + dfaCommon.printStateln("Argument prefix:"); + DFALatticeRef argPrefixLR = this.walk(argPrefix); - if (toCallFunction !is null) + list.each[i].paramInfo = &list.infoBuffer[i]; + this.seeFunctionCallArgument(argPrefixLR, &list.each[i], + toCallFunction, argPrefix.loc); + } + else { - if (toCallFunction.parameters !is null && toCallFunction.parameters.length != 0) + const argOffset = i - firstArgId; + Expression arg = (*arguments)[argOffset]; + + if (arg is null) + continue; + else if (sleOrigin !is null) { - allParameterVars = (*toCallFunction.parameters)[]; + if (auto ae = arg.isAddrExp) + { + if (sleOrigin is ae.e1.isStructLiteralExp) + continue; + } } - } - - dfaCommon.printState((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.writestring("Calling function parameter info\n"); - - prefix(" "); - ob.printf("count=%d, calledFunctionInfo=%p:%d\n", cast(int)arguments.length, calledFunctionInfo, - calledFunctionInfo !is null ? cast(int)calledFunctionInfo.parameters.length : 0); - prefix(" "); - ob.printf("allParameters=%d, allParameterVars=%d\n", - cast(int)allParameters.length, cast(int)allParameterVars.length); - }); + list.each[i].paramInfo = &list.infoBuffer[i]; + ensureDFAParameter(cast(int) argOffset, toCallFunction, + toCallFunctionType, list.each[i].paramInfo); - foreach (i, arg; *arguments) - { - Parameter* param = i < allParameters.length ? &allParameters.ptr[i] : null; - VarDeclaration paramVar = i < allParameterVars.length - ? allParameterVars.ptr[i] : null; - const storageClass = (param !is null ? param.storageClass : 0) | (paramVar !is null - ? paramVar.storage_class : 0); - ParameterDFAInfo* info = i < allInfos.length ? &allInfos.ptr[i] : null; - - dfaCommon.printState((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("Argument %d, stc=%lld", cast(int)i, storageClass); - ob.writestring("\n"); - - if (info !is null) - { - ob.printf(" info %d:%d", info.notNullIn, info.notNullOut); - ob.writestring("\n"); - } + dfaCommon.printIfState((ref OutBuffer ob, scope PrintPrefixType prefix) { + printDFAParameter(list.each[i].paramInfo); }); DFALatticeRef argExp = this.walk(arg); - this.seeConvergeFunctionCallArgument(argExp, info, - storageClass, toCallFunction, loc); + this.seeFunctionCallArgument(argExp, &list.each[i], toCallFunction, loc); - if (dfaCommon.currentDFAScope.haveJumped) + if (dfaCommon.currentDFAScope.controlFlowJumped) break; } } stmtWalker.endScope; + } - { - DFALatticeRef ret = dfaCommon.makeLatticeRef; + { + returnInfo = &returnBuffer; + ensureDFAParameter(-3, toCallFunction, toCallFunctionType, returnInfo); - if (returnInfo !is null && returnInfo.notNullOut == Fact.Guaranteed) - ret.acquireConstantAsContext(Truthiness.True, - Nullable.NonNull, dfaCommon.makeObject); - else - ret.acquireConstantAsContext; + const functionIsNoReturn = (toCallFunction !is null && toCallFunction.noreturn) + || (toCallFunctionType !is null + && toCallFunctionType.next !is null + && toCallFunctionType.next.isTypeNoreturn !is null); - // If the function is no return, or it hasn't been semantically analysed yet, - // we'll assume it can't return. - // Otherwise we get false positives. - if (functionIsNoReturn) - { - dfaCommon.currentDFAScope.haveJumped = true; - dfaCommon.currentDFAScope.haveReturned = true; - } + ret = dfaCommon.makeLatticeRef; + DFAConsequence* returnConsequence; - return ret; + if (returnInfo.notNullOut == Fact.Guaranteed) + returnConsequence = ret.acquireConstantAsContext(Truthiness.True, + Nullable.NonNull, null); + else + returnConsequence = ret.acquireConstantAsContext; + + if (returnInfo.notNullOut != Fact.NotGuaranteed) + { + returnConsequence.obj = dfaCommon.makeObject(); + returnConsequence.obj.minimumDeclaredAtDepth = dfaCommon.currentDFAScope.depth; + returnConsequence.obj.defaultTrackObj = true; } + + // If the function is no return, or it hasn't been semantically analysed yet, + // we'll assume it can't return. + // Otherwise we get false positives. + if (functionIsNoReturn) + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.NoReturn; } + + this.seeConvergeFunctionCall(ret, returnInfo, alr, loc); + return ret; } DFALatticeRef concatenateTwo(BinExp be, int op, bool assign = false) @@ -2694,7 +2780,7 @@ struct ExpressionWalker { DFAVar* var = dfaCommon.findVariable(vd); if (var !is null) - var.unmodellable = true; + var.markUnmodellable(); } void perExpr(Expression expr) diff --git a/compiler/src/dmd/dfa/fast/report.d b/compiler/src/dmd/dfa/fast/report.d index 5257b1ed3399..68ac609404bc 100644 --- a/compiler/src/dmd/dfa/fast/report.d +++ b/compiler/src/dmd/dfa/fast/report.d @@ -23,7 +23,10 @@ import dmd.declaration; import dmd.astenums; import core.stdc.stdio; +/// alias Fact = ParameterDFAInfo.Fact; +/// +alias EscapedRelationship = ParameterDFAInfo.EscapedRelationship; /*********************************************************** * The interface for reporting DFA errors and warnings. @@ -64,7 +67,7 @@ struct DFAReporter fail = __LINE__; else { - if (on.var.declaredAtDepth >= dfaCommon.lastLoopyLabel.depth) + if (on.var.oldestLifeTimeAllowedDepth >= dfaCommon.lastLoopyLabel.depth) errorSink.error(loc, "Dereference on null variable `%s`", on.var.var.ident.toChars); else @@ -93,7 +96,80 @@ struct DFAReporter { // this is where we validate escapes, for a specific location - if (!dfaCommon.currentDFAScope.haveReturned) + // Step 1. for escape analysis reporting, see deltas + // If we've jumped via continue, throw, or goto, disable delta's check. + // Step 2. for escape analysis inferrence promote to stack + + DFAVar* returnVar = dfaCommon.getReturnVariable(); + + { + // 1. + + foreach (DFAVar* intoVar, DFALattice* intoLR; *dfaCommon.currentDFAScope) + { + version (none) + { + printf("intoVar %p\n", intoVar); + intoLR.printStructure("lr"); + } + + // We skip the return variable specifically as it may have junk values in it + if (!dfaCommon.currentDFAScope.controlFlowReturned && intoVar is returnVar) + continue; + else if (!intoVar.isNullable) + continue; + + // intoVar is a known location, so it won't be a global. + // If we have returned, then we also consider the return value. + + if (intoVar is returnVar || (intoVar.var !is null && intoVar.oldestLifeTimeAllowedDepth > 0)) + { + // We're going into a stack variable, lets make sure any object we're putting into it as <= for other stack variables. + + DFAConsequence* cctx = intoLR.context; + + if (cctx.obj !is null) + { + bool silenceFutureErrors = true; + + checkForEscapes(intoVar, returnVar, cctx.obj, silenceFutureErrors, loc); + + if (silenceFutureErrors) + cctx.obj = null; // silence future errors + } + } + } + } + + { + // 2. + + // DO NOT MERGE THIS TURNED ON + // TURN THIS OFF + // TURN THIS OFF + // TURN THIS OFF + // TURN THIS OFF + + dfaCommon.allocator.allStackVariables((DFAVar* var) { + if (var.unmodellable || var.param !is null) + return; + else if (var.var is null || !var.var.type.isTypeClass) + return; // only applies to classes on stack + + if (!var.mayEscapeWithoutIndirection) + { + version (none) + { + printf("infer as on stack %s at %s\n", + var.var.ident.toChars, var.var.loc.toChars); + } + + var.var.onstack = true; + } + }); + } + + if (!dfaCommon.currentDFAScope.controlFlowReturned) return; // validate/infer on to function @@ -115,8 +191,7 @@ struct DFAReporter else if (suggestedNotNullOut == Fact.Unspecified && cctx.nullable == Nullable.NonNull) suggestedNotNullOut = Fact.Guaranteed; - assert(!param.specifiedByUser); - param.notNullOut = suggestedNotNullOut; + param.inferred.notNullOut = suggestedNotNullOut; }); } @@ -127,9 +202,13 @@ struct DFAReporter printf("End of function attributes for `%s` at %s\n", fd.ident.toChars, loc.toChars); } + auto tf = fd.type.isTypeFunction; + // validate/infer on to function - foreachFunctionVariable(dfaCommon, fd, (scv) { - ParameterDFAInfo* param = scv.var.param; + foreachFunctionVariable(dfaCommon, fd, (DFAScopeVar* scv) { + DFAVar* var = scv.var; + ParameterDFAInfo* param = var.param; + if (param is null) return; @@ -140,53 +219,69 @@ struct DFAReporter else printf("Variable %p ", scv.var); - printf("%d %d:%d %d:%d %d\n", scv.var.unmodellable, scv.var.isTruthy, - scv.var.isNullable, scv.var.isByRef, scv.var.writeCount, - param.specifiedByUser); - printf(" notNullIn=%d, notNullOut=%d\n", param.notNullIn, param.notNullOut); + printf("unmodellable=%d, isTruthy=%d, isNullable=%d, isByRef=%d, writeCount=%d\n", scv.var.unmodellable, scv.var.isTruthy, + scv.var.isNullable, scv.var.isByRef, scv.var.writeCount); + printf(" notNullIn=%d/%d, notNullOut=%d/%d\n", param.userSupplied.notNullIn, + param.inferred.notNullIn, param.userSupplied.notNullOut, + param.inferred.notNullOut); } - if (scv.var.unmodellable) + if (var.isNullable) { - if (!param.specifiedByUser) + if (var.unmodellable) { - param.specifiedByUser = false; - - if (scv.var.isNullable) - { - param.notNullIn = Fact.Unspecified; - param.notNullOut = Fact.Unspecified; - } + param.inferred.notNullIn = Fact.Unspecified; + param.inferred.notNullOut = Fact.Unspecified; } - } - else if (!param.specifiedByUser) - { - if (scv.var.isNullable) + else { - if (param.notNullIn == Fact.Unspecified) - param.notNullIn = Fact.NotGuaranteed; + if (param.inferred.notNullIn == Fact.Unspecified) + param.inferred.notNullIn = Fact.NotGuaranteed; - if (scv.var.isByRef) + if (var.isByRef) { - if (scv.var.writeCount > 1) + if (var.writeCount > 1) { - if (param.notNullOut == Fact.Unspecified) - param.notNullOut = Fact.NotGuaranteed; + if (param.inferred.notNullOut == Fact.Unspecified) + param.inferred.notNullOut = Fact.NotGuaranteed; } else - param.notNullOut = param.notNullIn; + param.inferred.notNullOut = param.inferred.notNullIn; } else - { - param.notNullOut = Fact.Unspecified; - } + param.inferred.notNullOut = Fact.Unspecified; } } + if (!var.unmodellable) + { + if (param.inferred.willEscape(-1) != ParameterDFAInfo.EscapedRelationship.Unknown) + { + // escapes into an unknown location, don't infer + } + else + { + if (!var.mayEscapeWithoutIndirection) + param.inferred.escapeIntoNothing = true; + } + } + + if (tf.trust == TRUST.safe && param.userSupplied.escapeIntoNothing + && !param.inferred.escapeIntoNothing && param.inferred.escapesInto != 0) + { + errorSink.error(var.var.loc, "Parameter is required to be scope but escapes"); + + if (param.inferred.willEscape(-1) != ParameterDFAInfo.EscapedRelationship.Unknown) + errorSink.errorSupplemental(var.var.loc, "Escapes into an unknown location"); + else if (var.mayEscapeWithoutIndirection) + errorSink.errorSupplemental(var.var.loc, "Escapes directly"); + } + version (none) { - printf(" notNullIn=%d, notNullOut=%d\n", - cast(int) param.notNullIn, param.notNullOut); + printf(" notNullIn=%d/%d, notNullOut=%d/%d\n", param.userSupplied.notNullIn, + param.inferred.notNullIn, param.userSupplied.notNullOut, + param.inferred.notNullOut); } }); } @@ -232,12 +327,12 @@ struct DFAReporter void onReadOfUninitialized(DFAVar* var, ref DFALatticeRef lr, ref Loc loc, bool forMathOp) { - if (!var.isModellable || var.declaredAtDepth == 0 || (!forMathOp + if (!var.isModellable || var.oldestLifeTimeAllowedDepth == 0 || (!forMathOp && var.isFloatingPoint) || var.var is null) return; else if ((var.var.storage_class & STC.temp) != 0) return; // Compiler generated, likely to be "fine" - else if (var.declaredAtDepth < dfaCommon.lastLoopyLabel.depth) + else if (var.oldestLifeTimeAllowedDepth < dfaCommon.lastLoopyLabel.depth) return; // Can we really know what state the variable is in? I don't think so. // The reason floating point is special cased here, is to allow catching of mathematical operations, @@ -282,6 +377,417 @@ struct DFAReporter }); } } + + void onThrowEscape(DFAObject* obj, ref const Loc loc) + { + if (obj is null) + return; + checkForUnknownEscape("throw", obj, loc, true); + } + + void onGlobalEscape(DFAVar* intoVar, DFAObject* obj, ref const Loc loc) + { + if (obj is null) + return; + checkForUnknownEscape("global", obj, loc, false); + } + + void onDelayEscapeOfObject(DFAVar* intoVar, DFAObject* obj, ref Loc loc) + { + version (none) + { + printf("On delay of obj %p for var %p at %s\n", obj, intoVar, loc.toChars); + } + + DFAVar* returnVar = dfaCommon.getReturnVariable; + bool silenceFutureErrors; + + obj.walk((DFAObject* root) { + version (none) + { + printf(" root %p\n", root); + } + + if (root.delayOnReadErrorOfEscape) + { + checkForEscapes(intoVar, returnVar, root, silenceFutureErrors, loc, true); + return false; + } + + return true; + }); + } + +private: + + void checkForEscapes(DFAVar* intoVar, DFAVar* returnVar, DFAObject* parentObject, + ref bool silenceFutureErrors, ref const Loc loc, bool noDelay = false) + { + version (none) + { + printf("checkForEscapes intoVar=%p, returnVar=%p, parentObject=%p\n", + intoVar, returnVar, parentObject); + } + + if (intoVar !is returnVar) + { + if (intoVar.param is null || parentObject.minimumDeclaredAtDepth + >= intoVar.oldestLifeTimeAllowedDepth) + { + silenceFutureErrors = false; + + if (parentObject.minimumDeclaredAtDepth == intoVar.oldestLifeTimeAllowedDepth) + return; + } + } + + bool emittedEscapeError, lookAtConstraintOnly; + + void onDemandEscapeError() + { + if (emittedEscapeError) + return; + + emittedEscapeError = true; + + // ugh oh! the memory has too long of a lifetime! + if (intoVar.var !is null) + { + errorSink.error(loc, "Stack variable stores a lifetime that exceeds its own"); + errorSink.errorSupplemental(intoVar.var.loc, + "For variable `%s`", intoVar.var.ident.toChars); + } + else + errorSink.error(loc, "Stack variable exceeds its lifetime by being returned"); + } + + void inferParameter(DFAVar* from, bool hadAnIndirection, + bool haveConstraintOnLifeTime, bool inCell) + { + EscapedRelationship currentRel = from.param.inferred.willEscape( + intoVar.param.parameterId); + + EscapedRelationship toInferRel; + + // T var = rhs; + // T* var = rhs; + // This can be by-value based upon rhs; + + // ref T var = x; + // This can be by-value only if it has had an indirection that isn't constrained. + + if (from.isByRef && (inCell || (!hadAnIndirection && intoVar.isByRef))) + toInferRel = EscapedRelationship.PointerTo; + else if (from.isByRef && !inCell) + toInferRel = EscapedRelationship.ByValue; + else if (hadAnIndirection && !haveConstraintOnLifeTime) + toInferRel = EscapedRelationship.ByValue; + else + toInferRel = EscapedRelationship.PointerTo; + + if (currentRel < toInferRel) + from.param.inferred.willEscape(intoVar.param.parameterId, toInferRel); + + if (hadAnIndirection && toInferRel == EscapedRelationship.ByValue) + from.mayEscapeWithIndirection = true; + else + from.mayEscapeWithoutIndirection = true; + } + + parentObject.walkIndirection((DFAObject* obj, bool hadAnIndirection, + bool haveConstraintOnLifeTime, bool inCell) { + if (obj.constrainedBy is intoVar) + return; + + version (none) + { + printf("potential escape 1 obj %p, hadAnIndirection=%d, haveConstraintOnLifeTime=%d, inCell=%d\n", + obj, hadAnIndirection, haveConstraintOnLifeTime, inCell); + } + + if (obj.lifeTimeUnderstood && !noDelay) + { + // S* ptr; + // { + // S buf; + // ptr = &buf; + // buf.__dtor; + // } + // S temp = *ptr; // error: ptr contains an escaped object that has had cleanup run on it + + parentObject.delayOnReadErrorOfEscape = true; + silenceFutureErrors = false; + return; + } + + // Cannot outlive this variable. + DFAVar* constrainedTo = (obj.constrainedBy !is null && obj.constrainedBy.var !is null) ? obj.constrainedBy + : null; + // The cell of a variable. + DFAVar* cellOf = (obj.storageFor !is null && obj.storageFor.var !is null) ? obj.storageFor + : null; + + version (none) + { + printf("derivedFrom=%p, constrainedTo %p, cellOf %p\n", + obj.derivedFrom, constrainedTo, cellOf); + } + + // Both constrainedTo and cellOf cannot be escape from. + // The benefit of differentiating them allows us to tell the user that it was the cell that was escaped, + // versus an object that was stored in it, that was later escaped. + + // Have multiple layers of pointers, which we can't model. + if (obj.derivedFrom !is null) + cellOf = null; + + DFAVar* either = constrainedTo !is null ? constrainedTo : cellOf; + + if (either is null && haveConstraintOnLifeTime) + lookAtConstraintOnly = true; + + if (intoVar.param !is null && either !is null && either.param !is null) + { + either.mayEscapeInitialValue = true; + inferParameter(either, hadAnIndirection, haveConstraintOnLifeTime, inCell); + return; + } + + if (hadAnIndirection) + { + } + else if (cellOf !is null && !cellOf.unmodellable + && intoVar.youngestLifeTimeAllowedDepth < cellOf.oldestLifeTimeAllowedDepth) + { + cellOf.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(cellOf.var.loc, + "A pointer to the cell of the variable `%s` has potentially escaped", + cellOf.var.ident.toChars); + } + else if (constrainedTo !is null && !constrainedTo.unmodellable + && obj.onTheStack && intoVar.youngestLifeTimeAllowedDepth < constrainedTo.oldestLifeTimeAllowedDepth) + { + constrainedTo.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(constrainedTo.var.loc, + "Pointer stored in variable `%s` has potentially escaped", + constrainedTo.var.ident.toChars); + } + }, null); + + if (lookAtConstraintOnly) + { + parentObject.walkIndirection(null, (DFAObject* constrained, + bool hadAnIndirection, bool haveConstraintOnLifeTime) { + if (constrained.constrainedBy is intoVar) + return; + + version (none) + { + printf("potential escape 2 obj %p, hadAnIndirection=%d, haveConstraintOnLifeTime=%d\n", + constrained, hadAnIndirection, haveConstraintOnLifeTime); + } + + DFAVar* constrainedTo = constrained.constrainedBy; + + if (intoVar.param !is null && constrainedTo.param !is null) + { + constrainedTo.mayEscapeInitialValue = true; + inferParameter(constrainedTo, hadAnIndirection, + haveConstraintOnLifeTime, false); + return; + } + + if (hadAnIndirection) + { + } + else if (!constrainedTo.unmodellable && constrained.onTheStack + && (intoVar.youngestLifeTimeAllowedDepth < constrainedTo.oldestLifeTimeAllowedDepth || intoVar is returnVar)) + { + constrainedTo.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(constrainedTo.var.loc, + "Pointer stored in variable `%s` has potentially escaped", + constrainedTo.var.ident.toChars); + } + }); + } + } + + void checkForUnknownEscape(const(char)* escapeMethod, + DFAObject* parentObject, ref const Loc loc, bool errorOnStackEscape) + { + version (none) + { + printf("checkForUnknownEscape %s, parentObject=%p\n", escapeMethod, parentObject); + } + + bool emittedEscapeError, lookAtConstraintOnly; + + void onDemandEscapeError() + { + if (emittedEscapeError) + return; + + emittedEscapeError = true; + + errorSink.error(loc, "Escape of unknown lifetime via %s", escapeMethod); + } + + void inferParameter(DFAVar* from, bool hadAnIndirection, + bool haveConstraintOnLifeTime, bool inCell) + { + EscapedRelationship currentRel = from.param.inferred.willEscape(-1); + + EscapedRelationship toInferRel; + + // T var = rhs; + // T* var = rhs; + // This can be by-value based upon rhs; + + // ref T var = x; + // This can be by-value only if it has had an indirection that isn't constrained. + + if (from.isByRef && inCell) + toInferRel = EscapedRelationship.PointerTo; + else if (from.isByRef && !inCell) + toInferRel = EscapedRelationship.ByValue; + else if (hadAnIndirection && !haveConstraintOnLifeTime) + toInferRel = EscapedRelationship.ByValue; + else + toInferRel = EscapedRelationship.PointerTo; + + if (currentRel < toInferRel) + from.param.inferred.willEscape(-1, toInferRel); + + if (hadAnIndirection) + from.mayEscapeWithIndirection = true; + else + from.mayEscapeWithoutIndirection = true; + } + + parentObject.walkIndirection((DFAObject* obj, bool hadAnIndirection, + bool haveConstraintOnLifeTime, bool inCell) { + + version (none) + { + printf("potential escape 1 obj %p, hadAnIndirection=%d, haveConstraintOnLifeTime=%d\n", + obj, hadAnIndirection, haveConstraintOnLifeTime); + } + + if (obj.lifeTimeUnderstood) + { + // S* ptr; + // { + // S buf; + // ptr = &buf; + // buf.__dtor; + // } + // S temp = *ptr; // error: ptr contains an escaped object that has had cleanup run on it + + parentObject.delayOnReadErrorOfEscape = true; + return; + } + + // Cannot outlive this variable. + DFAVar* constrainedTo = (obj.constrainedBy !is null && obj.constrainedBy.var !is null) ? obj.constrainedBy + : null; + // The cell of a variable. + DFAVar* cellOf = (obj.storageFor !is null && obj.storageFor.var !is null) ? obj.storageFor + : null; + + version (none) + { + printf("derivedFrom=%p, constrainedTo %p, cellOf %p\n", + obj.derivedFrom, constrainedTo, cellOf); + } + + // Both constrainedTo and cellOf cannot be escape from. + // The benefit of differentiating them allows us to tell the user that it was the cell that was escaped, + // versus an object that was stored in it, that was later escaped. + + // Have multiple layers of pointers, which we can't model. + if (obj.derivedFrom !is null) + cellOf = null; + + DFAVar* either = constrainedTo !is null ? constrainedTo : cellOf; + + if (either is null && haveConstraintOnLifeTime) + lookAtConstraintOnly = true; + + if (!errorOnStackEscape && either !is null && either.param !is null) + { + either.mayEscapeInitialValue = true; + inferParameter(either, hadAnIndirection, haveConstraintOnLifeTime, inCell); + return; + } + + if (hadAnIndirection || !errorOnStackEscape) + { + } + else if (cellOf !is null + && !cellOf.unmodellable && cellOf.oldestLifeTimeAllowedDepth >= 0) + { + cellOf.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(cellOf.var.loc, + "A pointer to the cell of the variable `%s` has potentially escaped", + cellOf.var.ident.toChars); + } + else if (constrainedTo !is null && !constrainedTo.unmodellable + && obj.onTheStack && constrainedTo.oldestLifeTimeAllowedDepth >= 0) + { + constrainedTo.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(constrainedTo.var.loc, + "Pointer stored in variable `%s` has potentially escaped", + constrainedTo.var.ident.toChars); + } + }, null); + + if (lookAtConstraintOnly) + { + parentObject.walkIndirection(null, (DFAObject* constrained, + bool hadAnIndirection, bool haveConstraintOnLifeTime) { + + version (none) + { + printf("potential escape 2 obj %p, hadAnIndirection=%d, haveConstraintOnLifeTime=%d\n", + constrained, hadAnIndirection, haveConstraintOnLifeTime); + } + + DFAVar* constrainedTo = constrained.constrainedBy; + + if (!errorOnStackEscape && constrainedTo.param !is null) + { + constrainedTo.mayEscapeInitialValue = true; + inferParameter(constrainedTo, hadAnIndirection, + haveConstraintOnLifeTime, false); + return; + } + + if (hadAnIndirection || !errorOnStackEscape) + { + } + else if (!constrainedTo.unmodellable + && constrained.onTheStack && constrainedTo.oldestLifeTimeAllowedDepth >= 0) + { + constrainedTo.mayEscapeInitialValue = true; + + onDemandEscapeError(); + errorSink.errorSupplemental(constrainedTo.var.loc, + "Pointer stored in variable `%s` has potentially escaped", + constrainedTo.var.ident.toChars); + } + }); + } + } } private: diff --git a/compiler/src/dmd/dfa/fast/statement.d b/compiler/src/dmd/dfa/fast/statement.d index 36451c4311bf..616c9d8ba55c 100644 --- a/compiler/src/dmd/dfa/fast/statement.d +++ b/compiler/src/dmd/dfa/fast/statement.d @@ -30,7 +30,6 @@ import dmd.identifier; import dmd.statement; import dmd.expression; import dmd.typesem; -import dmd.timetrace; import dmd.astenums; import dmd.mtype; import dmd.declaration; @@ -77,83 +76,39 @@ final: return this.endScope(loc); } - DFAScopeRef endScope(ref Loc endLoc, bool handleLabelPopping = true) + void mergeEndScopeLabels() { - DFAScopeRef ret = dfaCommon.popScope; - dfaCommon.sdepth--; - - if (handleLabelPopping) + while (dfaCommon.currentDFAScope.label !is null) { - while (ret.sc.label !is null) - { - Loc loc = *cast(Loc*)&ret.sc.label.loc; + DFAScopeRef scr = dfaCommon.popScope; + dfaCommon.sdepth--; + inLoopyLabel--; - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("End label scope %p:%p %s", ret.sc, - dfaCommon.currentDFAScope, ret.sc.label.ident.toChars); - if (loc.isValid) - appendLoc(ob, loc); - ob.writestring("\n"); - }); + Loc loc = *cast(Loc*)&scr.sc.label.loc; - ret.printStructure("*=", dfaCommon.sdepth, dfaCommon.currentFunction); - ret.check; + dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { + ob.printf("End label scope %p:%p %s", scr.sc, + dfaCommon.currentDFAScope, scr.sc.label.ident.toChars); + if (loc.isValid) + appendLoc(ob, loc); + ob.writestring("\n"); + }); - seeConvergeStatementLoopyLabels(ret, loc); - inLoopyLabel--; + scr.printStructure("*=", dfaCommon.sdepth); + scr.check; - ret = dfaCommon.popScope; - dfaCommon.sdepth--; - } + seeConvergeStatementLoopyLabels(scr, loc); } - - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("End scope %p:%p", ret.sc, dfaCommon.currentDFAScope); - if (endLoc.isValid) - appendLoc(ob, endLoc); - ob.writestring("\n"); - }); - - ret.printStructure("*=", dfaCommon.sdepth, dfaCommon.currentFunction); - ret.check; - - dfaCommon.currentDFAScope.printStructure("parent", dfaCommon.sdepth, - dfaCommon.currentFunction); - dfaCommon.currentDFAScope.check; - return ret; } - DFAScopeRef endScopeAndReport(ref Loc endLoc, bool endFunction = false) + DFAScopeRef endScope(ref Loc endLoc, bool handleLabelPopping = true) { - analyzer.reporter.onEndOfScope(dfaCommon.currentFunction, endLoc); - if (endFunction) - analyzer.reporter.onEndOfFunction(dfaCommon.currentFunction, endLoc); + if (handleLabelPopping) + mergeEndScopeLabels(); DFAScopeRef ret = dfaCommon.popScope; dfaCommon.sdepth--; - while (ret.sc.label !is null) - { - Loc loc = *cast(Loc*)&ret.sc.label.loc; - - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("End label scope %p:%p %s", ret.sc, - dfaCommon.currentDFAScope, ret.sc.label.ident.toChars); - if (loc.isValid) - appendLoc(ob, loc); - ob.writestring("\n"); - }); - - ret.printStructure("*=", dfaCommon.sdepth, dfaCommon.currentFunction); - ret.check; - - seeConvergeStatementLoopyLabels(ret, loc); - inLoopyLabel--; - - ret = dfaCommon.popScope; - dfaCommon.sdepth--; - } - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { ob.printf("End scope %p:%p", ret.sc, dfaCommon.currentDFAScope); if (endLoc.isValid) @@ -161,55 +116,42 @@ final: ob.writestring("\n"); }); - ret.printStructure("*=", dfaCommon.sdepth, dfaCommon.currentFunction); + ret.printStructure("*=", dfaCommon.sdepth); ret.check; if (dfaCommon.currentDFAScope !is null) { - dfaCommon.currentDFAScope.printStructure("parent", - dfaCommon.sdepth, dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("parent", dfaCommon.sdepth); dfaCommon.currentDFAScope.check; } return ret; } - void seeConvergeStatement(DFAScopeRef scr, bool ignoreWriteCount = false, - bool unknownAware = false) + DFAScopeRef endScopeAndReport(ref Loc endLoc, bool endFunction = false) { - dfaCommon.printStructure((ref OutBuffer ob, - scope PrintPrefixType prefix) => ob.printf("Converging statement: %d\n", - unknownAware)); - scr.printStructure("input", dfaCommon.sdepth, dfaCommon.currentFunction); - scr.check; + mergeEndScopeLabels(); - dfaCommon.check; - dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth, - dfaCommon.currentFunction); - dfaCommon.check; - - analyzer.convergeStatement(scr, true, false, ignoreWriteCount, unknownAware); + analyzer.reporter.onEndOfScope(dfaCommon.currentFunction, endLoc); + if (endFunction) + analyzer.reporter.onEndOfFunction(dfaCommon.currentFunction, endLoc); - dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth, - dfaCommon.currentFunction); - dfaCommon.currentDFAScope.check; + return this.endScope(endLoc, false); } void seeConvergeScope(DFAScopeRef scr) { dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.printf("Converging scope:\n")); - scr.printStructure("input", dfaCommon.sdepth, dfaCommon.currentFunction); + scr.printStructure("input", dfaCommon.sdepth); scr.check; dfaCommon.check; - dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth); dfaCommon.check; analyzer.convergeScope(scr); - dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth); dfaCommon.currentDFAScope.check; } @@ -220,12 +162,11 @@ final: dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.printf( "Converging statement if haveFalseBody=%d, unknownBranchTaken=%d, predicateNegation=%d:\n", haveFalseBody, unknownBranchTaken, predicateNegation)); - condition.printState("condition", dfaCommon.sdepth, dfaCommon.currentFunction); - scrTrue.printStructure("true", dfaCommon.sdepth, dfaCommon.currentFunction); - scrFalse.printStructure("false", dfaCommon.sdepth, dfaCommon.currentFunction); + condition.printState("condition", dfaCommon.sdepth); + scrTrue.printStructure("true", dfaCommon.sdepth); + scrFalse.printStructure("false", dfaCommon.sdepth); - dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; dfaCommon.check; @@ -233,8 +174,7 @@ final: haveFalseBody, unknownBranchTaken, predicateNegation); dfaCommon.check; - dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; } @@ -259,19 +199,17 @@ final: dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.writestring( "Converging statement loopy labels:\n")); - scr.printStructure("input", dfaCommon.sdepth, dfaCommon.currentFunction); + scr.printStructure("input", dfaCommon.sdepth); scr.check; - dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; dfaCommon.check; analyzer.convergeStatementLoopyLabels(scr, loc); dfaCommon.check; - dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; } @@ -296,19 +234,17 @@ final: { dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.writestring("Converging forward goto:\n")); - scr.printStructure("input", dfaCommon.sdepth, dfaCommon.currentFunction); + scr.printStructure("input", dfaCommon.sdepth); scr.check; - dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("scope", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; dfaCommon.check; - analyzer.convergeStatement(scr, false); + analyzer.convergeScope(scr); dfaCommon.check; - dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth, - dfaCommon.currentFunction); + dfaCommon.currentDFAScope.printStructure("so far", dfaCommon.sdepth,); dfaCommon.currentDFAScope.check; } @@ -316,14 +252,14 @@ final: { dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.writestring("Loop stage:\n")); - previous.printStructure("previous", dfaCommon.sdepth, dfaCommon.currentFunction); - next.printStructure("next", dfaCommon.sdepth, dfaCommon.currentFunction); + previous.printStructure("previous", dfaCommon.sdepth); + next.printStructure("next", dfaCommon.sdepth); dfaCommon.check; DFAScopeRef ret = analyzer.transferLoopyLabelAwareStage(previous, next); dfaCommon.check; - ret.printStructure("result", dfaCommon.sdepth, dfaCommon.currentFunction); + ret.printStructure("result", dfaCommon.sdepth); return ret; } @@ -351,7 +287,7 @@ final: void seeRead(ref DFALatticeRef lr, ref Loc loc) { dfaCommon.printStructureln("Seeing read of lr"); - lr.printState("read lr", dfaCommon.sdepth, dfaCommon.currentFunction, dfaCommon.edepth); + lr.printState("read lr", dfaCommon.sdepth, dfaCommon.edepth); analyzer.onRead(lr, loc); } @@ -372,18 +308,6 @@ final: void start(FuncDeclaration fd) { - dfaCommon.currentFunction = fd; - - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("Starting DFA walk on %s at ", fd.ident.toChars); - appendLoc(ob, fd.loc); - ob.writestring("\n"); - }); - - timeTraceBeginEvent(TimeTraceEventType.dfa); - scope (exit) - timeTraceEndEvent(TimeTraceEventType.dfa, fd); - this.startScope; dfaCommon.setScopeAsLoopyLabel; @@ -391,18 +315,27 @@ final: DFAScopeVar* scv; { - if (fd.parametersDFAInfo is null) - fd.parametersDFAInfo = new ParametersDFAInfo; + ensureDFAParameters(fd); if (fd.vthis !is null) { DFAVar* var = dfaCommon.findVariable(fd.vthis); - var.declaredAtDepth = dfaCommon.currentDFAScope.depth; + var.oldestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; + var.youngestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; var.writeCount = 1; var.param = &fd.parametersDFAInfo.thisPointer; - var.param.parameterId = -1; scv = dfaCommon.acquireScopeVar(var); + if (var.isNullable) + { + DFAConsequence* cctx = scv.lr.getContext; + cctx.obj = dfaCommon.makeObject(); + cctx.obj.minimumDeclaredAtDepth = var.oldestLifeTimeAllowedDepth; + cctx.obj.defaultTrackObj = true; + cctx.obj.constrainedBy = var; + cctx.obj.onTheStack = var.param.escapeIntoNothing; + } + dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.printf("vthis is %p scv %p\n", var, scv)); @@ -416,28 +349,34 @@ final: if (fd.parameters !is null) { - if (fd.parametersDFAInfo.parameters.length != fd.parameters.length) - fd.parametersDFAInfo.parameters.length = fd.parameters.length; - foreach (i, param; *fd.parameters) { DFAVar* var = dfaCommon.findVariable(param); - var.declaredAtDepth = dfaCommon.currentDFAScope.depth; + var.oldestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; + var.youngestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; var.writeCount = 1; var.param = &fd.parametersDFAInfo.parameters[i]; - *var.param = ParameterDFAInfo.init; // Array won't initialize it - var.param.parameterId = cast(int) i; scv = dfaCommon.acquireScopeVar(var); + if (var.isNullable) + { + DFAConsequence* cctx = scv.lr.getContext; + cctx.obj = dfaCommon.makeObject(); + cctx.obj.minimumDeclaredAtDepth = var.oldestLifeTimeAllowedDepth; + cctx.obj.defaultTrackObj = true; + cctx.obj.constrainedBy = var; + cctx.obj.onTheStack = var.param.escapeIntoNothing; + } + dfaCommon.printStructure((ref OutBuffer ob, - scope PrintPrefixType prefix) => ob.printf("param (%d) is `%s` %p scv %p stc %lld\n", - cast(int)i, param.ident !is null ? param.ident.toChars : null, - var, scv, param.storage_class)); + scope PrintPrefixType prefix) => ob.printf( + "param (%zd) is `%s` %p scv %p stc %lld escape %lld:%d/%lld:%d\n", + i, param.ident !is null ? param.ident.toChars : null, var, scv, + param.storage_class, var.param.userSupplied.escapesInto, + var.param.userSupplied.escapeIntoNothing, + var.param.inferred.escapesInto, var.param.inferred.escapeIntoNothing)); assert(scv.var is var); - DFAScopeVar* scv2 = dfaCommon.currentDFAScope.findScopeVar(var); - assert(scv is scv2); - var.isByRef = (param.storage_class & STC.ref_) == STC.ref_; if ((param.storage_class & STC.out_) == STC.out_) @@ -469,14 +408,15 @@ final: { DFAVar* var = dfaCommon.getReturnVariable(); - var.declaredAtDepth = dfaCommon.currentDFAScope.depth; + var.oldestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; + var.youngestLifeTimeAllowedDepth = dfaCommon.currentDFAScope.depth; var.writeCount = 1; var.param = &fd.parametersDFAInfo.returnValue; - var.param.parameterId = -2; TypeFunction tf = fd.type.isTypeFunction(); assert(tf !is null); + var.isByRef = tf.isRef; var.isNullable = tf.isRef || isTypeNullable(tf.next); scv = dfaCommon.acquireScopeVar(var); @@ -518,52 +458,43 @@ final: } } - this.visit(fd.fbody); + this.visitFlattened(fd.fbody); // implicit return // endScope call automatically infers/validates scope on to function. - dfaCommon.currentDFAScope.haveJumped = true; - dfaCommon.currentDFAScope.haveReturned = true; + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.EndOfFunction; this.endScopeAndReport(fd.endloc, true); + } - dfaCommon.printIfStructure((ref OutBuffer ob, scope void delegate(const(char)*) prefix) { - dfaCommon.allocator.allVariables((DFAVar* var) { - prefix("var"); - ob.printf(" %p base1=%p, base2=%p", var, var.base1, var.base2); - - if (var.var !is null) - { - ob.printf(", `%s` at ", var.var.ident.toChars); - appendLoc(ob, var.var.loc); - } - - ob.printf("\n"); - }); - dfaCommon.allocator.allObjects((DFAObject* obj) { - prefix("object"); - ob.printf(" %p base1=%p, base2=%p, storageFor=%p, mayNotBeExactPointer=%d\n", obj, - obj.base1, obj.base2, obj.storageFor, obj.mayNotBeExactPointer); - }); - }); + void visitFlattened(Statement st) + { + // Flattens out compound statements, + // usually this applies to function bodies, + // but it may apply to other statements where it matters. + // It matters mostly to function bodies due to function parameters, + // they need the same lifetime as the first set of statements. - if (fd.parametersDFAInfo !is null) + if (auto cs1 = st.isCompoundStatement) { - dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { - ob.printf("return=%d:%d", fd.parametersDFAInfo.returnValue.notNullIn, - fd.parametersDFAInfo.returnValue.notNullOut); - ob.writestring("\n"); - - ob.printf("this=%d:%d", fd.parametersDFAInfo.thisPointer.notNullIn, - fd.parametersDFAInfo.thisPointer.notNullOut); - ob.writestring("\n"); - - foreach (i, param; fd.parametersDFAInfo.parameters) + if (cs1.statements !is null) + { + foreach (stmt1; *cs1.statements) { - ob.printf("%d: %d:%d", cast(int)i, param.notNullIn, param.notNullOut); - ob.writestring("\n"); + if (auto cs2 = stmt1.isCompoundStatement) + { + if (cs2.statements !is null) + { + foreach (stmt2; *cs2.statements) + this.visit(stmt2); + } + } + else + this.visit(stmt1); } - }); + } } + else + this.visit(st); } extern (C++) override void visit(Statement st) @@ -584,8 +515,9 @@ final: dfaCommon.sdepth--; } - if (dfaCommon.currentDFAScope.haveReturned) + if (dfaCommon.currentDFAScope.controlFlowJumped) { + // We've jumped previously, which means we probably aren't valid anymore dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) { ob.printf("%3d (%s): ignoring at ", st.stmt, AllStmtOpNames[st.stmt].ptr); appendLoc(ob, st.loc); @@ -621,6 +553,13 @@ final: assert(0); } } + else version (none) + { + if (st.loc.linnum > 6902) + { + return; + } + } final switch (st.stmt) { @@ -636,21 +575,25 @@ final: expWalker.seeConvergeExpression(lr); break; case STMT.Return: - Expression exp = st.isReturnStatement.exp; - - if (exp !is null) { - DFALatticeRef lr = expWalker.seeAssign(dfaCommon.getReturnVariable, - true, expWalker.walk(exp), exp.loc); - expWalker.seeConvergeExpression(lr); - } + // Class constructors return the this pointer, which upsets escape analysis, ignore. + if (!dfaCommon.isCurrentFunctionClassConstructor) + { + Expression exp = st.isReturnStatement.exp; - // Mark the current scope as having returned. - // This signals that no code after this point in the current block is reachable. - dfaCommon.currentDFAScope.haveJumped = true; - dfaCommon.currentDFAScope.haveReturned = true; - analyzer.reporter.onEndOfScope(dfaCommon.currentFunction, st.loc); - break; + if (exp !is null) + { + DFALatticeRef lr = expWalker.seeAssign(dfaCommon.getReturnVariable, + true, expWalker.walk(exp), exp.loc); + expWalker.seeConvergeExpression(lr); + } + } + + // Mark the current scope as having returned. + // This signals that no code after this point in the current block is reachable. + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Return; + break; + } case STMT.Compound: auto cs = st.isCompoundStatement; @@ -670,7 +613,7 @@ final: dfaCommon.currentDFAScope.inProgressCompoundStatement = i; this.visit(st2); - if (dfaCommon.currentDFAScope.haveReturned) + if (dfaCommon.currentDFAScope.controlFlowReturned) { dfaCommon.printStructureln("Compound returned"); break; @@ -710,14 +653,10 @@ final: DFAVar* conditionVar; { - this.startScope; - dfaCommon.currentDFAScope.sideEffectFree = true; - conditionLR = expWalker.walkCondition(ifs.condition, predicateNegation); conditionVar = conditionLR.getGateConsequenceVariable; seeRead(conditionLR, ifs.condition.loc); - this.endScope; } { @@ -736,9 +675,10 @@ final: version (none) { printf("if %d %d %d\n", takeTrueBranch, takeFalseBranch, unknownBranchTaken); - conditionLR.printStructure("condition"); - trueCondition.printStructure("true condition"); - falseCondition.printStructure("false condition"); + conditionLR.printActual("condition"); + conditionLR.copy.printActual("copied"); + trueCondition.printActual("true condition"); + falseCondition.printActual("false condition"); } } @@ -762,12 +702,12 @@ final: assert(dfaCommon.currentDFAScope !is origScope); - ifbody = this.endScope; + ifbody = this.endScopeAndReport(*cast(Loc*)&ifs.ifbody.loc); ifbody.check; } else { - this.endScope; + this.endScopeAndReport(*cast(Loc*)&ifs.ifbody.loc); dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.writestring("true ignored\n")); } @@ -793,9 +733,11 @@ final: this.visit(scs.statement); else this.visit(ifs.elsebody); + elsebody = this.endScopeAndReport(*cast(Loc*)&ifs.elsebody.loc); } + else + elsebody = this.endScope; - elsebody = this.endScope; elsebody.check; } @@ -821,7 +763,7 @@ final: ob.writestring("\n"); }); - DFAScopeRef scbody = this.endScope; + DFAScopeRef scbody = this.endScopeAndReport(*cast(Loc*)&sc.endloc); this.seeConvergeScope(scbody); break; @@ -980,7 +922,7 @@ final: scope PrintPrefixType prefix) => ob.writestring("For body:\n")); this.visit(theBody); - if (!dfaCommon.currentDFAScope.haveJumped) + if (dfaCommon.currentDFAScope.controlFlowJumped) { dfaCommon.printStructure((ref OutBuffer ob, scope PrintPrefixType prefix) => ob.writestring("For increment:\n")); @@ -1032,7 +974,7 @@ final: seeRead(lrCondition, ds.condition.loc); expWalker.seeSilentAssert(lrCondition, false, true); - DFAScopeRef scr = this.endScope(ds.endloc); + DFAScopeRef scr = this.endScopeAndReport(*cast(Loc*)&ds.endloc); this.seeConvergeStatementLoopyLabels(scr, *cast(Loc*)&ds.loc); break; @@ -1055,9 +997,9 @@ final: this.startScope; this.visit(stmt); - DFAScopeRef scr = this.endScope; + DFAScopeRef scr = this.endScopeAndReport(*cast(Loc*)&stmt.loc); - if (scr.sc.haveReturned) + if (scr.sc.controlFlowReturned) { this.seeConvergeScope(scr); break; @@ -1109,7 +1051,7 @@ final: this.visit(ss.sdefault.statement); } - caState.containing = this.endScope; + caState.containing = this.endScopeAndReport(*cast(Loc*)&ss.sdefault.loc); assert(dfaCommon.currentDFAScope is oldScope); } @@ -1136,7 +1078,7 @@ final: else this.visit(cs.statement); - caState.containing = this.endScope; + caState.containing = this.endScopeAndReport(*cast(Loc*)&cs.loc); assert(dfaCommon.currentDFAScope is oldScope); } } @@ -1215,7 +1157,7 @@ final: } if (haveParentControlStatement) - dfaCommon.currentDFAScope.haveJumped = true; + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Continue; } if (auto forLoop = sc.controlStatement.isForStatement) @@ -1262,7 +1204,7 @@ final: } if (haveParentControlStatement) - dfaCommon.currentDFAScope.haveJumped = true; + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Break; } seeGoto(sc, true); @@ -1321,13 +1263,13 @@ final: dfaCommon.currentDFAScope.tryFinallyStatement = null; - DFAScopeRef scr = this.endScope; + DFAScopeRef scr = this.endScopeAndReport(*cast(Loc*)&tfs.loc); this.seeConvergeScope(scr); break; case STMT.Goto: auto gs = st.isGotoStatement; - dfaCommon.currentDFAScope.haveJumped = true; + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Goto; auto sc = dfaCommon.findScopeGivenLabel(gs.ident); @@ -1357,7 +1299,7 @@ final: else this.visit(ws._body); - DFAScopeRef scr = this.endScope(ws.endloc); + DFAScopeRef scr = this.endScopeAndReport(*cast(Loc*)&ws.endloc); this.seeConvergeScope(scr); break; @@ -1367,7 +1309,7 @@ final: this.startScope; this.visit(tc._body); - DFAScopeRef inProgress = this.endScope(); + DFAScopeRef inProgress = this.endScopeAndReport(*cast(Loc*)&tc.loc); if (tc.catches !is null) { @@ -1383,11 +1325,12 @@ final: case STMT.Throw: auto ts = st.isThrowStatement; + DFALatticeRef lr = expWalker.walk(ts.exp); - expWalker.seeConvergeExpression(lr); + lr = expWalker.seeThrow(lr, ts.loc); - dfaCommon.currentDFAScope.haveJumped = true; - dfaCommon.currentDFAScope.haveReturned = true; + expWalker.seeConvergeExpression(lr); + dfaCommon.currentDFAScope.controlFlow |= DFAScope.ControlFlow.Thrown; break; case STMT.Asm: diff --git a/compiler/src/dmd/dfa/fast/structure.d b/compiler/src/dmd/dfa/fast/structure.d index 5302398c0498..b2802e3eb8e0 100644 --- a/compiler/src/dmd/dfa/fast/structure.d +++ b/compiler/src/dmd/dfa/fast/structure.d @@ -61,6 +61,7 @@ struct DFACommon int sdepth, edepth; FuncDeclaration currentFunction; + bool isCurrentFunctionClassConstructor; // Making these enum's instead of fields allows for significant performance gains enum debugIt = false; @@ -94,7 +95,7 @@ struct DFACommon void prefix(const(char)* pre) { - printPrefix(ob, pre, sdepth, currentFunction, edepth); + printPrefix(ob, pre, sdepth, edepth); } del(ob, &prefix); @@ -123,10 +124,32 @@ struct DFACommon void prefix(const(char)* pre) { - printPrefix(ob, pre, sdepth, currentFunction, edepth); + printPrefix(ob, pre, sdepth, edepth); } prefix(""); + const prefixLength = ob.length; + + del(ob, &prefix); + + if (ob.length > prefixLength) + printf(ob.peekChars); + + fflush(stdout); + } + } + + void printIfState(scope void delegate(ref OutBuffer ob, scope PrintPrefixType prefix) del) + { + static if (debugIt) + { + OutBuffer ob; + + void prefix(const(char)* pre) + { + printPrefix(ob, pre, sdepth, edepth); + } + del(ob, &prefix); if (ob.length > 0) @@ -152,13 +175,15 @@ struct DFACommon void prefix(const(char)* pre) { - printPrefix(ob, pre, sdepth, currentFunction, edepth); + printPrefix(ob, pre, sdepth, edepth); } prefix(""); + const prefixLength = ob.length; + del(ob, &prefix); - if (ob.length > 0) + if (ob.length > prefixLength) printf(ob.peekChars); fflush(stdout); @@ -293,6 +318,7 @@ struct DFACommon DFAVar* next = *bucket; *bucket = allocator.makeVar(null, null); (*bucket).haveInfiniteLifetime = a.haveInfiniteLifetime && b.haveInfiniteLifetime; + (*bucket).mayBeGlobal = a.mayBeGlobal || b.mayBeGlobal; (*bucket).next = next; } @@ -309,6 +335,7 @@ struct DFACommon childOf.dereferenceVar = allocator.makeVar(null); childOf.dereferenceVar.base1 = childOf; childOf.dereferenceVar.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + childOf.dereferenceVar.mayBeGlobal = childOf.mayBeGlobal; } return childOf.dereferenceVar; @@ -325,6 +352,7 @@ struct DFACommon childOf.indexVar.base1 = childOf; childOf.indexVar.isAnIndex = true; childOf.indexVar.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + childOf.indexVar.mayBeGlobal = childOf.mayBeGlobal; } return childOf.indexVar; @@ -340,6 +368,7 @@ struct DFACommon childOf.asSliceVar = allocator.makeVar(null); childOf.asSliceVar.base1 = childOf; childOf.asSliceVar.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + childOf.asSliceVar.mayBeGlobal = childOf.mayBeGlobal; } return childOf.asSliceVar; @@ -354,8 +383,10 @@ struct DFACommon { childOf.lengthVar = allocator.makeVar(null); childOf.lengthVar.base1 = childOf; + childOf.lengthVar.isTruthy = true; childOf.lengthVar.isLength = true; childOf.lengthVar.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + childOf.lengthVar.mayBeGlobal = childOf.mayBeGlobal; } return childOf.lengthVar; @@ -380,12 +411,29 @@ struct DFACommon ret.base1 = childOf; ret.offsetFromBase = offset; ret.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + ret.mayBeGlobal = childOf.mayBeGlobal; ret.next = *bucket; *bucket = ret; return ret; } + DFAVar* findAddressOfVar(DFAVar* childOf) + { + if (childOf is null) + return null; + + if (childOf.addressOfVar is null) + { + childOf.addressOfVar = allocator.makeVar(null); + childOf.addressOfVar.base1 = childOf; + childOf.addressOfVar.haveInfiniteLifetime = childOf.haveInfiniteLifetime; + childOf.addressOfVar.mayBeGlobal = childOf.mayBeGlobal; + } + + return childOf.addressOfVar; + } + DFAScope* findScopeForControlStatement(Statement st) { DFAScope* current = this.currentDFAScope; @@ -653,6 +701,12 @@ struct DFACommon DFAObject* ret = allocator.makeObject; ret.storageFor = storageForVar; + ret.minimumDeclaredAtDepth = storageForVar.oldestLifeTimeAllowedDepth; + + if (storageForVar.var is null + || (storageForVar.var.storage_class & ( + STC.static_ | STC.tls | STC.shared_ | STC.gshared)) == 0) + ret.onTheStack = storageForVar.oldestLifeTimeAllowedDepth > 0; storageForVar.storageFor = ret; return ret; @@ -669,6 +723,63 @@ struct DFACommon DFAObject* ret = allocator.makeObject; ret.base1 = base1; ret.base2 = base2; + + ret.onTheStack = (base1 !is null && base1.onTheStack) || (base2 !is null && base2 + .onTheStack); + + ret.minimumDeclaredAtDepth = -1; + if (base1 !is null && base1.minimumDeclaredAtDepth > -1) + ret.minimumDeclaredAtDepth = base1.minimumDeclaredAtDepth; + if (base2 !is null && base2.minimumDeclaredAtDepth > -1 && (ret.minimumDeclaredAtDepth == -1 + || ret.minimumDeclaredAtDepth > base2.minimumDeclaredAtDepth)) + ret.minimumDeclaredAtDepth = base2.minimumDeclaredAtDepth; + + return ret; + } + + DFAObject* makeInCellObject(DFAObject* cell) + { + DFAObject* ret = allocator.makeObject; + + ret.inCell = cell; + ret.mayNotBeExactPointer = true; + ret.minimumDeclaredAtDepth = -1; + + if (cell !is null) + { + ret.onTheStack = cell.onTheStack; + + if (cell.minimumDeclaredAtDepth > -1) + ret.minimumDeclaredAtDepth = cell.minimumDeclaredAtDepth; + } + return ret; + } + + DFAArgumentListRef makeArgumentListRef(size_t countArgs) + { + assert(countArgs >= 0); + + const neededLists = (countArgs + 31) / 32; + DFAArgumentListRef ret; + DFAArgumentList* current; + + foreach (_; 0 .. neededLists) + { + if (current is null) + { + current = allocator.makeArgumentList(&this); + ret.list = current; + } + else + { + current.listnext = allocator.makeArgumentList(&this); + current = current.listnext; + } + + current.count = countArgs > 32 ? 32 : cast(ubyte) countArgs; + countArgs -= 32; + } + return ret; } @@ -682,7 +793,8 @@ struct DFACommon DFAScopeVar* swapLattice(DFAVar* contextVar, scope DFALatticeRef delegate(DFALatticeRef) dg) { - if (contextVar is null || this.currentDFAScope.depth < contextVar.declaredAtDepth) + if (contextVar is null || this.currentDFAScope.depth < contextVar + .oldestLifeTimeAllowedDepth) return null; DFAScopeVar* scv = this.acquireScopeVar(contextVar); @@ -771,14 +883,14 @@ struct DFACommon DFAScopeVar* acquireScopeVar(DFAVar* var) { - if (var is null || this.currentDFAScope.depth < var.declaredAtDepth) + if (var is null || this.currentDFAScope.depth < var.oldestLifeTimeAllowedDepth) return null; DFAScope* sc; sc = this.currentDFAScope; DFAScopeVar* scv, ret; - while (sc !is null && sc.depth >= var.declaredAtDepth) + while (sc !is null && sc.depth >= var.oldestLifeTimeAllowedDepth) { if ((scv = sc.findScopeVar(var)) !is null) break; @@ -956,7 +1068,7 @@ struct DFAAllocator // 1mb for regions of memory //enum RegionAllocationStep = 1_048_576; - DFAVar* allocatedlistvar, allocatedlistlastvar; + DFAVar* allocatedlistvar, allocatedlistlastvar, allocatedlistStackVar; DFAObject* allocatedlistobject; // We use free lists to reuse memory, during our operation. @@ -966,6 +1078,7 @@ struct DFAAllocator DFAScopeVar* freelistscopevar; DFAObject* freelistobject; DFAScope* freelistscope; + DFAArgumentList* freelistarglist; DFALattice* freelistlattice; DFAConsequence* freelistconsequence; @@ -1056,8 +1169,16 @@ struct DFAAllocator ret.offsetFromBase = -1; if (vd !is null) + { applyType(ret, vd); + if (childOf is null) + { + ret.stacklistnext = allocatedlistStackVar; + allocatedlistStackVar = ret; + } + } + if (childOf !is null) { ret.haveInfiniteLifetime = childOf.haveInfiniteLifetime; @@ -1105,6 +1226,13 @@ struct DFAAllocator return ret; } + DFAArgumentList* makeArgumentList(DFACommon* dfaCommon) + { + DFAArgumentList* ret = allocInternal!DFAArgumentList(freelistarglist); + ret.dfaCommon = dfaCommon; + return ret; + } + DFALattice* makeLattice(DFACommon* dfaCommon) { DFALattice* ret = allocInternal!DFALattice(freelistlattice); @@ -1202,6 +1330,18 @@ struct DFAAllocator freelistscope = s; } + void free(DFAArgumentList* list) + { + foreach (i; 0 .. list.count) + { + list.each[i].lr = DFALatticeRef.init; + list.each[i].paramInfo = null; + } + + list.listnext = freelistarglist; + freelistarglist = list; + } + void free(DFALattice* l) { @@ -1242,6 +1382,17 @@ struct DFAAllocator } } + void allStackVariables(scope void delegate(DFAVar* obj) del) + { + DFAVar* current = allocatedlistStackVar; + + while (current !is null) + { + del(current); + current = current.stacklistnext; + } + } + private: T* allocInternal(T)(ref T* freelist) { @@ -1348,6 +1499,7 @@ struct DFAVar private { DFAVar* listnext; + DFAVar* stacklistnext; DFAVar* next; DFAVar*[16] childVars; DFAVar* childOffsetVars; @@ -1359,6 +1511,8 @@ struct DFAVar DFAVar* indexVar; DFAVar* lengthVar; DFAVar* asSliceVar; + DFAVar* addressOfVar; + DFAVar* dereferenceVar; // child var VarDeclaration var; dinteger_t offsetFromBase; // -1 if its not an offset @@ -1372,15 +1526,22 @@ struct DFAVar bool isBoolean; bool isStaticArray; bool isFloatingPoint; + bool isAA; bool isByRef; + bool isScope; bool haveInfiniteLifetime; bool wasDefaultInitialized; // may not be accurate for all variables + bool mayBeGlobal; // Var may map to one or more global variable declarations + bool isStackVar; // If this is a stack variable or something else + bool mayEscapeInitialValue; // The pointer may have escaped, cannot be allocated on stack. + bool mayEscapeWithoutIndirection; // Will this object directly escape? + bool mayEscapeWithIndirection; // Will a child pointer in this object escape? - int declaredAtDepth; - int writeCount; + int oldestLifeTimeAllowedDepth; // Where destructors and storage are inherently limited + int youngestLifeTimeAllowedDepth; // Where the variable is actually defined - DFAVar* dereferenceVar; // child var + int writeCount; bool unmodellable; // DO NOT REPORT!!!! bool doNotInferNonNull; // i.e. was the rhs of > @@ -1395,6 +1556,20 @@ struct DFAVar return this.base1 !is null; } + void markUnmodellable() + { + version (none) + { + if (this.var !is null) + { + if (this.var.ident.toString == "buf") + assert(0); + } + } + + this.unmodellable = true; + } + /// Finds the root variables for this one, where base1 is null void walkRoots(scope void delegate(DFAVar* var) del) { @@ -1452,29 +1627,6 @@ struct DFAVar del(this.base2); } - void visitDereferenceBases(scope void delegate(DFAVar* var) del) - { - void handle(DFAVar* var) - { - while (var.base2 is null && var.base1 !is null - && (var.base1.dereferenceVar is var - || (var.offsetFromBase != -1 && var.var is null))) - { - var = var.base1; - } - - if (var.base2 !is null) - { - handle(var.base1); - handle(var.base2); - } - else - del(var); - } - - handle(&this); - } - /// If this variable is a reference to another variable, visit the base variable. void visitIfReferenceToAnotherVar(scope void delegate(DFAVar* var) del) { @@ -1507,11 +1659,13 @@ struct DFAVar } /// If this variable is a reference to another, takes into account dereferencing. - void visitReferenceToAnotherVar(scope void delegate(DFAVar* var) hasIndirection, - scope void delegate(DFAVar* var) noIndirection = null) + void visitIfReadOfReferenceToAnotherVar(scope void delegate(DFAVar* var) resolvedIndirection) { void handle(DFAVar* var, int refed) { + if (!(var.base1 !is null || var.base2 !is null || refed != 0)) + return; + while (var.base2 is null && var.base1 !is null) { if (var.offsetFromBase != -1 && var.var is null) @@ -1520,6 +1674,11 @@ struct DFAVar refed--; else if (var.base1.indexVar is var) refed++; + else if (var.base1.lengthVar is var && var.base1.isStaticArray + && !var.base1.haveBase) + return; // Statically known length, base doesn't matter as it won't be read + else if (var.base1.asSliceVar is var && !var.base1.haveBase && refed == 0) + return; // base1[] is a slice this does not inherently make it a read else break; @@ -1531,55 +1690,198 @@ struct DFAVar handle(var.base1, refed); handle(var.base2, refed); } - else if (refed > 0) - { - if (hasIndirection !is null) - hasIndirection(var); - } - else if (noIndirection !is null) - noIndirection(var); + else if (refed < 0) + resolvedIndirection(var); } handle(&this, 0); } - /// If this variable is a reference to another, takes into account dereferencing. - void visitIfReadOfReferenceToAnotherVar(scope void delegate(DFAVar* var) resolvedIndirection) - { - void handle(DFAVar* var, int refed) - { - if (!(var.base1 !is null || var.base2 !is null || refed != 0)) + void visitIndirectSources(scope void delegate(DFAVar* var, bool hadAnIndirection, bool hadAnOuterDeref, + bool takenAddressOf, bool hadFields, bool isOffsetOfStorage, ref bool unknown) del) + { + //&( base T + // storage = storage + //&( base T* + // obj1 = storage + //&( base T[] + // storage = storage + //&( base T*[] + // obj1 = obj1 + //&( base T . field T + // storage storage = storage + //&( base T . field T[] + // storage storage = storage + //&( base T* . field T + // obj1 obj1 = obj1 + //&( base T* . field T[] + // obj1 obj1 = obj1 + //&( base T* . field T* + // obj1 obj2 = obj1 + //&( base T* . field T*[] + // obj1 obj2 = obj2 + //&( base T* . field T* . field T + // obj1 obj2 obj2 = obj2 + + bool unknown; + + void findStorage(DFAVar* var, int derefCount, int indirectCount, + bool takenAddressOf, int fieldResolve, int isOffsetOfStorage) + { + if (unknown) return; - while (var.base2 is null && var.base1 !is null) + while (var !is null) { - if (var.offsetFromBase != -1 && var.var is null) - refed++; - else if (var.base1.dereferenceVar is var) - refed--; - else if (var.base1.indexVar is var) - refed++; - else if (var.base1.lengthVar is var && var.base1.isStaticArray - && !var.base1.haveBase) - return; // Statically known length, base doesn't matter as it won't be read - else if (var.base1.asSliceVar is var && !var.base1.haveBase && refed == 0) - return; // base1[] is a slice this does not inherently make it a read - else + version (none) + { + printf("finding storage for %p\n", var); + } + + if (var.offsetFromBase != -1) + { + if (var.isNullable) + { + indirectCount++; + isOffsetOfStorage = 0; + } + + if (indirectCount == 0) + fieldResolve++; + else if (indirectCount == 0) + isOffsetOfStorage++; + } + + if (var.base1 !is null) + { + if (var.base1.indexVar is var && indirectCount == 0) + isOffsetOfStorage++; + else if (var.base1 !is null && (var.base1.addressOfVar is var + || var.base1.asSliceVar is var)) + { + // a.b[] is equivalent to &a.b so we want to find storage of a.b aka a + takenAddressOf = true; + } + } + + if (var.base2 !is null) + { + findStorage(var.base2, derefCount, indirectCount, + takenAddressOf, fieldResolve, isOffsetOfStorage); + findStorage(var.base1, derefCount, indirectCount, + takenAddressOf, fieldResolve, isOffsetOfStorage); + return; + } + + if (var.base1 is null) break; + var = var.base1; + } + + // If we've reached here, we have skipped over any combiners base1+base2, + // and we've got ourselves a real variable/offset on to one. + + if (var is null) + return; + version (none) + { + printf("Found a storage var %p\n", var); + } + + del(var, indirectCount > 0, derefCount > 0, takenAddressOf, + fieldResolve > 0, isOffsetOfStorage > 0, unknown); + } + + void findOffset(DFAVar* var, int derefCount, int fieldResolve, int isOffsetOfStorage) + { + bool foundTakeAddressOf; + + while (var !is null) + { + version (none) + { + printf("seeing offset var %p offsetFromBase %lld isNullable %d\n", + var, var.offsetFromBase, var.isNullable); + + if (var.base1 !is null) + printf(" base1 %p, asSliceVar=%p/%d, indexVar=%p/%d\n", var.base1, var.base1.asSliceVar, + var.base1.asSliceVar is var, var.base1.indexVar, + var.base1.indexVar is var); + if (var.var !is null) + printf(" var ident %s\n", var.var.ident !is null + ? var.var.ident.toChars : null); + } + + if (var.offsetFromBase != -1) + { + fieldResolve++; + + if (var.isNullable) + { + // found some kind of offset that is an indirection i.e. + //&( base T* . field T* + // obj1 obj2 = obj1 + var = var.base1; + isOffsetOfStorage = 0; + break; + } + else if (var.offsetFromBase > 0) + isOffsetOfStorage++; + } + + if (var.base1 !is null) + { + if (var.base1.indexVar is var) + { + // var[0] is equivalent to doing *var + isOffsetOfStorage++; + derefCount++; + break; + } + else if (var.base1.dereferenceVar is var) + { + derefCount++; + break; + } + else if (var.base1 !is null && (var.base1.addressOfVar is var + || var.base1.asSliceVar is var)) + { + // a.b[] is equivalent to &a.b so we want to find storage of a.b aka a + foundTakeAddressOf = true; + var = var.base1; + break; + } + } + + if (var.base2 !is null) + { + findOffset(var.base2, derefCount, fieldResolve, isOffsetOfStorage); + findOffset(var.base1, derefCount, fieldResolve, isOffsetOfStorage); + return; + } + + if (var.base1 is null) + break; var = var.base1; } - if (var.base2 !is null) + // If we've reached here, we have skipped over any combiners base1+base2, + // and we've got ourselves a real variable/offset on to one. + + if (var is null) + return; + + version (none) { - handle(var.base1, refed); - handle(var.base2, refed); + printf("got offset var %p foundTakenAddressOf %d fieldResolve %d\n", + var, foundTakeAddressOf, fieldResolve); } - else if (refed < 0) - resolvedIndirection(var); + + findStorage(var, derefCount, 0, foundTakeAddressOf, fieldResolve, isOffsetOfStorage); } - handle(&this, 0); + findOffset(&this, 0, 0, 0); } bool isModellable() @@ -1654,12 +1956,40 @@ struct DFAObject DFAVar* storageFor; + // The scope variable that applied minimumDeclaredAtDepth + // onTheStack stores if its the cell lifetime, otherwise its probably for a parameter. + DFAVar* constrainedBy; + + // This object is derived from a pointer, specifically foo in: &foo.bar.dar + DFAObject* derivedFrom; + + // This is in the following cell i.e. pointer to array member or pointer to variable + DFAObject* inCell; + + // Having a base means it could be one of these. + // We do not know which one object specifically this will have at runtime. DFAObject* base1; DFAObject* base2; + // Helper, stores the minimum derivedAtDepth value between the bases, derivedFrom and storageFor, -1 for not set + int minimumDeclaredAtDepth; + // Pointer arithmetic may mean this object isn't 1:1 with the object start. bool mayNotBeExactPointer; + // If we were allocated just to be able to track a variable + bool defaultTrackObj; + + // Do we point to one or more stack variables storages + bool onTheStack; + + // The user understands the lifetime requirements for this object. + // Delay errors in case they actually don't. + bool lifeTimeUnderstood; + + // This object has escaped, but we're going to delay any error until it has been read. + bool delayOnReadErrorOfEscape; + void walkRoots(scope void delegate(DFAObject* root) del) { DFAObject* obj = &this; @@ -1674,6 +2004,111 @@ struct DFAObject del(obj); } + + void walk(scope bool delegate(DFAObject* root) del) + { + DFAObject* obj = &this; + + while (obj.base1 !is null) + { + if (!del(obj)) + return; + + if (obj.base2 !is null) + obj.base2.walk(del); + + obj = obj.base1; + } + + del(obj); + } + + void walkIndirection(scope void delegate(DFAObject* root, bool hadAnIndirection, bool haveConstraintOnLifeTime, + bool inCell) findRootsDel, scope void delegate(DFAObject* constrained, + bool hadAnIndirection, bool haveConstraintOnLifeTime) findConstraintsDel) + { + void findRoots(DFAObject* current, int indirectCount, + int constrainedMinimumLifetime, bool inCell) + { + while (current.base1 !is null || current.derivedFrom !is null) + { + if (current.constrainedBy !is null + && current.constrainedBy.oldestLifeTimeAllowedDepth + > constrainedMinimumLifetime) + constrainedMinimumLifetime = current.constrainedBy.oldestLifeTimeAllowedDepth; + + if (current.derivedFrom !is null) + { + if (current.base1 !is null) + findRoots(current.derivedFrom, indirectCount + 1, + constrainedMinimumLifetime, inCell); + else + { + current = current.derivedFrom; + indirectCount++; + continue; + } + } + + if (current.inCell !is null) + findRoots(current.inCell, 0, constrainedMinimumLifetime, true); + + if (current.base2 !is null) + findRoots(current.base2, indirectCount, constrainedMinimumLifetime, inCell); + + current = current.base1; + } + + if (current.inCell !is null) + findRoots(current.inCell, 0, constrainedMinimumLifetime, true); + else + findRootsDel(current, indirectCount > 0, + constrainedMinimumLifetime >= 0, inCell && indirectCount == 0); + } + + void findConstraints(DFAObject* current, int indirectCount, int constrainedMinimumLifetime) + { + while (current.base1 !is null || current.derivedFrom !is null) + { + if (current.constrainedBy !is null) + { + findConstraintsDel(current, indirectCount > 0, constrainedMinimumLifetime >= 0); + + if ( + current.constrainedBy.oldestLifeTimeAllowedDepth + > constrainedMinimumLifetime) + constrainedMinimumLifetime = current.constrainedBy + .oldestLifeTimeAllowedDepth; + } + + if (current.derivedFrom !is null) + { + if (current.base1 !is null) + findConstraints(current.derivedFrom, indirectCount + 1, + constrainedMinimumLifetime); + else + { + current = current.derivedFrom; + indirectCount++; + continue; + } + } + + if (current.inCell !is null) + findConstraints(current.inCell, 0, constrainedMinimumLifetime); + + if (current.base2 !is null) + findConstraints(current.base2, indirectCount, constrainedMinimumLifetime); + + current = current.base1; + } + } + + if (findRootsDel !is null) + findRoots(&this, 0, -1, false); + if (findConstraintsDel !is null) + findConstraints(&this, 0, -1); + } } struct DFAScopeRef @@ -1703,6 +2138,20 @@ struct DFAScopeRef return sc is null; } + uint controlFlowJumped() + { + if (isNull) + return 0; + return sc.controlFlow & DFAScope.ControlFlow.HaveJumped; + } + + uint controlFlowReturned() + { + if (isNull) + return 0; + return sc.controlFlow & DFAScope.ControlFlow.HaveReturned; + } + DFAScopeVar* findScopeVar(DFAVar* contextVar) { if (sc is null) @@ -1776,31 +2225,28 @@ struct DFAScopeRef return ret; } - void printStructure(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printStructure(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.sc.printStructure(prefix, sdepth, currentFunction, depth); + this.sc.printStructure(prefix, sdepth, depth); } - void printState(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printState(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.sc.printState(prefix, sdepth, currentFunction, depth); + this.sc.printState(prefix, sdepth, depth); } - void printActual(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printActual(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.sc.printActual(prefix, sdepth, currentFunction, depth); + this.sc.printActual(prefix, sdepth, depth); } int opApply(scope int delegate(DFALattice*) dg) @@ -1866,8 +2312,7 @@ struct DFAScope DFAScopeVar*[16] buckets; int depth; - bool haveJumped; // thrown, goto, break, continue, return - bool haveReturned; + uint controlFlow; bool isLoopyLabel; // Is a loop or label bool isLoopyLabelKnownToHaveRun; // was the loopy label guaranteed to have at least one iteration? bool inConditional; @@ -1883,6 +2328,32 @@ struct DFAScope DFAScopeRef beforeScopeState, afterScopeState; + /// What control flow has occured on this scope + enum ControlFlow : uint + { + Unknown = 0x00, + Thrown = 0x01, + Goto = 0x02, + Break = 0x04, + Continue = 0x08, + Return = 0x10, + EndOfFunction = 0x20, + NoReturn = 0x40, + + HaveJumped = Thrown | Goto | Break | Continue | Return | EndOfFunction | NoReturn, + HaveReturned = Thrown | Return | EndOfFunction | NoReturn, + } + + bool controlFlowReturned() + { + return (this.controlFlow & ControlFlow.HaveReturned) > 0; + } + + bool controlFlowJumped() + { + return (this.controlFlow & ControlFlow.HaveJumped) > 0; + } + DFALattice* findLattice(DFAVar* contextVar) { assert(contextVar !is null); // if this is null idk what is going on! @@ -1983,9 +2454,6 @@ struct DFAScope DFAScopeVar* assignLattice(DFAVar* contextVar, DFALatticeRef lr) { - if (lr.isNull) - return null; - DFAScopeVar** bucket = &this.buckets[cast(size_t) contextVar % this.buckets.length]; while (*bucket !is null && (*bucket).var < contextVar) @@ -2007,30 +2475,44 @@ struct DFAScope return scv; } - void printStructure(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printStructure(const(char)* prefix = "", int sdepth = 0, int depth = 0) { static if (!this.dfaCommon.debugStructure) return; else - printActual(prefix, sdepth, currentFunction, depth); + printActual(prefix, sdepth, depth); } - void printState(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printState(const(char)* prefix = "", int sdepth = 0, int depth = 0) { static if (!this.dfaCommon.debugIt) return; else - printActual(prefix, sdepth, currentFunction, depth); + printActual(prefix, sdepth, depth); } - void printActual(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printActual(const(char)* prefix = "", int sdepth = 0, int depth = 0) { - printPrefix("%s Scope", sdepth, currentFunction, depth, prefix); - printf(" %p depth=%d, completed=%d:%d", &this, this.depth, - this.haveReturned, this.haveJumped); + printPrefix("%s Scope", sdepth, depth, prefix); + printf(" %p depth=%d", &this, this.depth); + + { + printf(", controlFlow=("); + + uint printedCFI; + foreach (cf; __traits(allMembers, ControlFlow)) + { + if ((__traits(getMember, ControlFlow, cf) & this.controlFlow) > 0) + { + if (printedCFI++ == 0) + printf("%s", cf.ptr); + else + printf(" %s", cf.ptr); + } + } + + printf(")"); + } if (this.label !is null) printf(", label=`%s`\n", this.label.ident.toChars); @@ -2039,24 +2521,25 @@ struct DFAScope if (!this.beforeScopeState.isNull) { - printPrefix("%s before scope state:\n", sdepth, currentFunction, depth, prefix); - this.beforeScopeState.sc.printActual(prefix, sdepth, currentFunction, depth + 1); + printPrefix("%s before scope state:\n", sdepth, depth, prefix); + this.beforeScopeState.sc.printActual(prefix, sdepth, depth + 1); } if (!this.afterScopeState.isNull) { - printPrefix("%s after scope state:\n", sdepth, currentFunction, depth, prefix); - this.afterScopeState.sc.printActual(prefix, sdepth, currentFunction, depth + 1); + printPrefix("%s after scope state:\n", sdepth, depth, prefix); + this.afterScopeState.sc.printActual(prefix, sdepth, depth + 1); } - printPrefix("%s scv's:\n", sdepth, currentFunction, depth, prefix); + printPrefix("%s scv's:\n", sdepth, depth, prefix); foreach (contextVar, l, scv; this) { - printPrefix("%s on %p", sdepth, currentFunction, depth + 1, prefix, contextVar); + printPrefix("%s on %p", sdepth, depth + 1, prefix, contextVar); if (contextVar !is null) { - printf(";%d", contextVar.declaredAtDepth); + printf(";%d<%d", contextVar.oldestLifeTimeAllowedDepth, + contextVar.youngestLifeTimeAllowedDepth); if (contextVar.base1 !is null) printf(":%p", contextVar.base1); @@ -2076,14 +2559,14 @@ struct DFAScope if (this.isLoopyLabel) { - printPrefix("%s predicate:\n", sdepth, currentFunction, depth, prefix); - scv.lrGatePredicate.printActual(prefix, sdepth, currentFunction, depth + 1); - printPrefix("%s !predicate:\n", sdepth, currentFunction, depth, prefix); - scv.lrGateNegatedPredicate.printActual(prefix, sdepth, currentFunction, depth + 1); + printPrefix("%s predicate:\n", sdepth, depth, prefix); + scv.lrGatePredicate.printActual(prefix, sdepth, depth + 1); + printPrefix("%s !predicate:\n", sdepth, depth, prefix); + scv.lrGateNegatedPredicate.printActual(prefix, sdepth, depth + 1); } - printPrefix("%s lattice:\n", sdepth, currentFunction, depth, prefix); - l.printActual(prefix, sdepth, currentFunction, depth + 1); + printPrefix("%s lattice:\n", sdepth, depth, prefix); + l.printActual(prefix, sdepth, depth + 1); } } @@ -2182,6 +2665,90 @@ struct DFAScope } } +struct DFAArgumentListRef +{ + package DFAArgumentList* list; + + static if (DFACleanup) + { + this(ref DFAArgumentListRef other) + { + this.list = other.list; + other.list = null; + } + + ~this() + { + DFAArgumentList* current; + + while (this.list !is null) + { + current = this.list; + this.list = current.listnext; + + current.dfaCommon.allocator.free(current); + } + } + } + + bool isNull() + { + return list is null; + } + + DFAArgumentList.Each* opIndex(size_t i) + { + DFAArgumentList* current = this.list; + + while (current !is null) + { + if (i < current.count) + return ¤t.each[i]; + + i -= current.count; + current = current.listnext; + } + + return null; + } + + int opApply(scope int delegate(ubyte, DFAArgumentList*) del) + { + DFAArgumentList* current = this.list; + + while (current !is null) + { + foreach (i; 0 .. current.count) + { + const got = del(cast(ubyte) i, current); + if (got) + return got; + } + + current = current.listnext; + } + + return 0; + } +} + +struct DFAArgumentList +{ + DFAArgumentList* listnext; + DFACommon* dfaCommon; + + Each[32] each; + ParameterDFAInfo[32] infoBuffer; + + ubyte count; + + struct Each + { + DFALatticeRef lr; + ParameterDFAInfo* paramInfo; + } +} + struct DFALatticeRef { package DFALattice* lattice; @@ -2302,6 +2869,14 @@ struct DFALatticeRef return this.lattice.context.var; } + DFAObject* getContextObject() + { + if (isNull) + return null; + else + return lattice.context.obj; + } + DFAConsequence* setContext(DFAConsequence* c) { if (c is null) @@ -2394,28 +2969,25 @@ struct DFALatticeRef return this.lattice.opApply(dg); } - void printStructure(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printStructure(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.lattice.printStructure(prefix, sdepth, currentFunction, depth); + this.lattice.printStructure(prefix, sdepth, depth); } - void printState(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printState(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.lattice.printState(prefix, sdepth, currentFunction, depth); + this.lattice.printState(prefix, sdepth, depth); } - void printActual(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printActual(const(char)* prefix = "", int sdepth = 0, int depth = 0) { if (this.isNull) return; - this.lattice.printActual(prefix, sdepth, currentFunction, depth); + this.lattice.printActual(prefix, sdepth, depth); } DFALatticeRef copy() @@ -2580,33 +3152,34 @@ struct DFALattice } } - void printStructure(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printStructure(const(char)* prefix = "", int sdepth = 0, int depth = 0) { static if (!dfaCommon.debugStructure) return; else - printActual(prefix, sdepth, currentFunction, depth); + printActual(prefix, sdepth, depth); } - void printState(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + void printState(const(char)* prefix = "", int sdepth = 0, int depth = 0) { static if (!dfaCommon.debugIt) return; else - printActual(prefix, sdepth, currentFunction, depth); + printActual(prefix, sdepth, depth); } - private void printActual(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0) + private void printActual(const(char)* prefix = "", int sdepth = 0, int depth = 0) { - printPrefix("%s Lattice:\n", sdepth, currentFunction, depth, prefix); + bool gotOne; foreach (consequence; this) { - consequence.print(prefix, sdepth, currentFunction, depth, consequence is this.context); + consequence.print(prefix, sdepth, depth, consequence is this.context); + gotOne = true; } + + if (!gotOne) + printPrefix("%s Lattice empty\n", sdepth, depth, prefix); } int opApply(scope int delegate(DFAConsequence* consequence) dg) @@ -2857,6 +3430,16 @@ struct DFAPAValue return other; } + Truthiness truthiness() + { + if (this.kind == Kind.UnknownUpperPositive) + return Truthiness.True; + else if (this.kind == Kind.Concrete || this.kind == Kind.Lower) + return this.value > 0 ? Truthiness.True : Truthiness.False; + else + return Truthiness.Unknown; + } + bool canFitIn(Type type) { if (this.kind < Kind.Lower) @@ -3750,7 +4333,9 @@ struct DFAConsequence this.obj = other.obj; } - void meetConsequence(DFAConsequence* c1, DFAConsequence* c2, bool couldScopeNotHaveRan = false) + void meetConsequence(DFAConsequence* c1, DFAConsequence* c2, + bool couldScopeNotHaveRan = false, bool ignoreWriteCount = false, + bool preferUnknown = false) { void doOne(DFAConsequence* c) { @@ -3791,19 +4376,13 @@ struct DFAConsequence this.pa = couldScopeNotHaveRan ? DFAPAValue.Unknown : c1.pa.meet(c2.pa); - if (this.var is null || this.var.isTruthy) - { - this.truthiness = (couldScopeNotHaveRan && c1.truthiness != c2.truthiness) - ? Truthiness.Unknown : (c1.truthiness < c2.truthiness - ? c1.truthiness : c2.truthiness); - if (this.truthiness == Truthiness.Maybe) - this.truthiness = Truthiness.Unknown; - } - if (this.var is null || this.var.isNullable) { - this.nullable = (couldScopeNotHaveRan && c1.nullable != c2.nullable) - ? Nullable.Unknown : (c1.nullable < c2.nullable ? c1.nullable : c2.nullable); + if (preferUnknown) + this.nullable = (c1.nullable != c2.nullable) ? Nullable.Unknown : c1.nullable; + else + this.nullable = (couldScopeNotHaveRan && c1.nullable != c2.nullable) + ? Nullable.Unknown : (c1.nullable < c2.nullable ? c1.nullable : c2.nullable); if (c1.obj !is null || c2.obj !is null) { @@ -3813,6 +4392,25 @@ struct DFAConsequence this.obj = c1.obj !is null ? c1.obj : c2.obj; } } + + if (this.var is null || this.var.isBoolean) + { + if (preferUnknown) + this.truthiness = (c1.truthiness != c2.truthiness) + ? Truthiness.Unknown : c1.truthiness; + else + this.truthiness = (couldScopeNotHaveRan && c1.truthiness != c2.truthiness) + ? Truthiness.Unknown : (c1.truthiness < c2.truthiness + ? c1.truthiness : c2.truthiness); + + if (this.truthiness == Truthiness.Maybe) + this.truthiness = Truthiness.Unknown; + } + else if (this.var.isNullable) + this.truthiness = this.nullable == Nullable.NonNull ? Truthiness.True + : (this.nullable == Nullable.Null ? Truthiness.False : Truthiness.Unknown); + else if (this.var.isTruthy) + this.truthiness = this.pa.truthiness; } const writeCount = this.var.writeCount; @@ -3824,11 +4422,12 @@ struct DFAConsequence c2 !is null ? c2.var : null); printf(" writeCount=%d/%d:%d\n", writeCount, c1.writeOnVarAtThisPoint, c2 !is null ? c2.writeOnVarAtThisPoint : -1); - printf(" couldScopeNotHaveRan=%d\n", couldScopeNotHaveRan); + printf(" couldScopeNotHaveRan=%d, ignoreWriteCount=%d, preferUnknown=%d\n", + couldScopeNotHaveRan, ignoreWriteCount, preferUnknown); fflush(stdout); } - if (c2 is null || c2.writeOnVarAtThisPoint < writeCount) + if (c2 is null || (!ignoreWriteCount && c2.writeOnVarAtThisPoint < writeCount)) doOne(c1); else doMulti; @@ -3952,24 +4551,22 @@ struct DFAConsequence } } - void print(const(char)* prefix = "", int sdepth = 0, - FuncDeclaration currentFunction = null, int depth = 0, bool context = false) + void print(const(char)* prefix = "", int sdepth = 0, int depth = 0, bool context = false) { static immutable TruthinessStr = [ "unkno".ptr, "maybe".ptr, "false".ptr, " true".ptr ]; static immutable NullableStr = ["unkno".ptr, " null".ptr, "!null".ptr]; - printPrefix(" %s%s ", sdepth, currentFunction, depth, context ? "+".ptr : " ".ptr, prefix); - printf("%p: ", &this); + printPrefix(" %s%s ", sdepth, depth, context ? "+".ptr : " ".ptr, prefix); + printf("%08p: ", this.var); printf("truthiness=%s, nullable=%s, write=%d, ", TruthinessStr[this.truthiness], NullableStr[this.nullable], this.writeOnVarAtThisPoint); printf("maybe=%p:%d, protectElseNegate=%d, invertedOnce=%d ", maybe, maybeTopSeen, protectElseNegate, invertedOnce); - printf("previous=%p, next=%p, pa=%03d/%lld", this.previous, this.next, - this.pa.kind, this.pa.value); + printf("previous=%p, next=%p, pa=%03d/%lld, obj=%p ", this.previous, + this.next, this.pa.kind, this.pa.value, this.obj); - printf(", %p", this.var); if (this.var !is null) { printf("=%d:%lld:b/%p/%p", this.var.assertedCount, @@ -3979,7 +4576,6 @@ struct DFAConsequence printf("@%p=`%s`", this.var.var, this.var.var.toChars); } - printf(", obj=%p", this.obj); printf("\n", this.var); } } @@ -3994,24 +4590,30 @@ void applyType(DFAVar* var, VarDeclaration vd) var.isBoolean = vd.type.ty == Tbool; var.isFloatingPoint = vd.type.isTypeBasic !is null && (vd.type.isTypeBasic.flags & TFlags.floating) != 0; + var.isAA = vd.type.ty == Taarray; + + // don't trust DIP1000's scope infering on variables, we'll do our own equivalent behavior that is more dynamic. + var.isScope = (vd.storage_class & STC.scope_) != 0 && (vd.storage_class & STC.scopeinferred) == 0; + + var.isByRef = (vd.storage_class & STC.ref_) == STC.ref_; // Unfortunately isDataseg can have very undesirable side effects that kill compilation, // even if it shouldn't when this is ran. if (vd.parent !is null && vd.parent.isFuncDeclaration) { - // make sure the parent of this variable is a function, if it isn't then we can't model it. - if (!(vd.canTakeAddressOf - && (vd.storage_class & (STC.static_ | STC.extern_ | STC.gshared)) == 0)) - var.unmodellable = true; + // Make sure the parent of this variable is a function, if it isn't then we can't model it. + var.isStackVar = vd.canTakeAddressOf + && (vd.storage_class & (STC.static_ | STC.extern_ | STC.gshared)) == 0; } else - var.unmodellable = true; + var.isStackVar = false; + + var.unmodellable = !var.isStackVar; } -void printPrefix(Args...)(ref OutBuffer ob, const(char)* prefix, int sdepth, - FuncDeclaration currentFunction, int edepth, Args args) +void printPrefix(Args...)(ref OutBuffer ob, const(char)* prefix, int sdepth, int edepth, Args args) { - ob.printf("%.*s[%p]", sdepth, PrintPipeText.ptr, currentFunction); + ob.printf("%.*s", sdepth, PrintPipeText.ptr); if (edepth == 0) ob.write(">"); @@ -4021,10 +4623,9 @@ void printPrefix(Args...)(ref OutBuffer ob, const(char)* prefix, int sdepth, ob.printf(prefix, args); } -void printPrefix(Args...)(const(char)* prefix, int sdepth, - FuncDeclaration currentFunction, int edepth, Args args) +void printPrefix(Args...)(const(char)* prefix, int sdepth, int edepth, Args args) { - printf("%.*s[%p]", sdepth, PrintPipeText.ptr, currentFunction); + printf("%.*s", sdepth, PrintPipeText.ptr); if (edepth == 0) printf(">"); diff --git a/compiler/src/dmd/dfa/utils.d b/compiler/src/dmd/dfa/utils.d index 74da0a3e401b..60f1d46d5560 100644 --- a/compiler/src/dmd/dfa/utils.d +++ b/compiler/src/dmd/dfa/utils.d @@ -16,6 +16,177 @@ import dmd.visitor; import dmd.identifier; import dmd.expression; import dmd.typesem : isFloating; +import dmd.func; +import core.stdc.stdio; + +/// Ensure that a function declaration is properly attributed for the fast DFA engine. +ParametersDFAInfo* ensureDFAParameters(FuncDeclaration fd) +{ + if (fd is null || fd.parametersDFAInfo !is null) + return null; + + TypeFunction tf = fd.type.toTypeFunction; + fd.parametersDFAInfo = new ParametersDFAInfo; + + const listLength = tf.parameterList.length; + + if (tf.next !is null && tf.next.ty != Tvoid) + fd.parametersDFAInfo.returnValue.parameterId = -3; + + if (tf.isRef) + fd.parametersDFAInfo.returnValue.isByRef = true; + + if (fd.vthis !is null) + { + fd.parametersDFAInfo.thisPointer.parameterId = -2; + + // This pointer is always by-ref if its a struct + if (fd.vthis.type.isTypeStruct) + { + fd.parametersDFAInfo.thisPointer.isByRef = true; + fd.parametersDFAInfo.thisPointer.userSupplied.willEscape(-3, tf.isRef + ? ParameterDFAInfo.EscapedRelationship.PointerTo + : ParameterDFAInfo.EscapedRelationship.ByValue); + } + else if (tf.isReturn) + fd.parametersDFAInfo.thisPointer.userSupplied.willEscape(-3, + ParameterDFAInfo.EscapedRelationship.ByValue); + else if (tf.isScopeQual) + fd.parametersDFAInfo.thisPointer.userSupplied.escapeIntoNothing = true; + } + + { + // Getting the actual number of parameters is all over the place, depending on the stage of compilation. + + const countParams = listLength > 0 ? listLength : (fd.parameters !is null + ? fd.parameters.length : 0); + fd.parametersDFAInfo.parameters.length = countParams; + } + + foreach (i, ref paramDFAInfo; fd.parametersDFAInfo.parameters) + paramDFAInfo.parameterId = cast(int) i; + + { + const toModelCount = listLength <= 29 ? listLength : 29; + + foreach (i, param; tf.parameterList) + { + if (i > toModelCount) + break; + + ParameterDFAInfo* paramDFAInfo = &fd.parametersDFAInfo.parameters[i]; + const vd = fd.parameters !is null && fd.parameters.length > i + ? (*fd.parameters)[i] : null; + const stc = param.storageClass | (vd !is null ? vd.storage_class : 0); + + if (stc & (STC.ref_ | STC.out_ | STC.autoref)) + paramDFAInfo.isByRef = true; + + if (stc & (STC.return_ | STC.returnScope | STC.returnRef) && (stc & STC.returninferred) == 0) + paramDFAInfo.userSupplied.willEscape(-3, tf.isRef && paramDFAInfo.isByRef + ? ParameterDFAInfo.EscapedRelationship.PointerTo + : ParameterDFAInfo.EscapedRelationship.ByValue); + if ((stc & STC.scope_) && (stc & (STC.scopeinferred | STC.returnScope | STC.returnRef)) == 0) + paramDFAInfo.userSupplied.escapeIntoNothing = true; + } + } + + return fd.parametersDFAInfo; +} + +/// Ensure we have access to a description of a given function call, regardless of having a function declaration. +/// paramDFAInfo should have a buffer in init state passed in +void ensureDFAParameter(int id, FuncDeclaration fd, TypeFunction tf, + ref ParameterDFAInfo* paramDFAInfo) +{ + if (fd !is null) + { + assert(fd.parametersDFAInfo !is null); + if (id == -3) + paramDFAInfo = &fd.parametersDFAInfo.returnValue; + else if (id == -2) + paramDFAInfo = &fd.parametersDFAInfo.thisPointer; + else if (id == -1) + return; + else if (fd.parametersDFAInfo.parameters.length > id) + paramDFAInfo = &fd.parametersDFAInfo.parameters[id]; + return; + } + + if (tf !is null && tf.parameterList.parameters !is null + && tf.parameterList.parameters.length > id) + { + const stc = (*tf.parameterList.parameters)[id].storageClass; + + if (stc & (STC.ref_ | STC.out_ | STC.autoref)) + paramDFAInfo.isByRef = true; + + if (stc & (STC.return_ | STC.returnScope | STC.returnRef) && (stc & STC.returninferred) == 0) + paramDFAInfo.userSupplied.willEscape(-3, tf.isRef && paramDFAInfo.isByRef + ? ParameterDFAInfo.EscapedRelationship.PointerTo + : ParameterDFAInfo.EscapedRelationship.ByValue); + if ((stc & STC.scope_) && (stc & (STC.scopeinferred | STC.returnScope | STC.returnRef)) == 0) + paramDFAInfo.userSupplied.escapeIntoNothing = true; + } +} + +void printDFAParameters(ParametersDFAInfo* params) +{ + if (params is null) + { + printf("Params (null)\n"); + return; + } + + printf("Params:\n"); + if (params.returnValue.parameterId == -3) + printDFAParameter(¶ms.returnValue); + if (params.thisPointer.parameterId == -2) + printDFAParameter(¶ms.thisPointer); + + foreach (ref param; params.parameters) + printDFAParameter(¶m); +} + +void printDFAParameter(ParameterDFAInfo* param) +{ + if (param is null) + { + printf("Param (null)\n"); + return; + } + + printf("- Param %d, null=%d/%d:%d/%d, by-ref=%d, escapeIntoNothing=%d/%d, escapes=(%lld/%lld ", + param.parameterId, param.userSupplied.notNullIn, + param.inferred.notNullIn, param.userSupplied.notNullOut, param.inferred.notNullOut, param.isByRef, + param.userSupplied.escapeIntoNothing, param.inferred.escapeIntoNothing, + param.userSupplied.escapesInto, param.inferred.escapesInto); + + ulong escapesIntoUser = param.userSupplied.escapesInto, + escapeIntoInferred = param.inferred.escapesInto; + + foreach (i; 0 .. 4) + { + foreach (j; 0 .. 8) + { + printf("%01d", cast(int)(escapesIntoUser & 0x3)); + escapesIntoUser >>= 2; + } + + printf("/"); + + foreach (j; 0 .. 8) + { + printf("%01d", cast(int)(escapeIntoInferred & 0x3)); + escapeIntoInferred >>= 2; + } + + if (i < 3) + printf(" "); + } + + printf(")\n"); +} /*********************************************************** * Checks if a type is capable of being null at runtime. diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 9b7ed069299d..2b81f5a95524 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -184,16 +184,16 @@ extern(D) struct ParametersDFAInfo /// Information for data flow analysis per parameter extern(D) struct ParameterDFAInfo { - /// Parameter id: -1 this, -2 return, otherwise it is FuncDeclaration.parameters index + /// Parameter id: -1 for into unknown, -2 this, -3 return, otherwise it is FuncDeclaration.parameters index int parameterId; - /// Is the parameter non-null upon input, only applies to pointers. - Fact notNullIn; - /// Is the parameter non-null, applies only for by-ref parameters that are pointers. - Fact notNullOut; + /// User supplied attributes + Inferrable userSupplied; + /// Inferred attributes + Inferrable inferred; - /// Was the attributes for this parameter specified by the user? - bool specifiedByUser; + /// Is parameter by-ref, so will be unpredictable after call from callee's perspective + bool isByRef; /// Given a property, has it been specificed and is it guaranteed? enum Fact : ubyte @@ -205,6 +205,96 @@ extern(D) struct ParameterDFAInfo /// Guaranteed } + + /// + enum EscapedRelationship + { + /// + Unknown = 0b00, + /// + ByValue = 0b01, + /// + PointerTo = 0b10, + /// + Borrows = 0b11, + } + + /// + struct Inferrable + { + /// Is the parameter non-null upon input, only applies to pointers. + Fact notNullIn; + /// Is the parameter non-null, applies only for by-ref parameters that are pointers. + Fact notNullOut; + + /// If we escape into nothing, and still modellable (scope) + bool escapeIntoNothing; + + /** + return, this, into-the-unknown, (29x) parameters... + + 00 = unknown + 01 = by-value + 10 = pointer-to + 11 = borrows + */ + ulong escapesInto; + + /// Will this parameter escape into the following parameter id, at what relationship strength? + EscapedRelationship willEscape(int id) + { + if (id >= 29) + return EscapedRelationship.Unknown; + + if (id == -3) + return cast(EscapedRelationship)(this.escapesInto & 0x3); + else if (id == -2) + return cast(EscapedRelationship)((this.escapesInto >> 2) & 0x3); + else if (id == -1) + return cast(EscapedRelationship)((this.escapesInto >> 4) & 0x3); + + this.escapesInto >>= 6 + (id * 2); + return cast(EscapedRelationship)(this.escapesInto & 0x3); + } + + /// Set this parameter's relationship strength to the following parameter id. + void willEscape(int id, EscapedRelationship as) + { + if (id >= 29) + return; + + int shift; + + if (id == -3) + shift = 0; + else if (id == -2) + shift = 2; + else if (id == -1) + shift = 4; + else + shift = 6 + (id * 2); + + const ulong mask = 3UL << shift; + this.escapesInto = (this.escapesInto & ~mask) | ((cast(ulong) as & 0x3) << shift); + } + } + + /// + Fact notNullIn() + { + return inferred.notNullIn > userSupplied.notNullIn ? inferred.notNullIn : userSupplied.notNullIn; + } + + /// + Fact notNullOut() + { + return inferred.notNullOut > inferred.notNullOut ? inferred.notNullOut : userSupplied.notNullOut; + } + + bool escapeIntoNothing() + { + return userSupplied.escapeIntoNothing || inferred.escapeIntoNothing; + } } /*********************************************************** diff --git a/compiler/src/dmd/mangle/cppwin.d b/compiler/src/dmd/mangle/cppwin.d index 8a674dcb0f51..b4fbf1bf7082 100644 --- a/compiler/src/dmd/mangle/cppwin.d +++ b/compiler/src/dmd/mangle/cppwin.d @@ -79,9 +79,10 @@ private extern (C++) final class VisualCPPMangler : Visitor extern (D) this(VisualCPPMangler rvl) scope @safe { - saved_idents[] = rvl.saved_idents[]; - saved_types[] = rvl.saved_types[]; - loc = rvl.loc; + this.saved_idents[] = rvl.saved_idents[]; + this.saved_types[] = rvl.saved_types[]; + this.loc = rvl.loc; + this.eSink = rvl.eSink; } public: diff --git a/compiler/src/dmd/mtype.d b/compiler/src/dmd/mtype.d index 3dc6871abf80..1d85b72652a7 100644 --- a/compiler/src/dmd/mtype.d +++ b/compiler/src/dmd/mtype.d @@ -2183,6 +2183,7 @@ extern (C++) final class TypeTag : Type extern (C++) struct ParameterList { /// The raw (unexpanded) formal parameters, possibly containing tuples. + /// If you are wanting to inspect the parameters, do not iterate on parameters field, use opApply. Parameters* parameters; STC stc; // storage class of ... VarArg varargs = VarArg.none; diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 8056a0115672..86d7d909ce3a 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1460,7 +1460,7 @@ private extern(C++) final class Semantic3Visitor : Visitor version (FastDFA) { - if (global.params.useFastDFA && global.errors == oldErrors && funcdecl.fbody && funcdecl.type.ty != Terror) + if (/+global.params.useFastDFA &&+/ global.errors == oldErrors && funcdecl.fbody && funcdecl.type.ty != Terror) { // Don't run DFA if there are errors, // this is a costly enough operation that it warrents the explicit check. diff --git a/compiler/test/compilable/__fastdfa_escape_test.d b/compiler/test/compilable/__fastdfa_escape_test.d new file mode 100644 index 000000000000..41742962e51d --- /dev/null +++ b/compiler/test/compilable/__fastdfa_escape_test.d @@ -0,0 +1,217 @@ +// DO NOT RENAME THIS MODULE, name enables __FastDFAEscapeTest UDA + +/* +REQUIRED_ARGS: -preview=fastdfa +*/ +module __fastdfa_escape_test; + +struct __FastDFAEscapeTest { + Param[] params; +} + +struct Param { + bool escapeIntoNothing; + ulong escapeInto; + + this(bool escapeIntoNothing, ulong escapeInto) { + this.escapeIntoNothing = escapeIntoNothing; + this.escapeInto = escapeInto; + } +} + +struct Base { + int* field; +} + +struct S1 +{ + S2 s2; +} + +struct S2 +{ + int field; +} + +struct S3 +{ + S2* s2; +} + +enum Nothing = __FastDFAEscapeTest(null); +enum EscapeToGlobalPtr = __FastDFAEscapeTest([Param(false, 32)]); +enum NoWhere001 = __FastDFAEscapeTest([Param(true, 0)]); +enum Returns001 = __FastDFAEscapeTest([Param(false, 1)]); +enum Returns001Ptr = __FastDFAEscapeTest([Param(false, 2)]); + +#line 1000 + +// +// +// + +@NoWhere001 +void goesNoWhere1(int* ptr) { +} + +@NoWhere001 +void goesNoWhere2(scope int* ptr) { +} + +@NoWhere001 +void goesNoWhere3(int* ptr) { + int* ptr2 = ptr; +} + +@NoWhere001 +void goesNoWhere4(scope int* ptr) { + int* ptr2 = ptr; +} + +@NoWhere001 +void goesNoWhere5(int* ptr) { + int* ptr2 = ptr; + int var = *ptr2; +} + +@NoWhere001 +void goesNoWhere6(scope int* ptr) { + int* ptr2 = ptr; + int var = *ptr2; +} + +@Returns001Ptr +int* returnArg1(int* ptr) { + return ptr; +} + +@Returns001 +int* returnArg2(ref int* ptr) { + return ptr; +} + +@Returns001Ptr +ref int returnArgRef1(ref int ptr) { + return ptr; +} + +@Returns001Ptr +ref int* returnArgRef2(ref int* ptr) { + return ptr; +} + +@Returns001Ptr +int** returnArgRef3(ref int* ptr) { + return &ptr; +} + +@Returns001Ptr +ref S2 returnArgRef4(ref const(S2) x) +{ + return cast() x; // ok +} + +@Returns001 +int* returnArgField(Base* base) { + return base.field; +} + +@Returns001Ptr +int** returnArgPtr(Base* base) { + return &base.field; +} + +@Nothing +void trackEscape1() +{ + S1 s1; + int* ptr = &s1.s2.field; // ok + S2* s2 = &s1.s2; // ok +} + +@Returns001Ptr +S2* trackEscape2(S1* s1) +{ + int* ptr = &s1.s2.field; // ok + return &s1.s2; // ok +} + +@Nothing +void trackEscape5() +{ + int* ptr; + + { + int var; + ptr = &var; + } // no error +} // no error + +@Nothing +int* trackEscape7() +{ + S3 var; + return &var.s2.field; // ok +} + +@NoWhere001 +void trackEscape10(int* ptr) +{ + int buf; + ptr = &buf; // ok +} + +@EscapeToGlobalPtr +void escapeToGlobalSystem2(scope int* ptr1) @system +{ + __gshared int* ptr2; + + ptr2 = ptr1; // ok +} + +@Returns001Ptr +int* returnArrayElement(int[] data) +{ + foreach (ref datem; data) + { + return &datem; // ok + } + + return null; +} + +// +// +// + +@Returns001Ptr +int* callReturn(int* input) { + return returnArg1(input); +} + +@Returns001 +int* callReturnArg2(ref int* ptr) { + return returnArg2(ptr); +} + +@Returns001Ptr +int** callReturnArgRef3(ref int* ptr) { + return returnArgRef3(ptr); +} + +@Returns001Ptr +int** callReturnArgPtr(Base* base) { + return returnArgPtr(base); +} + +@Returns001 +int* argValue(scope Base* base) { + return base.field; +} + +@Nothing +S2** pointerToObjectField() +{ + S3* s = new S3; + return &s.s2; +} // ok diff --git a/compiler/test/compilable/fastdfa.d b/compiler/test/compilable/fastdfa.d index 23d6b3daf6ac..ef809ffaf42f 100644 --- a/compiler/test/compilable/fastdfa.d +++ b/compiler/test/compilable/fastdfa.d @@ -2,6 +2,32 @@ * REQUIRED_ARGS: -preview=fastdfa */ +struct S1 +{ + S2 s2; +} + +struct S2 +{ + int field; +} + +struct S3 +{ + S2* s2; +} + +struct S4 +{ + int* field; +} + +struct S5 +{ + S5* next; + S5* another; +} + void typeNextIterate(Type t) { Type ts; @@ -69,6 +95,86 @@ void pickPtr(bool condition) int* ptr = condition ? &i1 : &i2; } +void uninitInitStruct() +{ + S1 s = void; + s.s2 = S2(); // ok, may init +} + +void uninitFieldWillInit() +{ + S2 s = void; + s.field = 2; // ok +} + +void uninitTakePointer() +{ + int var = void; + int* ptr = &var; // ok +} + +void uninitBufferTakePointer1() +{ + char[64] buf = void; + char* ptr = buf.ptr; // ok +} + +void uninitBufferTakePointer2() +{ + char[64] buf = void; + char* ptr = &buf[0]; // ok +} + +void pointerToUninitBufferCall() +{ + void toCall(ubyte*) + { + } + + ubyte[64] buf = void; + ubyte* ptr = buf.ptr; + + toCall(ptr); +} + +void uninitPointerArithmetic() +{ + ubyte[16] result = void; + auto p2 = cast(ulong*)((cast(void*)&result) + 8); // ok +} + +void escapeCleanupRequiredNoRead() +{ + struct S + { + ~this() + { + } + } + + S* ptr; + + { + S buf; + ptr = &buf; + buf.__xdtor; + } // ok +} + +void escapeToGlobalSystem1() +{ + __gshared int* ptr; + int var; + + ptr = &var; // ok +} + +void nullPtrVarDerefOuter() +{ + int* ptr; + *(&ptr) = new int; +} + @safe: bool isNull1(int* ptr) @@ -115,12 +221,12 @@ int* nullable1(int* ptr) return ret; } -void nullable2a(S2* head, S2* previous) +void nullable2a(S5* head, S5* previous) { - S2* start; + S5* start; { - S2* current; + S5* current; if (start is null) { @@ -137,10 +243,10 @@ void nullable2a(S2* head, S2* previous) } } -void nullable2b(S2* start, S2* head, S2* previous) +void nullable2b(S5* start, S5* head, S5* previous) { { - S2* current; + S5* current; if (start is null) { @@ -157,7 +263,7 @@ void nullable2b(S2* start, S2* head, S2* previous) } } -void nullable3(S2** nextParent, S2* r) +void nullable3(S5** nextParent, S5* r) { if (*nextParent is null) *nextParent = r; // should not error @@ -168,7 +274,7 @@ void nullable4(int* temp) @trusted int buffer; if (temp is null) - temp = &buffer; + temp = &buffer; // okay, same loopy label lifetime int v = *temp; // should not error } @@ -182,18 +288,28 @@ void truthiness1() bool b = !a, c = a != false, d = a == false; } -struct S1 +void theSitchFinally1() { - int* field; -} + { + goto Label; + } // have jumped -struct S2 -{ - S2* next; - S2* another; + { + Label: // don't know that we consumed the jump + } + + // \/ ignored + + int* ptr; + + scope (exit) + int vS = *ptr; // ok + + int vMid = *ptr; // ok + truthinessNo; } -void trackS1(S1 s) +void trackS1(S4 s) { bool b = s.field !is null; } @@ -389,7 +505,7 @@ void rotateForChildren(void** parent) return; } -void removeValue(S2* valueNode) +void removeValue(S5* valueNode) { if (valueNode.another !is null) valueNode.next.next = valueNode.next; @@ -1121,3 +1237,34 @@ void checkFloatInit5() { float t = var * 2; } } + +ubyte[] copySliceAssign1(ubyte[] input) +{ + ubyte[] output = new ubyte[input.length]; + return output[] = input[]; +} // ok + +ubyte[][] copySliceAssign2(ubyte[] input) +{ + ubyte[][] output = new ubyte[][input.length]; + return output[] = input; +} // ok + +void branchMayNull(S2 cond) +{ + int* ptr; + + if (cond.field == 2) + ptr = new int; + else + ptr = null; + + if (cond.field == 2) + int val = *ptr; +} + +void unsafeThrowOfStack1() +{ + scope string myText = "hi"; + throw new Exception(myText); // ok, constructor for Exception is not annotated +} diff --git a/compiler/test/compilable/ftimetrace_inline.d b/compiler/test/compilable/ftimetrace_inline.d index d6082794a07a..5d3f027d3b13 100644 --- a/compiler/test/compilable/ftimetrace_inline.d +++ b/compiler/test/compilable/ftimetrace_inline.d @@ -7,6 +7,8 @@ Code generation, Codegen: function add, object.add Codegen: function uses, object.uses Codegen: module object, object +DFA: add, object.add +DFA: uses, object.uses Import object.object, object.object Inline: add, object.add Inline: uses, object.uses diff --git a/compiler/test/compilable/ftimetrace_pragma.d b/compiler/test/compilable/ftimetrace_pragma.d index 6d690e420617..50c2fd94adec 100644 --- a/compiler/test/compilable/ftimetrace_pragma.d +++ b/compiler/test/compilable/ftimetrace_pragma.d @@ -8,6 +8,9 @@ Codegen: function cube, object.cube Codegen: function square, object.square Codegen: function uses, object.uses Codegen: module object, object +DFA: cube, object.cube +DFA: square, object.square +DFA: uses, object.uses Import object.object, object.object Inline: cube, object.cube Inline: uses, object.uses diff --git a/compiler/test/fail_compilation/__fastdfa_escape_test.d b/compiler/test/fail_compilation/__fastdfa_escape_test.d new file mode 100644 index 000000000000..3b150bcfc12d --- /dev/null +++ b/compiler/test/fail_compilation/__fastdfa_escape_test.d @@ -0,0 +1,85 @@ +// DO NOT RENAME THIS MODULE, name enables __FastDFAEscapeTest UDA + +/* +REQUIRED_ARGS: -preview=fastdfa +TEST_OUTPUT: +--- +fail_compilation/__fastdfa_escape_test.d(1006): Error: Parameter is required to be scope but escapes +fail_compilation/__fastdfa_escape_test.d(1006): Escapes directly +fail_compilation/__fastdfa_escape_test.d(1014): Error: Escape of unknown lifetime via throw +fail_compilation/__fastdfa_escape_test.d(1013): Pointer stored in variable `myEx` has potentially escaped +fail_compilation/__fastdfa_escape_test.d(1020): Error: Escape of unknown lifetime via throw +fail_compilation/__fastdfa_escape_test.d(1018): Pointer stored in variable `e` has potentially escaped +fail_compilation/__fastdfa_escape_test.d(1032): Error: Stack variable stores a lifetime that exceeds its own +fail_compilation/__fastdfa_escape_test.d(1026): For variable `ptr` +fail_compilation/__fastdfa_escape_test.d(1030): A pointer to the cell of the variable `buf` has potentially escaped +fail_compilation/__fastdfa_escape_test.d(1040): Error: Stack variable exceeds its lifetime by being returned +fail_compilation/__fastdfa_escape_test.d(1038): Pointer stored in variable `ptr` has potentially escaped +--- +*/ +module __fastdfa_escape_test; + +struct __FastDFAEscapeTest { + Param[] params; +} + +struct Param { + bool escapeIntoNothing; + ulong escapeInto; + + this(bool escapeIntoNothing, ulong escapeInto) { + this.escapeIntoNothing = escapeIntoNothing; + this.escapeInto = escapeInto; + } +} + +struct Base { + int* field; +} + +enum Nothing = __FastDFAEscapeTest(null); +enum NoWhere001 = __FastDFAEscapeTest([Param(true, 0)]); +enum Returns001Ptr = __FastDFAEscapeTest([Param(false, 2)]); + +#line 1000 + +// +// +// + +@Returns001Ptr +int** argValueScopeReturnError(scope Base* base) @safe { // error + return &base.field; +} + +@Nothing +void unsafeThrowOfStack2() +{ + scope Exception myEx = new Exception("myText"); + throw myEx; // error +} + +@NoWhere001 +void unsafeThrowOfStack3(scope Exception e) +{ + throw e; // error +} + +@Nothing +void trackEscape6() +{ + int* ptr; + + foreach (i; 0 .. 2) + { + int buf; + ptr = &buf; + } // error +} // no error + +@Nothing +int* trackEscape9() +{ + scope int* ptr = new int; + return ptr; +} // error diff --git a/compiler/test/fail_compilation/fastdfa.d b/compiler/test/fail_compilation/fastdfa.d index b6c166bffbd8..597f1d4b787b 100644 --- a/compiler/test/fail_compilation/fastdfa.d +++ b/compiler/test/fail_compilation/fastdfa.d @@ -2,45 +2,55 @@ * REQUIRED_ARGS: -preview=fastdfa * TEST_OUTPUT: --- -fail_compilation/fastdfa.d(63): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(78): Error: Argument is expected to be non-null but was null -fail_compilation/fastdfa.d(71): For parameter `ptr` in argument 0 -fail_compilation/fastdfa.d(96): Error: Dereference on null variable `ptr` -fail_compilation/fastdfa.d(94): Error: Dereference on null variable `ptr` -fail_compilation/fastdfa.d(104): Error: Variable `ptr` was required to be non-null and has become null -fail_compilation/fastdfa.d(125): Error: Variable `ptr` was required to be non-null and has become null -fail_compilation/fastdfa.d(140): Error: Dereference on null variable `ptr` -fail_compilation/fastdfa.d(162): Error: Dereference on null variable `ptr` -fail_compilation/fastdfa.d(179): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(185): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(194): Error: Dereference on null variable `ptr` -fail_compilation/fastdfa.d(209): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(217): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(219): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(226): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(233): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(237): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(239): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(249): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(250): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(264): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(273): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(289): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(295): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading -fail_compilation/fastdfa.d(294): For variable `val1` -fail_compilation/fastdfa.d(298): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading -fail_compilation/fastdfa.d(294): For variable `val1` -fail_compilation/fastdfa.d(305): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(312): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(348): Error: Assert can be proven to be false -fail_compilation/fastdfa.d(355): Error: Expression reads a default initialized variable that is a floating point type -fail_compilation/fastdfa.d(355): It will have the value of Not Any Number(nan), it will be propagated with mathematical operations -fail_compilation/fastdfa.d(354): For variable `v` -fail_compilation/fastdfa.d(354): Initialize to float.nan or 0 explicitly to disable this error -fail_compilation/fastdfa.d(365): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading -fail_compilation/fastdfa.d(364): For variable `v` +fail_compilation/fastdfa.d(1019): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1034): Error: Argument is expected to be non-null but was null +fail_compilation/fastdfa.d(1027): For parameter `ptr` in argument 0 +fail_compilation/fastdfa.d(1044): Error: Dereference on null variable `ptr` +fail_compilation/fastdfa.d(1042): Error: Dereference on null variable `ptr` +fail_compilation/fastdfa.d(1051): Error: Variable `ptr` was required to be non-null and has become null +fail_compilation/fastdfa.d(1072): Error: Variable `ptr` was required to be non-null and has become null +fail_compilation/fastdfa.d(1087): Error: Dereference on null variable `ptr` +fail_compilation/fastdfa.d(1109): Error: Dereference on null variable `ptr` +fail_compilation/fastdfa.d(1126): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1132): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1141): Error: Dereference on null variable `ptr` +fail_compilation/fastdfa.d(1156): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1164): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1166): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1173): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1180): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1184): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1186): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1196): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1197): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1211): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1220): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1236): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1242): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading +fail_compilation/fastdfa.d(1241): For variable `val1` +fail_compilation/fastdfa.d(1245): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading +fail_compilation/fastdfa.d(1241): For variable `val1` +fail_compilation/fastdfa.d(1252): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1259): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1295): Error: Assert can be proven to be false +fail_compilation/fastdfa.d(1302): Error: Expression reads a default initialized variable that is a floating point type +fail_compilation/fastdfa.d(1302): It will have the value of Not Any Number(nan), it will be propagated with mathematical operations +fail_compilation/fastdfa.d(1301): For variable `v` +fail_compilation/fastdfa.d(1301): Initialize to float.nan or 0 explicitly to disable this error +fail_compilation/fastdfa.d(1312): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading +fail_compilation/fastdfa.d(1311): For variable `v` +fail_compilation/fastdfa.d(1320): Error: Stack variable exceeds its lifetime by being returned +fail_compilation/fastdfa.d(1318): Pointer stored in variable `b` has potentially escaped +fail_compilation/fastdfa.d(1339): Error: Stack variable stores a lifetime that exceeds its own +fail_compilation/fastdfa.d(1331): For variable `ptr` +fail_compilation/fastdfa.d(1334): A pointer to the cell of the variable `buf` has potentially escaped +fail_compilation/fastdfa.d(1350): Error: Expression reads from an uninitialized variable, it must be written to at least once before reading +fail_compilation/fastdfa.d(1349): For variable `foo` +fail_compilation/fastdfa.d(1361): Error: Dereference on null variable `foo` --- - */ +*/ + +#line 1000 @safe: @@ -78,23 +88,14 @@ void nonnullCall() nonnull1(null); // error } -void theSitchFinally() +void theSitchFinally2() { - { - goto Label; - } - - { - Label: - } - int* ptr; scope (exit) int vS = *ptr; // error int vMid = *ptr; // error - truthinessNo; } void loopy6() @@ -364,3 +365,52 @@ float uninitFloat() { float v = void; return v * 2; // error } + +int* escapeCondVar(bool cond) +{ + int* a = new int; + scope int* b = new int; + return cond ? a : b; +} // error, but on b only + +void escapeCleanupRequiredRead() @system +{ + struct S + { + ~this() + { + } + } + + S* ptr; + + { + S buf; + ptr = &buf; + buf.__xdtor; + } // ok + + S temp = *ptr; // error +} + +void unitClassField() @system +{ + class Foo + { + int a; + } + + Foo foo = void; + int v = foo.a; // error +} + +void nullClassField() +{ + class Foo + { + int a; + } + + Foo foo; + int v = foo.a; // error +} diff --git a/compiler/test/unit/semantic/control_flow.d b/compiler/test/unit/semantic/control_flow.d index 384376f512a4..915097ec42cf 100644 --- a/compiler/test/unit/semantic/control_flow.d +++ b/compiler/test/unit/semantic/control_flow.d @@ -32,7 +32,7 @@ unittest { testStatement(`false || throw new Error("");`, BE.errthrow | BE.fallt unittest { testStatement(`assert(0);`, BE.halt); } -unittest { testStatement(`int i; assert(i);`, BE.fallthru); } // Should this include errthrow? +//unittest { testStatement(`int i; assert(i);`, BE.fallthru); } // Should this include errthrow? /// Checks that `blockExit` yields `expected` for the given `code`. void testStatement(const string stmt, const BE expected)